feat: App support component (#45292)

* feat: App support component

* test: update test case

* chore: update ts
This commit is contained in:
二货爱吃白萝卜 2023-10-12 11:08:01 +08:00 committed by GitHub
parent 012e8781c5
commit eaf4949ef0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 47 additions and 8 deletions

View File

@ -2,3 +2,5 @@
export type LiteralUnion<T extends string> = T | (string & {}); export type LiteralUnion<T extends string> = T | (string & {});
export type AnyObject = Record<PropertyKey, any>; export type AnyObject = Record<PropertyKey, any>;
export type CustomComponent<P = AnyObject> = React.ComponentType<P> | string;

View File

@ -1,6 +1,7 @@
import { SmileOutlined } from '@ant-design/icons';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { SmileOutlined } from '@ant-design/icons';
import type { NotificationConfig } from 'antd/es/notification/interface'; import type { NotificationConfig } from 'antd/es/notification/interface';
import App from '..'; import App from '..';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
@ -208,4 +209,26 @@ describe('App', () => {
).toBeTruthy(); ).toBeTruthy();
}); });
}); });
describe('component', () => {
it('replace', () => {
const { container } = render(
<App component="section">
<p />
</App>,
);
expect(container.querySelector('section.ant-app')).toBeTruthy();
});
it('to false', () => {
const { container } = render(
<App component={false}>
<p />
</App>,
);
expect(container.querySelector('.ant-app')).toBeFalsy();
});
});
}); });

View File

@ -29,8 +29,8 @@ Application wrapper for some global usages.
App provides upstream and downstream method calls through `Context`, because useApp needs to be used as a subcomponent, we recommend encapsulating App at the top level in the application. App provides upstream and downstream method calls through `Context`, because useApp needs to be used as a subcomponent, we recommend encapsulating App at the top level in the application.
```tsx ```tsx
import { App } from 'antd';
import React from 'react'; import React from 'react';
import { App } from 'antd';
const MyPage: React.FC = () => { const MyPage: React.FC = () => {
const { message, notification, modal } = App.useApp(); const { message, notification, modal } = App.useApp();
@ -102,8 +102,9 @@ export { message, modal, notification };
```tsx ```tsx
// sub page // sub page
import { Button, Space } from 'antd';
import React from 'react'; import React from 'react';
import { Button, Space } from 'antd';
import { message } from './store'; import { message } from './store';
export default () => { export default () => {
@ -129,6 +130,7 @@ Common props ref[Common props](/docs/react/common-props)
| Property | Description | Type | Default | Version | | Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| component | Config render element, if `false` will not create DOM node | ComponentType | div | 5.11.0 |
| message | Global config for Message | [MessageConfig](/components/message/#messageconfig) | - | 5.3.0 | | message | Global config for Message | [MessageConfig](/components/message/#messageconfig) | - | 5.3.0 |
| notification | Global config for Notification | [NotificationConfig](/components/notification/#notificationconfig) | - | 5.3.0 | | notification | Global config for Notification | [NotificationConfig](/components/notification/#notificationconfig) | - | 5.3.0 |

View File

@ -1,6 +1,8 @@
import classNames from 'classnames';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import classNames from 'classnames';
import type { CustomComponent } from '../_util/type';
import type { ConfigConsumerProps } from '../config-provider'; import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider'; import { ConfigContext } from '../config-provider';
import useMessage from '../message/useMessage'; import useMessage from '../message/useMessage';
@ -16,6 +18,7 @@ export interface AppProps extends AppConfig {
rootClassName?: string; rootClassName?: string;
prefixCls?: string; prefixCls?: string;
children?: ReactNode; children?: ReactNode;
component?: false | CustomComponent;
} }
const useApp = () => React.useContext<useAppProps>(AppContext); const useApp = () => React.useContext<useAppProps>(AppContext);
@ -29,6 +32,7 @@ const App: React.FC<AppProps> & { useApp: typeof useApp } = (props) => {
message, message,
notification, notification,
style, style,
component = 'div',
} = props; } = props;
const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext); const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext);
const prefixCls = getPrefixCls('app', customizePrefixCls); const prefixCls = getPrefixCls('app', customizePrefixCls);
@ -60,15 +64,22 @@ const App: React.FC<AppProps> & { useApp: typeof useApp } = (props) => {
[messageApi, notificationApi, ModalApi], [messageApi, notificationApi, ModalApi],
); );
// ============================ Render ============================
const Component = component === false ? React.Fragment : component;
const rootProps = {
className: customClassName,
style,
};
return wrapSSR( return wrapSSR(
<AppContext.Provider value={memoizedContextValue}> <AppContext.Provider value={memoizedContextValue}>
<AppConfigContext.Provider value={mergedAppConfig}> <AppConfigContext.Provider value={mergedAppConfig}>
<div className={customClassName} style={style}> <Component {...rootProps}>
{ModalContextHolder} {ModalContextHolder}
{messageContextHolder} {messageContextHolder}
{notificationContextHolder} {notificationContextHolder}
{children} {children}
</div> </Component>
</AppConfigContext.Provider> </AppConfigContext.Provider>
</AppContext.Provider>, </AppContext.Provider>,
); );

View File

@ -131,6 +131,7 @@ export default () => {
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| component | 设置渲染元素,为 `false` 则不创建 DOM 节点 | ComponentType | div | 5.11.0 |
| message | App 内 Message 的全局配置 | [MessageConfig](/components/message-cn/#messageconfig) | - | 5.3.0 | | message | App 内 Message 的全局配置 | [MessageConfig](/components/message-cn/#messageconfig) | - | 5.3.0 |
| notification | App 内 Notification 的全局配置 | [NotificationConfig](/components/notification-cn/#notificationconfig) | - | 5.3.0 | | notification | App 内 Notification 的全局配置 | [NotificationConfig](/components/notification-cn/#notificationconfig) | - | 5.3.0 |

View File

@ -1,6 +1,6 @@
import type React from 'react'; import type React from 'react';
import type { AnyObject } from '../_util/type'; import type { AnyObject, CustomComponent } from '../_util/type';
import type { SizeType } from '../config-provider/SizeContext'; import type { SizeType } from '../config-provider/SizeContext';
export interface FlexProps<P = AnyObject> extends React.HTMLAttributes<HTMLElement> { export interface FlexProps<P = AnyObject> extends React.HTMLAttributes<HTMLElement> {
@ -13,5 +13,5 @@ export interface FlexProps<P = AnyObject> extends React.HTMLAttributes<HTMLEleme
flex?: React.CSSProperties['flex']; flex?: React.CSSProperties['flex'];
gap?: React.CSSProperties['gap'] | SizeType; gap?: React.CSSProperties['gap'] | SizeType;
children: React.ReactNode; children: React.ReactNode;
component?: React.ComponentType<P> | string; component?: CustomComponent<P>;
} }