antd源碼解讀(10)- notification

Notification

這是一個全局變量的組件,能夠在任意地方調用其函數就可以生成一個,咱們就來看看這個組件又是用了什麼奇巧淫技來實現的git

-- 注意:解讀的源碼版本爲2.13.4 rc-notification版本爲2.0.0 不要下載錯了github

本節講點

  1. 查看notification組件源碼的文件順序和入口點
  2. rc-utils組件中的createChainedFunction函數
  3. 緩存機制
  4. ReactDOM.unmountComponentAtNode

快速閱讀代碼

我將帶你們使用略覽代碼的方法來進行一個組件的快速通讀,這就跟高中英語閱讀時使用的一種閱讀方法同樣,快速閱讀,略過細節,抓主線路,理清整個組件工做原理以後再去查看細節api

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

    由於使用方法是直接使用的notification.api(config),因此想到先去看看是怎麼拋出的 export { default as notification } from './notification'緩存

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

    再看看引用的文件是怎麼拋出的 export default api as NotificationApi;app

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

    由下往上看代碼,看到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';

  5. rc-notification/src/Notification.jsx

    在上面第3條咱們看到有的一個方法newInstance是用來建立新實例,因此咱們在這個文件中也能夠看到相應的代碼Notification.newInstance = function newNotificationInstance,在這個函數中咱們繼續略覽代碼,看到ReactDOM.render(<Notification {...props} ref={ref} />, div);咱們知道這是將一個組件渲染在一個dom節點,因此下一個查看點就應該是Notification這個組件類

  6. rc-notification/src/Notification.jsx

    看到文件上面class Notification extends Component,能夠看到整個組件的實現,咱們能夠在render函數中看到一個循環輸出,那就是在循環輸出state中存的noticestate中的notice是經過上面第3點展現的代碼,獲取實例以後使用notice函數調用的實例的add函數進行添加的

    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函數將其進行銷燬,這個方法適用於某些不能在當前組件中進行組件銷燬的狀況。

相關文章
相關標籤/搜索