antd原始碼解讀(10)- notification

markzzw發表於2018-01-15

Notification

這是一個全域性變數的元件,可以在任意地方呼叫其函式就能夠生成一個,我們就來看看這個元件又是用了什麼奇巧淫技來實現的

-- 注意:解讀的原始碼版本為2.13.4 rc-notification版本為2.0.0 不要下載錯了

本節講點

  1. 檢視notification元件原始碼的檔案順序和入口點
  2. rc-utils元件中的createChainedFunction函式
  3. 快取機制
  4. ReactDOM.unmountComponentAtNode

快速閱讀程式碼

我將帶大家使用略覽程式碼的方法來進行一個元件的快速通讀,這就跟高中英語閱讀時使用的一種閱讀方法一樣,快速閱讀,略過細節,抓主線路,理清整個元件工作原理之後再去檢視細節

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

    因為使用方法是直接使用的notification.api(config),所以想到先去看看是怎麼丟擲的 export { default as notification } from './notification'

  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';

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

相關文章