React高阶组件之rc-notification源码解析
2018-08-30 本文已影响4人
打铁大师
github地址
demo地址
React Notification UI 组件
安装
npm install rc-notification
用法
var Notification = require('rc-notification');
Notification.newInstance({}, notification => {
notification.notice({
content: 'content'
});
});
API
Notification.newInstance(props, (notification) => void) => void
props 详情:
name | type | default | description |
---|---|---|---|
prefixCls | String | notification 容器css类名的前缀 | |
style | Object | {'top': 65, left: '50%'} | notification容器额外的样式 |
getContainer | Function:HTMLElement | 返回html节点,作为notification 挂载容器 |
notification.notice(props)
props 详情
name | type | default | description |
---|---|---|---|
content | React.Element | notice的内容 | |
key | String | notice的id | |
closable | Boolean | 实现显示关闭按钮 | |
onClose | Function | 当notice关闭时会被调用 | |
duration | number | 1.5 | notice显示的时间(秒) |
style | Object | { right: '50%' } | 单个notice的额外样式 |
maxCount | number | notices显示的最大数量,当超出限制,会移除第一个notice | |
closeIcon | ReactNode | 关闭icon |
notification.removeNotice(key:string)
使用key移除单个notice
notification.component
notification组件自身的引用
notification.destroy()
销毁当前的notification组件
源码包括Notice组件 和 Notification组件
Notice组件对应单个notice,Notification对应多个notice
Notice源码:
import React, {Component} from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
export default class Notice extends Component {
//规定了每个属性的类型,any表示任意类型
static propTypes = {
duration: PropTypes.number, //组件挂载后多久关闭
onClose: PropTypes.func, //当notice关闭后被调用的函数
children: PropTypes.any, //子元素
update: PropTypes.bool, //update属性为true时,组件更新会重启定时器
closeIcon: PropTypes.node //指定的close icon
};
//定义了默认的属性
static defaultProps = {
onClose() {
}, //Notice没有定义关闭的具体细节,由使用者发挥
duration: 1.5,
style: {
right: '50%'
}
};
componentDidMount() {
//启动关闭组件的定时器
this.startCloseTimer();
}
componentDidUpdate(prevProps) {
if (this.props.duration !== prevProps.duration
|| this.props.update) {
this.restartCloseTimer();
}
}
componentWillUnmount() {
//组件卸载了,清除定时器
this.clearCloseTimer();
}
close = () => {
this.clearCloseTimer();
this.props.onClose();
};
startCloseTimer = () => {
//duration 默认为1.5秒。if的含义是,duration不能设置为0和null,undefined
if (this.props.duration) {
this.closeTimer = setTimeout(() => {
this.close();
}, this.props.duration * 1000);
}
};
clearCloseTimer = () => {
if (this.closeTimer) {
//清除定时器,并将this.closeTimer设置为null
clearTimeout(this.closeTimer);
this.closeTimer = null;
}
};
restartCloseTimer() {
//重启关闭组件的定时器。重启前,先清除定时器。
this.clearCloseTimer();
this.startCloseTimer();
}
render() {
const props = this.props;
//componentClass表示整个组件的样式前缀
const componentClass = `${props.prefixCls}-notice`;
//使用classNames得出组件实际的css类,因为classNames会过滤掉值为false的css类。
const className = {
[`${componentClass}`]: 1,
[`${componentClass}-closable`]: props.closable,
[props.className]: !!props.className,
};
return (
//鼠标移到组件时,清除定时器。移出组件,开启定时器。
//组件没有定义样式。使用者可以传入样式类的前缀prefixCls。
<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`}>
{props.closeIcon || <span className={`${componentClass}-close-x`}/>}
</a> : null
}
</div>
);
}
}
Notification组件源码:
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import Animate from 'rc-animate';
import createChainedFunction from 'rc-util/lib/createChainedFunction';
import classnames from 'classnames';
import Notice from './Notice';
let seed = 0;
const now = Date.now();
function getUuid() {
return `rcNotification_${now}_${seed++}`;
}
class Notification extends Component {
//定义props对象的属性值类型
static propTypes = {
prefixCls: PropTypes.string,
transitionName: PropTypes.string,
animation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
style: PropTypes.object,
maxCount: PropTypes.number,
closeIcon: PropTypes.node,
};
//设置props对象的属性值的默认值
static defaultProps = {
prefixCls: 'rc-notification',
animation: 'fade',
style: {
top: 65,
left: '50%'
}
};
state = {
notices: [],
};
getTransitionName() {
const props = this.props;
let transitionName = props.transitionName;
if (!transitionName && props.animation) {
transitionName = `${props.prefixCls}-${props.animation}`;
}
return transitionName;
}
//add方法保证了notice不会重复加入到notices队列中。
add = (notice) => {
//key表示一个notice的id
const key = notice.key = notice.key || getUuid();
const {maxCount} = this.props;
this.setState(previousState => {
const notices = previousState.notices;
//要添加的notice是否已经存在
const noticeIndex = notices.map(v => v.key).indexOf(key);
//使用concat()来复制notice数组
const updatedNotices = notices.concat();
//如果要加的notice已经存在
if (noticeIndex !== -1) {
//删除已存在的notice,加入要添加的notice
updatedNotices.splice(noticeIndex, 1, notice);
} else {
//如果设置了maxCount,且notices中的数量已达到maxCount的限制,那么移除第一个notice
if (maxCount && notices.length >= maxCount) {
//updateKey设置为最初移除的key,最要是为了利用已存在的组件
notice.updateKey = updatedNotices[0].updateKey || updatedNotices[0].key;
updatedNotices.shift();
}
//加入的要添加的notice
updatedNotices.push(notice);
}
return {
notices: updatedNotices,
};
});
};
remove = (key) => {
this.setState(previousState => {
return {
notices: previousState.notices.filter(notice => notice.key !== key),
};
});
};
render() {
const props = this.props;
const {notices} = this.state;
const noticeNodes = notices.map((notice, index) => {
//如果notice是数组最后一个,且存在updateKey。
// 说明,该notice添加进来之前,数组已经达到maxCount,并挤掉了数组的第一个noitce。
// update 为true,是由于重用了之前被挤掉的notice的组件,需要更新重启Notice组件的定时器
const update = Boolean(index === notices.length - 1 && notice.updateKey);
//key相同,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新。
const key = notice.updateKey ? notice.updateKey : notice.key;
//createChainedFunction目的是,让this.remove函数,notice.onClose函数能够接收相同的参数,并一同调用。
//即调用onClose时,会先调用this.remove,再调用notice.onClose
const onClose = createChainedFunction(this.remove.bind(this, notice.key), notice.onClose);
return (<Notice
prefixCls={props.prefixCls}
{...notice}
key={key}
update={update}
onClose={onClose}
closeIcon={props.closeIcon}
>
{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>
);
}
}
Notification.newInstance = function newNotificationInstance(properties, callback) {
const {getContainer, ...props} = properties || {};
const div = document.createElement('div');
if (getContainer) {// getContainer是函数,返回HTMLElement。返回结果将作为notification挂载的容器
const root = getContainer();
root.appendChild(div);
} else {
document.body.appendChild(div);
}
//由上可知,其实notification最后都挂载在div中
let called = false;
//参数notification是指Notification组件
function ref(notification) {
//设置变量called的原因是
//因为ref回调会在一些生命周期回调之前执行,这里为了保证ref回调在组件的所有生命周期中只执行完整的一次,所以才使用了一个布尔值。
if (called) {
return;
}
called = true;
//传进来的callback函数,接收一个对象,该对象可以增加notice,移除notice,提供组件的引用,销毁组件
callback({
notice(noticeProps) {
notification.add(noticeProps);
},
removeNotice(key) {
notification.remove(key);
},
component: notification, //compoent可以向外暴露Notification实例
destroy() {
ReactDOM.unmountComponentAtNode(div); //从div中移除已挂载的Notification组件
div.parentNode.removeChild(div);//移除挂载的容器
},
});
}
ReactDOM.render(<Notification {...props} ref={ref}/>, div);
};
export default Notification;
收获:
Notification源码总共才200多行,但使用了很多react高级特性。读完之后收获不少,补充了一些以前不知道的知识点。
具体收获如下:
-
key相同,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新。
key值不同,则react先销毁该组件(有状态组件的componentWillUnmount会执行),然后重新创建该组件(有状态组件的constructor和componentWillUnmount都会执行) -
boolean unmountComponentAtNode(DOMElement container)
从 DOM 中移除已经挂载的 React 组件,清除相应的事件处理器和 state。如果在 container 内没有组件挂载,这个函数将什么都不做。如果组件成功移除,则返回 true;如果没有组件被移除,则返回 false。