antd 源码解读 notification

Notification

这是一个全局变量的组件,能够在任意地方调用其函数就可以生成一个,咱们就来看看这个组件又是用了什么奇巧淫技来实现的。前端

-- 注意:解读的antd的源码版本为 2.13.4 rc-notification 版本为 2.0.0 结合源码查看的时候不要下载错了。git

本节讲点

  1. 查看 notification 组件源码的文件顺序和入口点
  2. rc-utils 组件中的 createChainedFunction 函数
  3. 缓存机制
  4. ReactDOM.unmountComponentAtNode

快速阅读代码

我将带你们使用 略览 代码的方法来进行一个组件的快速通读,这就跟高中英语阅读时使用的一种阅读方法同样,快速阅读,略过细节,抓主线路,理清整个组件工做原理以后再去查看细节。github

1. antd-design-master/components/index.tsx

由于使用方法是直接使用的 notification.api(config),因此想到先去看看是怎么抛出的。 export {default as notification} from './notification'api

2. antd-design-master/components/notification/index.tsx

再看看引用的文件是怎么抛出的。 export default api as NotificationApi;数组

3. antd-design-master/components/notification/index.tsx

由下往上看代码,看到 api 的构成,再看到 api.notice->function notice->function getNotificationInstance->(Notification as any).newInstance->import Notification from 'rc-notification';缓存

getNotificationInstance(
      outerPrefixCls,
      args.placement || defaultPlacement
    ).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 实例都存在一个全局常量中,方便第二次使用只要这个实例没有被 destroy。微信

4. rc-notification/src/index.js

找到入口文件 import Notification from './Notification';antd

5. rc-notification/src/Notification.jsx

在上面第 3 条咱们看到有的一个方法 newInstance 是用来建立新实例,因此咱们在这个文件中也能够看到相应的代码 Notification.newInstance = function newNotificationInstance,在这个函数中咱们继续略览代码,看到 ReactDOM.render(<Notification {...props} ref={ref} />, div); 咱们知道这是将一个组件渲染在一个 dom 节点,因此下一个查看点就应该是 Notification 这个组件类。app

6. rc-notification/src/Notification.jsx

看到文件上面 class Notification extends Component,能够看到整个组件的实现,咱们能够在 render 函数中看到一个循环输出,那就是在循环输出 state 中存的 noticestate 中的 notice 是经过上面第 3 点展现的代码,获取实例以后使用 notice 函数调用的实例的 add 函数进行添加的。dom

const onClose = createChainedFunction(this.remove.bind(this, notice.key), notice.onClose);
  return (<Notice prefixCls={props.prefixCls} {...notice} onClose={onClose} > {notice.content} </Notice>);
复制代码

7. rc-notification/src/Notice.jsx

componentDidMount() {
    if (this.props.duration) {
      this.closeTimer = setTimeout(() => {
        this.close();
      }, this.props.duration * 1000);
    }
  }

  componentWillUnmount() {
    this.clearCloseTimer();
  }

  clearCloseTimer = () => {
    if (this.closeTimer) {
      clearTimeout(this.closeTimer);
      this.closeTimer = null;
    }
  }

  close = () => {
    this.clearCloseTimer();
    this.props.onClose();
  }
复制代码

这个文件中玄妙之处其实在于以上三个函数,在 componentDidMount 之时,添加了一个定时器,将在规定时间以后删除掉当前的这个提示窗,而且这个删除动做是交由给外层文件去删除当前这个提示框的实例进行的也就是第 6 点文件中的 remove 函数,在最新的(3.0.0)rc-notification 中添加了如下代码,为了可以在鼠标移上去以后不让消息框消失,增长了用户体验度。

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>
    );
  }
复制代码

CreateChainedFunction

这个函数是使用在上面第 6 点,目的是为了可以删除当前的 notification 的缓存值,而后再执行外部传入的关闭回调函数,这个函数的实如今 rc-util 包中,这个包中有不少的方法是值得学习的,可是他在 github 上面的 star 数量却只有 73 个,这里软推一下吧。

export default function createChainedFunction() {
  const args = [].slice.call(arguments, 0);
  if (args.length === 1) {
    return args[0];
  }

  return function chainedFunction() {
    for (let i = 0; i < args.length; i++) {
      if (args[i] && args[i].apply) {
        args[i].apply(this, arguments);
      }
    }
  };
}
复制代码

这个函数中使用了call 来将传入的参数变成一个数组,而后使用 apply 将传入的函数一一执行,这样子就可以实现一个函数接受多个函数,而后按照顺序执行,而且在第 6 点的代码中 this.remove.bind(this, notice.key) 使用了 bind 函数指定了 this 和传入参数,方法很精妙也很经典。

缓存机制

notification 组件在 ant-design-master 中使用了。

const notificationInstance = {};

destroy() {
Object.keys(notificationInstance).forEach(cacheKey => {
  notificationInstance[cacheKey].destroy();
  delete notificationInstance[cacheKey];
});
}
复制代码

来进行对建立实例的缓存,而后在销毁时将缓存的实例删除。

notification 2.0.0 中也使用了缓存机制。

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),
    };
  }
});
}

remove = (key) => {
this.setState(previousState => {
  return {
    notices: previousState.notices.filter(notice => notice.key !== key),
  };
});
}
复制代码

在这个代码中看到这个缓存机制是使用的数组的方式实现的,可是在外层封装倒是用的是是对象的方式实现,我猜测这两个代码不是一我的写的。。。代码风格不统一呢。

ReactDOM.unmountComponentAtNode

Notification.newInstance = function newNotificationInstance(properties) {
const {getContainer, ...props} = properties || {};
let div;
if (getContainer) {
  div = getContainer();
} else {
  div = document.createElement('div');
  document.body.appendChild(div);
}
const notification = ReactDOM.render(<Notification {...props} />, div); return { notice(noticeProps) { notification.add(noticeProps); }, removeNotice(key) { notification.remove(key); }, component: notification, destroy() { ReactDOM.unmountComponentAtNode(div); document.body.removeChild(div); }, }; }; 复制代码

从上面的代码中看出,notification 组件使用 unmountComponentAtNode 函数将其进行销毁,这个方法适用于某些不能在当前组件中进行组件销毁的状况,举个例子,模态框的删除也可使用这个方法执行。


关注微信公众号:创宇前端(KnownsecFED),码上获取更多优质干货!

相关文章
相关标签/搜索