mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
feat: Message hooks API (#25422)
* chore: comment on usePatchElement * refactor: conform message & notifaction code logic * feat: message useMessage, wip * feat: message.useMessage, it works now * fix: promise on regular api * feat: message hooks * chore: fix lint * chore: new line * chore: revert new line * refactor: prefixCls * fix: prefixCls * test: cov * chore * chore * chore * chore * docs * docs: message hooks faq * test: remove useless config provider * chore: remove some test codes * chore * docs: hooks version
This commit is contained in:
parent
6a65b47df3
commit
01cec29a8e
@ -23,6 +23,7 @@ lib/**/*
|
||||
node_modules
|
||||
_site
|
||||
dist
|
||||
coverage
|
||||
**/*.d.ts
|
||||
# Scripts
|
||||
scripts/previewEditor/**/*
|
@ -7,8 +7,11 @@ export default function usePatchElement(): [
|
||||
const [elements, setElements] = React.useState<React.ReactElement[]>([]);
|
||||
|
||||
function patchElement(element: React.ReactElement) {
|
||||
// append a new element to elements (and create a new ref)
|
||||
setElements(originElements => [...originElements, element]);
|
||||
|
||||
// return a function that removes the new element out of elements (and create a new ref)
|
||||
// it works a little like useEffect
|
||||
return () => {
|
||||
setElements(originElements => originElements.filter(ele => ele !== element));
|
||||
};
|
||||
|
@ -22,6 +22,17 @@ exports[`renders ./components/message/demo/duration.md correctly 1`] = `
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/message/demo/hooks.md correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Display normal message
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/message/demo/info.md correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
|
195
components/message/__tests__/hooks.test.js
Normal file
195
components/message/__tests__/hooks.test.js
Normal file
@ -0,0 +1,195 @@
|
||||
/* eslint-disable jsx-a11y/control-has-associated-label */
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import message from '..';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
|
||||
describe('message.hooks', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
message.destroy();
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
const Context = React.createContext('light');
|
||||
|
||||
const Demo = () => {
|
||||
const [api, holder] = message.useMessage();
|
||||
|
||||
return (
|
||||
<ConfigProvider prefixCls="my-test">
|
||||
<Context.Provider value="bamboo">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
api.open({
|
||||
content: (
|
||||
<Context.Consumer>
|
||||
{name => <span className="hook-test-result">{name}</span>}
|
||||
</Context.Consumer>
|
||||
),
|
||||
duration: 0,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{holder}
|
||||
</Context.Provider>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const wrapper = mount(<Demo />);
|
||||
wrapper.find('button').simulate('click');
|
||||
expect(document.querySelectorAll('.my-test-message-notice').length).toBe(1);
|
||||
expect(document.querySelector('.hook-test-result').innerHTML).toEqual('bamboo');
|
||||
});
|
||||
|
||||
it('should work with success', () => {
|
||||
const Context = React.createContext('light');
|
||||
|
||||
const Demo = () => {
|
||||
const [api, holder] = message.useMessage();
|
||||
|
||||
return (
|
||||
<ConfigProvider prefixCls="my-test">
|
||||
<Context.Provider value="bamboo">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
api.success({
|
||||
content: (
|
||||
<Context.Consumer>
|
||||
{name => <span className="hook-test-result">{name}</span>}
|
||||
</Context.Consumer>
|
||||
),
|
||||
duration: 0,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{holder}
|
||||
</Context.Provider>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const wrapper = mount(<Demo />);
|
||||
wrapper.find('button').simulate('click');
|
||||
expect(document.querySelectorAll('.my-test-message-notice').length).toBe(1);
|
||||
expect(document.querySelectorAll('.anticon-check-circle').length).toBe(1);
|
||||
expect(document.querySelector('.hook-test-result').innerHTML).toEqual('bamboo');
|
||||
});
|
||||
|
||||
it('should work with onClose', done => {
|
||||
// if not use real timer, done won't be called
|
||||
jest.useRealTimers();
|
||||
const Demo = () => {
|
||||
const [api, holder] = message.useMessage();
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
api.open({
|
||||
content: 'amazing',
|
||||
duration: 1,
|
||||
onClose() {
|
||||
done();
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{holder}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const wrapper = mount(<Demo />);
|
||||
wrapper.find('button').simulate('click');
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it('should work with close promise', done => {
|
||||
// if not use real timer, done won't be called
|
||||
jest.useRealTimers();
|
||||
const Demo = () => {
|
||||
const [api, holder] = message.useMessage();
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
api
|
||||
.open({
|
||||
content: 'good',
|
||||
duration: 1,
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{holder}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const wrapper = mount(<Demo />);
|
||||
wrapper.find('button').simulate('click');
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it('should work with hide', () => {
|
||||
let hide;
|
||||
const Demo = () => {
|
||||
const [api, holder] = message.useMessage();
|
||||
return (
|
||||
<ConfigProvider prefixCls="my-test">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
hide = api.open({
|
||||
content: 'nice',
|
||||
duration: 0,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{holder}
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const wrapper = mount(<Demo />);
|
||||
wrapper.find('button').simulate('click');
|
||||
jest.runAllTimers();
|
||||
expect(document.querySelectorAll('.my-test-message-notice').length).toBe(1);
|
||||
hide();
|
||||
jest.runAllTimers();
|
||||
expect(document.querySelectorAll('.my-test-message-notice').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should be same hook', () => {
|
||||
let count = 0;
|
||||
|
||||
const Demo = () => {
|
||||
const [, forceUpdate] = React.useState({});
|
||||
const [api] = message.useMessage();
|
||||
|
||||
React.useEffect(() => {
|
||||
count += 1;
|
||||
expect(count).toEqual(1);
|
||||
forceUpdate();
|
||||
}, [api]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
mount(<Demo />);
|
||||
});
|
||||
});
|
42
components/message/demo/hooks.md
Normal file
42
components/message/demo/hooks.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
order: 10
|
||||
title:
|
||||
zh-CN: 通过 Hooks 获取上下文(4.5.0+)
|
||||
en-US: Get context with hooks (4.5.0+)
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
通过 `message.useMessage` 创建支持读取 context 的 `contextHolder`。
|
||||
|
||||
## en-US
|
||||
|
||||
Use `message.useMessage` to get `contextHolder` with context accessible issue.
|
||||
|
||||
```jsx
|
||||
import { message, Button } from 'antd';
|
||||
|
||||
const Context = React.createContext({ name: 'Default' });
|
||||
|
||||
function Demo() {
|
||||
const [messsageApi, contextHolder] = message.useMessage();
|
||||
const info = () => {
|
||||
messsageApi.open({
|
||||
type: 'info',
|
||||
content: <Context.Consumer>{({ name }) => `Hello, ${name}!`}</Context.Consumer>,
|
||||
duration: 1,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ name: 'Ant Design' }}>
|
||||
{contextHolder}
|
||||
<Button type="primary" onClick={info}>
|
||||
Display normal message
|
||||
</Button>
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Demo />, mountNode);
|
||||
```
|
92
components/message/hooks/useMessage.tsx
Normal file
92
components/message/hooks/useMessage.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import * as React from 'react';
|
||||
import useRCNotification from 'rc-notification/lib/useNotification';
|
||||
import {
|
||||
NotificationInstance as RCNotificationInstance,
|
||||
NoticeContent as RCNoticeContent,
|
||||
HolderReadyCallback as RCHolderReadyCallback,
|
||||
} from 'rc-notification/lib/Notification';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../../config-provider';
|
||||
import {
|
||||
MessageInstance,
|
||||
ArgsProps,
|
||||
attachTypeApi,
|
||||
ThenableArgument,
|
||||
getKeyThenIncreaseKey,
|
||||
} from '..';
|
||||
|
||||
export default function createUseMessage(
|
||||
getRcNotificationInstance: (
|
||||
args: ArgsProps,
|
||||
callback: (info: { prefixCls: string; instance: RCNotificationInstance }) => void,
|
||||
) => void,
|
||||
getRCNoticeProps: (args: ArgsProps, prefixCls: string) => RCNoticeContent,
|
||||
) {
|
||||
const useMessage = (): [MessageInstance, React.ReactElement] => {
|
||||
// We can only get content by render
|
||||
let getPrefixCls: ConfigConsumerProps['getPrefixCls'];
|
||||
|
||||
// We create a proxy to handle delay created instance
|
||||
let innerInstance: RCNotificationInstance | null = null;
|
||||
const proxy = {
|
||||
add: (noticeProps: RCNoticeContent, holderCallback?: RCHolderReadyCallback) => {
|
||||
innerInstance?.component.add(noticeProps, holderCallback);
|
||||
},
|
||||
} as any;
|
||||
|
||||
const [hookNotify, holder] = useRCNotification(proxy);
|
||||
|
||||
function notify(args: ArgsProps) {
|
||||
const { prefixCls: customizePrefixCls } = args;
|
||||
const mergedPrefixCls = getPrefixCls('message', customizePrefixCls);
|
||||
const target = args.key || getKeyThenIncreaseKey();
|
||||
const closePromise = new Promise(resolve => {
|
||||
const callback = () => {
|
||||
if (typeof args.onClose === 'function') {
|
||||
args.onClose();
|
||||
}
|
||||
return resolve(true);
|
||||
};
|
||||
getRcNotificationInstance(
|
||||
{
|
||||
...args,
|
||||
prefixCls: mergedPrefixCls,
|
||||
},
|
||||
({ prefixCls, instance }) => {
|
||||
innerInstance = instance;
|
||||
hookNotify(getRCNoticeProps({ ...args, key: target, onClose: callback }, prefixCls));
|
||||
},
|
||||
);
|
||||
});
|
||||
const result: any = () => {
|
||||
if (innerInstance) {
|
||||
innerInstance.removeNotice(target);
|
||||
}
|
||||
};
|
||||
result.then = (filled: ThenableArgument, rejected: ThenableArgument) =>
|
||||
closePromise.then(filled, rejected);
|
||||
result.promise = closePromise;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fill functions
|
||||
const hookApiRef = React.useRef<any>({});
|
||||
|
||||
hookApiRef.current.open = notify;
|
||||
|
||||
['success', 'info', 'warning', 'error', 'loading'].forEach(type =>
|
||||
attachTypeApi(hookApiRef.current, type),
|
||||
);
|
||||
|
||||
return [
|
||||
hookApiRef.current,
|
||||
<ConfigConsumer key="holder">
|
||||
{(context: ConfigConsumerProps) => {
|
||||
({ getPrefixCls } = context);
|
||||
return holder;
|
||||
}}
|
||||
</ConfigConsumer>,
|
||||
];
|
||||
};
|
||||
|
||||
return useMessage;
|
||||
}
|
@ -90,3 +90,27 @@ message.config({
|
||||
| top | Distance from top | number | 24 | |
|
||||
| rtl | Whether to enable RTL mode | boolean | false | |
|
||||
| prefixCls | The prefix className of message node | string | `ant-message` | 4.5.0 |
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why I can not access context, redux in message?
|
||||
|
||||
antd will dynamic create React instance by `ReactDOM.render` when call message methods. Whose context is different with origin code located context.
|
||||
|
||||
When you need context info (like ConfigProvider context), you can use `message.useMessage` to get `api` instance and `contextHolder` node. And put it in your children:
|
||||
|
||||
```tsx
|
||||
const [api, contextHolder] = message.useMessage();
|
||||
|
||||
return (
|
||||
<Context1.Provider value="Ant">
|
||||
{/* contextHolder is inside Context1 which means api will get value of Context1 */}
|
||||
{contextHolder}
|
||||
<Context2.Provider value="Design">
|
||||
{/* contextHolder is outside Context2 which means api will **not** get value of Context2 */}
|
||||
</Context2.Provider>
|
||||
</Context1.Provider>
|
||||
);
|
||||
```
|
||||
|
||||
**Note:** You must insert `contextHolder` into your children with hooks. You can use origin method if you do not need context connection.
|
||||
|
@ -1,28 +1,83 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Notification from 'rc-notification';
|
||||
import RCNotification from 'rc-notification';
|
||||
import {
|
||||
NotificationInstance as RCNotificationInstance,
|
||||
NoticeContent,
|
||||
} from 'rc-notification/lib/Notification';
|
||||
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
|
||||
import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
|
||||
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
|
||||
import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled';
|
||||
import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled';
|
||||
import createUseMessage from './hooks/useMessage';
|
||||
|
||||
type NoticeType = 'info' | 'success' | 'error' | 'warning' | 'loading';
|
||||
|
||||
let messageInstance: RCNotificationInstance | null;
|
||||
let defaultDuration = 3;
|
||||
let defaultTop: number;
|
||||
let messageInstance: any;
|
||||
let key = 1;
|
||||
let prefixCls = 'ant-message';
|
||||
let localPrefixCls = 'ant-message';
|
||||
let transitionName = 'move-up';
|
||||
let getContainer: () => HTMLElement;
|
||||
let maxCount: number;
|
||||
let rtl = false;
|
||||
|
||||
function getMessageInstance(callback: (i: any) => void) {
|
||||
export function getKeyThenIncreaseKey() {
|
||||
return key++;
|
||||
}
|
||||
|
||||
export interface ConfigOptions {
|
||||
top?: number;
|
||||
duration?: number;
|
||||
prefixCls?: string;
|
||||
getContainer?: () => HTMLElement;
|
||||
transitionName?: string;
|
||||
maxCount?: number;
|
||||
rtl?: boolean;
|
||||
}
|
||||
|
||||
function setMessageConfig(options: ConfigOptions) {
|
||||
if (options.top !== undefined) {
|
||||
defaultTop = options.top;
|
||||
messageInstance = null; // delete messageInstance for new defaultTop
|
||||
}
|
||||
if (options.duration !== undefined) {
|
||||
defaultDuration = options.duration;
|
||||
}
|
||||
if (options.prefixCls !== undefined) {
|
||||
localPrefixCls = options.prefixCls;
|
||||
}
|
||||
if (options.getContainer !== undefined) {
|
||||
getContainer = options.getContainer;
|
||||
}
|
||||
if (options.transitionName !== undefined) {
|
||||
transitionName = options.transitionName;
|
||||
messageInstance = null; // delete messageInstance for new transitionName
|
||||
}
|
||||
if (options.maxCount !== undefined) {
|
||||
maxCount = options.maxCount;
|
||||
messageInstance = null;
|
||||
}
|
||||
if (options.rtl !== undefined) {
|
||||
rtl = options.rtl;
|
||||
}
|
||||
}
|
||||
|
||||
function getRCNotificationInstance(
|
||||
args: ArgsProps,
|
||||
callback: (info: { prefixCls: string; instance: RCNotificationInstance }) => void,
|
||||
) {
|
||||
const prefixCls = args.prefixCls || localPrefixCls;
|
||||
if (messageInstance) {
|
||||
callback(messageInstance);
|
||||
callback({
|
||||
prefixCls,
|
||||
instance: messageInstance,
|
||||
});
|
||||
return;
|
||||
}
|
||||
Notification.newInstance(
|
||||
RCNotification.newInstance(
|
||||
{
|
||||
prefixCls,
|
||||
transitionName,
|
||||
@ -32,17 +87,21 @@ function getMessageInstance(callback: (i: any) => void) {
|
||||
},
|
||||
(instance: any) => {
|
||||
if (messageInstance) {
|
||||
callback(messageInstance);
|
||||
callback({
|
||||
prefixCls,
|
||||
instance: messageInstance,
|
||||
});
|
||||
return;
|
||||
}
|
||||
messageInstance = instance;
|
||||
callback(instance);
|
||||
callback({
|
||||
prefixCls,
|
||||
instance,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
type NoticeType = 'info' | 'success' | 'error' | 'warning' | 'loading';
|
||||
|
||||
export interface ThenableArgument {
|
||||
(val: any): void;
|
||||
}
|
||||
@ -53,10 +112,18 @@ export interface MessageType {
|
||||
promise: Promise<void>;
|
||||
}
|
||||
|
||||
const typeToIcon = {
|
||||
info: InfoCircleFilled,
|
||||
success: CheckCircleFilled,
|
||||
error: CloseCircleFilled,
|
||||
warning: ExclamationCircleFilled,
|
||||
loading: LoadingOutlined,
|
||||
};
|
||||
export interface ArgsProps {
|
||||
content: React.ReactNode;
|
||||
duration: number | null;
|
||||
type: NoticeType;
|
||||
prefixCls?: string;
|
||||
onClose?: () => void;
|
||||
icon?: React.ReactNode;
|
||||
key?: string | number;
|
||||
@ -64,23 +131,29 @@ export interface ArgsProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const iconMap = {
|
||||
info: InfoCircleFilled,
|
||||
success: CheckCircleFilled,
|
||||
error: CloseCircleFilled,
|
||||
warning: ExclamationCircleFilled,
|
||||
loading: LoadingOutlined,
|
||||
};
|
||||
|
||||
function notice(args: ArgsProps): MessageType {
|
||||
function getRCNoticeProps(args: ArgsProps, prefixCls: string): NoticeContent {
|
||||
const duration = args.duration !== undefined ? args.duration : defaultDuration;
|
||||
const IconComponent = iconMap[args.type];
|
||||
|
||||
const IconComponent = typeToIcon[args.type];
|
||||
const messageClass = classNames(`${prefixCls}-custom-content`, {
|
||||
[`${prefixCls}-${args.type}`]: args.type,
|
||||
[`${prefixCls}-rtl`]: rtl === true,
|
||||
});
|
||||
return {
|
||||
key: args.key,
|
||||
duration,
|
||||
style: args.style || {},
|
||||
className: args.className,
|
||||
content: (
|
||||
<div className={messageClass}>
|
||||
{args.icon || (IconComponent && <IconComponent />)}
|
||||
<span>{args.content}</span>
|
||||
</div>
|
||||
),
|
||||
onClose: args.onClose,
|
||||
};
|
||||
}
|
||||
|
||||
function notice(args: ArgsProps): MessageType {
|
||||
const target = args.key || key++;
|
||||
const closePromise = new Promise(resolve => {
|
||||
const callback = () => {
|
||||
@ -89,20 +162,8 @@ function notice(args: ArgsProps): MessageType {
|
||||
}
|
||||
return resolve(true);
|
||||
};
|
||||
getMessageInstance(instance => {
|
||||
instance.notice({
|
||||
key: target,
|
||||
duration,
|
||||
style: args.style || {},
|
||||
className: args.className,
|
||||
content: (
|
||||
<div className={messageClass}>
|
||||
{args.icon || (IconComponent && <IconComponent />)}
|
||||
<span>{args.content}</span>
|
||||
</div>
|
||||
),
|
||||
onClose: callback,
|
||||
});
|
||||
getRCNotificationInstance(args, ({ prefixCls, instance }) => {
|
||||
instance.notice(getRCNoticeProps({ ...args, key: target, onClose: callback }, prefixCls));
|
||||
});
|
||||
});
|
||||
const result: any = () => {
|
||||
@ -128,44 +189,9 @@ function isArgsProps(content: JointContent): content is ArgsProps {
|
||||
);
|
||||
}
|
||||
|
||||
export interface ConfigOptions {
|
||||
top?: number;
|
||||
duration?: number;
|
||||
prefixCls?: string;
|
||||
getContainer?: () => HTMLElement;
|
||||
transitionName?: string;
|
||||
maxCount?: number;
|
||||
rtl?: boolean;
|
||||
}
|
||||
|
||||
const api: any = {
|
||||
open: notice,
|
||||
config(options: ConfigOptions) {
|
||||
if (options.top !== undefined) {
|
||||
defaultTop = options.top;
|
||||
messageInstance = null; // delete messageInstance for new defaultTop
|
||||
}
|
||||
if (options.duration !== undefined) {
|
||||
defaultDuration = options.duration;
|
||||
}
|
||||
if (options.prefixCls !== undefined) {
|
||||
prefixCls = options.prefixCls;
|
||||
}
|
||||
if (options.getContainer !== undefined) {
|
||||
getContainer = options.getContainer;
|
||||
}
|
||||
if (options.transitionName !== undefined) {
|
||||
transitionName = options.transitionName;
|
||||
messageInstance = null; // delete messageInstance for new transitionName
|
||||
}
|
||||
if (options.maxCount !== undefined) {
|
||||
maxCount = options.maxCount;
|
||||
messageInstance = null;
|
||||
}
|
||||
if (options.rtl !== undefined) {
|
||||
rtl = options.rtl;
|
||||
}
|
||||
},
|
||||
config: setMessageConfig,
|
||||
destroy() {
|
||||
if (messageInstance) {
|
||||
messageInstance.destroy();
|
||||
@ -174,10 +200,14 @@ const api: any = {
|
||||
},
|
||||
};
|
||||
|
||||
['success', 'info', 'warning', 'error', 'loading'].forEach(type => {
|
||||
api[type] = (content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose) => {
|
||||
export function attachTypeApi(originalApi: any, type: string) {
|
||||
originalApi[type] = (
|
||||
content: JointContent,
|
||||
duration?: ConfigDuration,
|
||||
onClose?: ConfigOnClose,
|
||||
) => {
|
||||
if (isArgsProps(content)) {
|
||||
return api.open({ ...content, type });
|
||||
return originalApi.open({ ...content, type });
|
||||
}
|
||||
|
||||
if (typeof duration === 'function') {
|
||||
@ -185,22 +215,29 @@ const api: any = {
|
||||
duration = undefined;
|
||||
}
|
||||
|
||||
return api.open({ content, duration, type, onClose });
|
||||
return originalApi.open({ content, duration, type, onClose });
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
['success', 'info', 'warning', 'error', 'loading'].forEach(type => attachTypeApi(api, type));
|
||||
|
||||
api.warn = api.warning;
|
||||
api.useMessage = createUseMessage(getRCNotificationInstance, getRCNoticeProps);
|
||||
|
||||
export interface MessageApi {
|
||||
export interface MessageInstance {
|
||||
info(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
|
||||
success(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
|
||||
error(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
|
||||
warn(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
|
||||
warning(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
|
||||
loading(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
|
||||
open(args: ArgsProps): MessageType;
|
||||
}
|
||||
|
||||
export interface MessageApi extends MessageInstance {
|
||||
warn(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
|
||||
config(options: ConfigOptions): void;
|
||||
destroy(): void;
|
||||
useMessage(): [MessageInstance, React.ReactElement];
|
||||
}
|
||||
|
||||
export default api as MessageApi;
|
||||
|
@ -91,3 +91,27 @@ message.config({
|
||||
| top | 消息距离顶部的位置 | number | 24 | |
|
||||
| rtl | 是否开启 RTL 模式 | boolean | false | |
|
||||
| prefixCls | 消息节点的 className 前缀 | string | `ant-message` | 4.5.0 |
|
||||
|
||||
## FAQ
|
||||
|
||||
### 为什么 message 不能获取 context、redux 的内容?
|
||||
|
||||
直接调用 message 方法,antd 会通过 `ReactDOM.render` 动态创建新的 React 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。
|
||||
|
||||
当你需要 context 信息(例如 ConfigProvider 配置的内容)时,可以通过 `message.useMessage` 方法会返回 `api` 实体以及 `contextHolder` 节点。将其插入到你需要获取 context 位置即可:
|
||||
|
||||
```tsx
|
||||
const [api, contextHolder] = message.useMessage();
|
||||
|
||||
return (
|
||||
<Context1.Provider value="Ant">
|
||||
{/* contextHolder 在 Context1 内,它可以获得 Context1 的 context */}
|
||||
{contextHolder}
|
||||
<Context2.Provider value="Design">
|
||||
{/* contextHolder 在 Context2 外,因而不会获得 Context2 的 context */}
|
||||
</Context2.Provider>
|
||||
</Context1.Provider>
|
||||
);
|
||||
```
|
||||
|
||||
**异同:**通过 hooks 创建的 `contextHolder` 必须插入到子元素节点中才会生效,当你不需要上下文信息时请直接调用。
|
||||
|
@ -23,6 +23,7 @@ let defaultPrefixCls = 'ant-notification';
|
||||
let defaultPlacement: NotificationPlacement = 'topRight';
|
||||
let defaultGetContainer: () => HTMLElement;
|
||||
let defaultCloseIcon: React.ReactNode;
|
||||
let rtl = false;
|
||||
|
||||
export interface ConfigProps {
|
||||
top?: number;
|
||||
@ -35,7 +36,6 @@ export interface ConfigProps {
|
||||
rtl?: boolean;
|
||||
}
|
||||
|
||||
let rtl = false;
|
||||
function setNotificationConfig(options: ConfigProps) {
|
||||
const { duration, placement, bottom, top, getContainer, closeIcon, prefixCls } = options;
|
||||
if (prefixCls !== undefined) {
|
||||
@ -225,12 +225,14 @@ function getRCNoticeProps(args: ArgsProps, prefixCls: string) {
|
||||
};
|
||||
}
|
||||
|
||||
function notice(args: ArgsProps) {
|
||||
getNotificationInstance(args, ({ prefixCls, instance }) => {
|
||||
instance.notice(getRCNoticeProps(args, prefixCls));
|
||||
});
|
||||
}
|
||||
|
||||
const api: any = {
|
||||
open: (args: ArgsProps) => {
|
||||
getNotificationInstance(args, ({ prefixCls, instance }) => {
|
||||
instance.notice(getRCNoticeProps(args, prefixCls));
|
||||
});
|
||||
},
|
||||
open: notice,
|
||||
close(key: string) {
|
||||
Object.keys(notificationInstance).forEach(cacheKey =>
|
||||
Promise.resolve(notificationInstance[cacheKey]).then(instance => {
|
||||
|
Loading…
Reference in New Issue
Block a user