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),碼上獲取更多優質乾貨!

相關文章
相關標籤/搜索