notification就是通知提醒框,在系统四个角显示通知提醒信息。常常用于如下状况:css
先来看一下notification的API。设计模式
notification.success(config)
notification.error(config)
notification.info(config)
notification.warning(config)
notification.warn(config)
notification.close(key: String)
notification.destroy()
能够看到,notification的API在antd的组件中能够说是很是另类的,看着是否是有点眼熟,很像常常使用的Console的API,调用起来十分简单。api
console.log()
console.error()
console.info()
console.warn()
config的配置也比较简单,主要是标题,内容,关闭时的延时和回调等。详见ANTD的官网。数组
在分析代码以前,咱们先来看下notification的结构,通知组件主要分为三层,由高到低是antd
<font color="blue">NotificationApi => Notification => n*Notice。</font>app
NotificationApi是一个封装的接口,提供统一调用的API,如info(),warn()等。函数
Notification是一个Notice容器,就是用来容纳Notice列表的父组件,提供了添加,删除等操做Notice的方法。源码分析
Notice就是咱们所看到的通知标签了。性能
先从入口index.js入手,由于这是一个notification的API封装,不是一个组件,因此没有render方法。动画
//.......省略部分代码........ const api: any = { open: notice,//入口 close(key: string) { Object.keys(notificationInstance) .forEach(cacheKey => notificationInstance[cacheKey].removeNotice(key)); }, config: setNotificationConfig, destroy() { Object.keys(notificationInstance).forEach(cacheKey => { notificationInstance[cacheKey].destroy(); delete notificationInstance[cacheKey]; }); }, }; //.......省略部分代码........ ['success', 'info', 'warning', 'error'].forEach((type) => { api[type] = (args: ArgsProps) => api.open({ ...args, type, }); }); api.warn = api.warning; export interface NotificationApi { success(args: ArgsProps): void; error(args: ArgsProps): void; info(args: ArgsProps): void; warn(args: ArgsProps): void; warning(args: ArgsProps): void; open(args: ArgsProps): void; close(key: string): void; config(options: ConfigProps): void; destroy(): void; } export default api as NotificationApi;
接口比较清晰,能够看出API提供的不一样的方法实际是经过一个相似工厂方法的open函数实现的,open函数的具体实现是notice,那么看下这个notice函数。
function notice(args: ArgsProps) { const outerPrefixCls = args.prefixCls || 'ant-notification'; const prefixCls = `${outerPrefixCls}-notice`; const duration = args.duration === undefined ? defaultDuration : args.duration; //生成icon组件 let iconNode: React.ReactNode = null; if (args.icon) { iconNode = ( <span className={`${prefixCls}-icon`}> {args.icon} </span> ); } else if (args.type) { const iconType = typeToIcon[args.type]; iconNode = ( <Icon className={`${prefixCls}-icon ${prefixCls}-icon-${args.type}`} type={iconType} /> ); } const autoMarginTag = (!args.description && iconNode) ? <span className={`${prefixCls}-message-single-line-auto-margin`} /> : null; getNotificationInstance(outerPrefixCls, args.placement || defaultPlacement, (notification: any) => { notification.notice({ content: ( <div className={iconNode ? `${prefixCls}-with-icon` : ''}> {iconNode} <div className={`${prefixCls}-message`}> {autoMarginTag} {args.message} </div> <div className={`${prefixCls}-description`}>{args.description}</div> {args.btn ? <span className={`${prefixCls}-btn`}>{args.btn}</span> : null} </div> ), duration, closable: true, onClose: args.onClose, key: args.key, style: args.style || {}, className: args.className, }); }); }
这段代码主要的部分就是调用了getNotificationInstance函数,看名字应该是获得Notification的实例,命名方式是典型的单例模式,做为列表的容器组件,使用单例模式不只节省了内存空间,并且单例延迟执行的特性也保证了在没有通知的状况下不会生成notification组件,提高了页面的性能。
function getNotificationInstance(prefixCls: string, placement: NotificationPlacement, callback: (n: any) => void)
查看定义,第一个参数是css前缀,第二个参数是notification的弹出位置,分为topLeft topRight bottomLeft bottomRight,第三个参数是一个回调,回调的参数是notification实例,能够看到,在回调中调用了notification的notice方法,notice方法的参数是一个对象,content看名字应该是通知标签的内容,其余的参数也是调用notification中传入的config参数。
接下来看下getNotificationInstance的实现
function getNotificationInstance(prefixCls: string, placement: NotificationPlacement, callback: (n: any) => void) { const cacheKey = `${prefixCls}-${placement}`; if (notificationInstance[cacheKey]) { callback(notificationInstance[cacheKey]); return; } //---实例化Notification组件 (Notification as any).newInstance({ prefixCls, className: `${prefixCls}-${placement}`, style: getPlacementStyle(placement), getContainer: defaultGetContainer, }, (notification: any) => { notificationInstance[cacheKey] = notification; callback(notification); }); }
代码很简短,能够看到确实是使用了单例模式,由于存在4个弹出位置,因此将每一个位置的notification实例存放在notificationInstance[cacheKey]数组里,cacheKey是css前缀和弹出位置的组合,用以区分每一个实例。接下来进入newInstance方法来看下是怎么使用单例模式生成notification实例的。
Notification.newInstance = function newNotificationInstance(properties, callback) { const { getContainer, ...props } = properties || {}; const div = document.createElement('div'); if (getContainer) { const root = getContainer(); root.appendChild(div); } else { document.body.appendChild(div); } let called = false; function ref(notification) { if (called) { return; } called = true; callback({ notice(noticeProps) { notification.add(noticeProps); }, removeNotice(key) { notification.remove(key); }, component: notification, destroy() { ReactDOM.unmountComponentAtNode(div); div.parentNode.removeChild(div); }, }); } ReactDOM.render(<Notification {...props} ref={ref} />, div); };
主要完成了两件事
能够看到传入callback的参数对notification又作了一层封装,目的是为了封装destroy函数,其中
+ notice():添加一个notice组件到notification + removeNotice():删除指定notice组件。 + destroy():销毁notification组件。
再回过头来看回调函数的内容。
getNotificationInstance(outerPrefixCls, args.placement || defaultPlacement, (notification: any) => { notification.notice({ content: ( <div className={iconNode ? `${prefixCls}-with-icon` : ''}> {iconNode} <div className={`${prefixCls}-message`}> {autoMarginTag} {args.message} </div> <div className={`${prefixCls}-description`}>{args.description}</div> {args.btn ? <span className={`${prefixCls}-btn`}>{args.btn}</span> : null} </div> ), duration, closable: true, onClose: args.onClose, key: args.key, style: args.style || {}, className: args.className, }); });
调用了notification的notice方法,由前面的代码可知notice实际上是调用了Notification组件的add方法,记下来看下add方法是怎样将标签添加进Notification的。
//省略部分代码 state = { notices: [], }; //省略部分代码 add = (notice) => { const key = notice.key = notice.key || getUuid(); this.setState(previousState => { const notices = previousState.notices; if (!notices.filter(v => v.key === key).length) { return { notices: notices.concat(notice), }; } }); }
Notification将要显示的通知列表存在state的notices中,同经过add函数动态添加,key是该notice的惟一标识,经过filter将已存在的标签过滤掉。能够想见,Notification就是将state中的notices经过map渲染出要显示的标签列表,直接进入Notification组件的render方法。
render() { const props = this.props; const noticeNodes = this.state.notices.map((notice) => { const onClose = createChainedFunction(this.remove.bind(this, notice.key), notice.onClose); return (<Notice prefixCls={props.prefixCls} {...notice} onClose={onClose} > {notice.content} </Notice>); }); const className = { [props.prefixCls]: 1, [props.className]: !!props.className, }; return ( <div className={classnames(className)} style={props.style}> <Animate transitionName={this.getTransitionName()}>{noticeNodes}</Animate> </div> ); } }
根据state的notices生成Notice组件列表noticeNodes,而后将noticeNodes插入到一个Animate的动画组件中。其中createChainedFunction的做用是一次调用传入的各函数,其中remove方法是移除state中相应的节点,onClose是传入的关闭标签后的回调函数。
看到这里Notification的结构已经比较清晰了,最后再来看下Notice组件的实现。
export default class Notice extends Component { static propTypes = { duration: PropTypes.number, onClose: PropTypes.func, children: PropTypes.any, }; static defaultProps = { onEnd() { }, onClose() { }, duration: 1.5, style: { right: '50%', }, }; componentDidMount() { this.startCloseTimer(); } componentWillUnmount() { this.clearCloseTimer(); } close = () => { this.clearCloseTimer(); this.props.onClose(); } startCloseTimer = () => { if (this.props.duration) { this.closeTimer = setTimeout(() => { this.close(); }, this.props.duration * 1000); } } clearCloseTimer = () => { if (this.closeTimer) { clearTimeout(this.closeTimer); this.closeTimer = null; } } render() { const props = this.props; const componentClass = `${props.prefixCls}-notice`; const className = { [`${componentClass}`]: 1, [`${componentClass}-closable`]: props.closable, [props.className]: !!props.className, }; return ( <div className={classNames(className)} style={props.style} onMouseEnter={this.clearCloseTimer} onMouseLeave={this.startCloseTimer} > <div className={`${componentClass}-content`}>{props.children}</div> {props.closable ? <a tabIndex="0" onClick={this.close} className={`${componentClass}-close`}> <span className={`${componentClass}-close-x`}></span> </a> : null } </div> ); } }
这个组件比较简单,主要是实现标签显示一段时间后自动消失,经过setTimeout设置一段时间后调用close方法,也就是上一段代码中实现的移除state中的相应节点以及调用相应的回调函数。
看到这里antd的通知组件的实现已经比较清晰了,代码并无特别复杂的部分,可是这种使用单例模式动态添加组件的设计十分值得借鉴,在实现相似通知组件或者须要动态添加的组件的时候能够参考这种设计模式,antd的Message组件也采用了一样的设计。