Antd 是怎么使用 React 制作 notification 组件的
作者:markzzw 时间:2019-12-18 本文相关代码地址:github
interface NotificationProps {
maxCount?: number,
closeIcon?: React.ReactNode,
}
class Notification extends React.Component<NotificationProps, {
notices: NoticeProps[];
}> {
constructor(props: NotificationProps) {
super(props);
this.state = {
notices: [],
}
}
addNotice = (notice: NoticeProps) => {
const key = notice.noticeId = notice.noticeId || cuid();
const { maxCount } = this.props;
this.setState(previousState => {
const notices = previousState.notices;
const noticeIndex = notices.map(v => v.noticeId).indexOf(key);
const updatedNotices = notices.concat();
if (noticeIndex !== -1) {
updatedNotices.splice(noticeIndex, 1, notice);
} else {
if (maxCount && notices.length >= maxCount) {
updatedNotices.shift();
}
notice.key = notice.noticeId;
updatedNotices.push(notice);
}
return {
notices: updatedNotices,
};
});
}
removeNotice = (key: string) => this.setState(previousState => ({
notices: previousState.notices.filter(n => n.noticeId !== key),
}));
render() {
const { notices } = this.state;
return <div className="notices">{notices.map((n: NoticeProps) => <Notice {...n} onClose={this.removeNotice} />)}</div>;
}
}
export default Notification;
不难理解,我们使用 state 进行每个组件中的 notice 进行管理,然后将其渲染,相当于是每个实例中单独管理自己的 notice list,这里需要将其看做成为一个实例化的类,并且因为使用了 react 会因为 state 的改变而重绘,所以 state 是最佳载体
export interface NoticeProps extends CommonComponentProps {
onClose: (key: string) => void;
content: React.ReactNode;
key: string;
noticeId: string;
duration?: number;
disableIcon?: boolean;
}
class Notice extends React.Component<NoticeProps> {
key: string;
timer: NodeJS.Timeout | null;
constructor(props: NoticeProps) {
super(props);
this.key = this.props.noticeId;
this.timer = null;
}
componentDidMount() {
const { duration, onClose } = this.props;
if (duration) {
this.timer = setTimeout(() => {
onClose(this.key);
}, duration);
}
}
componentWillUnmount() {
if (this.timer) {
clearTimeout(this.timer as NodeJS.Timeout);
this.timer = null;
}
}
onMouseEnter = (e: React.MouseEvent) => {
e.preventDefault();
const { duration } = this.props;
if (duration || this.timer) {
clearTimeout(this.timer as NodeJS.Timeout);
this.timer = null;
}
}
onMouseLeave = (e: React.MouseEvent) => {
e.preventDefault();
const { duration, onClose } = this.props;
if (duration) {
this.timer = setTimeout(() => {
onClose(this.key);
}, duration);
}
}
render() {
const { className, content, noticeId, disableIcon, style, onClose } = this.props;
const classname = classnames('pb-notice', className);
const closeIcon = disableIcon ? null : (
<div className="notice-close-btn" onClick={() => {
onClose(this.key);
}}>X</div>);
return <div
id={noticeId}
className={classname}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
style={style}
>
<div className="notice-content">{content}</div>
{closeIcon}
</div>;
}
}
export default Notice;
type placementType =
'right-top' |
'left-top' |
'right-bottom' |
'left-bottom' |
'center-top';
export interface NotificationOptions {
message: React.ReactNode;
description?: React.ReactNode;
className?: string;
top?: number;
bottom?: number;
duration?: number | null;
onClose?: (e: any) => void;
placement?: placementType;
}
type messageType = 'info' | 'error' | 'succes' | 'warning';
interface MessageOptions extends NotificationOptions {
type: messageType;
}
export interface NotificationInstanceProps extends NotificationOptions{
container: HTMLElement;
}
export interface NotificationInstanceCallbackReturn {
notice: (noticeProps: NoticeProps) => void,
removeNotice: (key: string) => void,
destroy: () => void,
component: Notification,
container: HTMLElement
}
class NotificationFactory {
notifications: { [key: string]: { notification: NotificationInstanceCallbackReturn, div: HTMLDivElement } };
defaultPlacement = 'right-top';
constructor() {
this.notifications = {};
}
private genClassName = (placement: placementType) => `pb-notifcation-${placement}`;
private getContainer = (placement: placementType) => {
if (get(this.notifications, [placement, 'div'], '')) {
return this.notifications[placement].div;
}
const container = document.createElement('div');
container.className = this.genClassName(placement);
return container;
};
private genNotificationProps = (options: NotificationOptions) => {
let props: any = {};
const content = get(options, 'message', '');
const duration = get(options, 'duration', null);
props.content = content;
if (duration) props.duration = duration;
return props;
}
private genMessageProps = (options: MessageOptions) => {
let props: any = {};
const content = get(options, 'message', '');
const duration = get(options, 'duration', 3000);
const type = get(options, 'type', 'info');
props.content = content;
props.duration = duration;
props.className = `pb-message pb-message-${type}`;
props.disableIcon = true;
return props;
}
getNotificationInstance = (
props: NotificationInstanceProps,
callback: (n: NotificationInstanceCallbackReturn) => void
) => {
const div = props.container || document.createElement('div');
document.body.appendChild(div);
let called = false;
function ref(notification: Notification) {
if (called) {
return;
}
called = true;
callback({
notice: (noticeProps: NoticeProps) => notification.addNotice(noticeProps),
removeNotice: (key: string) => notification.removeNotice(key),
component: notification,
destroy: () => {
ReactDOM.unmountComponentAtNode(div);
div.parentNode && div.parentNode.removeChild(div);
},
container: div
});
}
ReactDOM.render(<Notification {...props} ref={ref} />, div);
};
open = (
options: NotificationOptions | MessageOptions,
type: 'notice' | 'message'
) => {
const placement = type === 'message'
? 'center-top'
: get(options, 'placement', this.defaultPlacement) as placementType;
const currentNotification = get(this.notifications, [placement, 'notification'], null);
if (currentNotification) {
currentNotification.notice(
type === 'message'
? this.genMessageProps(options as MessageOptions)
: this.genNotificationProps(options)
);
} else {
const div = this.getContainer(placement);
this.getNotificationInstance({
container: div,
...options
}, (n: NotificationInstanceCallbackReturn) => {
this.notifications[placement] = {
notification: n,
div: n.container as HTMLDivElement
}
n.notice(
type === 'message'
? this.genMessageProps(options as MessageOptions)
: this.genNotificationProps(options)
);
});
}
}
notice = (options: NotificationOptions) => {
this.open(options, 'notice');
}
message = (options: MessageOptions) => {
this.open(options, 'message');
}
}
export default NotificationFactory;