先来看一下notification的API。css
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,调用起来十分简单。设计模式
console.log()
console.error()
console.info()
console.warn()
config的配置也比较简单,主要是标题,内容,关闭时的延时和回调等。详见ANTD的官网。api
在分析代码以前,咱们先来看下notification的结构,通知组件主要分为三层,由外到内分别是数组
NotificationApi => Notification => n*Notice。antd
NotificationApi是一个封装的接口,提供统一调用的API,如info(),warn()等。app
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];
});
},
};
//.......省略部分代码........
//不一样类型经过open传入参数实现
['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;
//获得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,
});
});
}
复制代码
这段代码主要的部分就是调用了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);
};
复制代码
主要完成了两件事
再回过头来看回调函数的内容。
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;z
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组件也采用了一样的设计。