Ant Design源碼分析(三):Wave組件

Wave組件效果預覽

       在上一篇文章Button組件的源碼分析中遇到了一個Wave組件, Wave組件在Ant design中提供了通用的表單控件點擊效果,在本身閱讀源碼以前,也並無過更多留心過在這些表單控件的動畫效果是如何實現的,甚至可能有時都沒注意到這些動畫效果。下面先一塊兒來看如下具體的效果(留意邊框之外,一閃一閃的波浪動畫效果):css

Button組件
Wave提供的動畫效果node

Radio組件
圖片描述segmentfault

Switch組件
圖片描述緩存


       看完UI效果以後咱們大概已經知道是什麼了,再看代碼部分,因爲代碼書寫順序與閱讀順序並不一致,爲了方便理解,咱們在分析源碼的過程當中,會調整代碼解釋的順序app

源碼分析

// 一個新的依賴,暫時不知道是什麼,依據名字推測與動畫效果有關
import TransitionEvents from 'css-animation/lib/Event';

export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
  
  //... some type code
  
  // 咱們發現Wave組件只提供組件邏輯,不參與UI展現,這種容器組件,每每在DidMount或WillMount聲明週期中開始
  // 構建組件邏輯,往下看
  render() {
    return this.props.children;
  }
  
  // 只有一行代碼, 先看下方bindAnimationEvent方法
  componentDidMount() {
    this.instance = this.bindAnimationEvent(findDOMNode(this) as HTMLElement);
  }
    
  // 在組件卸載時,清除掉事件監聽與定時器,避免內存泄漏
  componentWillUnmount() {
    if (this.instance) {
      this.instance.cancel();
    }
    if (this.clickWaveTimeoutId) {
      clearTimeout(this.clickWaveTimeoutId);
    }
  }  

  // 根據名字推測: 爲DOM節點綁定動畫效果,進入函數內部查看
  bindAnimationEvent = (node: HTMLElement) => {
   //... some code 

    const onClick = (e: MouseEvent) => {
      //... some code

      // 不管是否正在執行動畫,先清除掉動畫效果(至於怎麼清除,先不關注)
      this.resetEffect(node);
      
      // 從target取得顏色值
      const waveColor =
        getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
        getComputedStyle(node).getPropertyValue('border-color') ||
        getComputedStyle(node).getPropertyValue('background-color');

      // 在這裏能夠看到以前定義的私有變量clickWaveTimeoutId,被用做儲存一個定時器
      this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0);
    };
  
    // 監聽node(this.props.children)的onClick事件
    node.addEventListener('click', onClick, true);

    // 將移除監聽事件的回調函數封裝在一個對象中並做爲返回值,看着這裏應該想起以前定義的私有變量instance,
    // 回顧DidMount生命週期函數,你會發現這個返回的對象將會被儲存在instance中
    return {
      cancel: () => {
        node.removeEventListener('click', onClick, true);
      },
    };
  }

  //未完待續

咱們經過觀察上方bindAnimationEvent方法,其主要作了三件事,調用了兩個外部函數this.resetEffectthis.onClick
一、過濾不執行的條件(代碼省略掉了,非主幹邏輯)
二、聲明onClick函數,並做爲node的點擊事件觸發時要執行的函數
三、返回一個儲存了取消監聽click事件方法的對象函數

我的認爲bindAnimationEvent方法,作了太多的事情,而ComponentDidMount作的事情太少(單一原則)
往下方,繼續查看 this.resetEffectthis.onClick 分別是作什麼的,以及如何實現的源碼分析

// 這個函數是實現動畫效果的核心,其主要有三個行爲:一、建立內聯style標籤, 二、插入css字符串 三、並將其插入到document中
  // 咱們知道css也是能夠控制DOM變化的,好比僞類元素:after :before  這裏正是經過:after來實現效果的
  onClick = (node: HTMLElement, waveColor: string) => {
    //... some code 1
    const { insertExtraNode } = this.props;

    /* 建立了一個div元素extraNode,裝飾該div,在inserExtracNode= true時,將extraNode做爲node的子元素 */
    /* 建立一個div元素,並緩存中私有變量extraNode中 */
    this.extraNode = document.createElement('div');
    
    /* 這裏用let 更合適 */
    const extraNode = this.extraNode;
    extraNode.className = 'ant-click-animating-node';
    // 可能有人好奇這個extraNode是幹嗎的?
    // 往以後的代碼中看的時候會發現,在insertExtraNode爲false時,Wave經過插入僞類元素:after 來做爲承載動畫效果的DOM元素
    // 在insertExtraNode = true時,會生成一個div替代:after僞類元素,猜想是某些this.props.children可能自帶:after,因此
    // 使用div元素來替代:after避免衝突,在這裏咱們只須要知道它是做爲承載動畫css的DOM元素便可
   
    // 獲取指定的string insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node';
    const attributeName = this.getAttributeName();  
    
    // Element.removeAttribute('someString');  從element中刪除值爲someString的屬性
    // Element.setAttribute(name, value); 爲element元素值爲value的name屬性
    node.removeAttribute(attributeName);
    node.setAttribute(attributeName, 'true');
    
    // 行爲1:這裏建立了一個內聯style標籤
    styleForPesudo = styleForPesudo || document.createElement('style');
    if (waveColor &&
        waveColor !== '#ffffff' &&
        waveColor !== 'rgb(255, 255, 255)' &&
        this.isNotGrey(waveColor) &&
        /* 透明度不爲0的任意顏色 */
        !/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) &&  // any transparent rgba color
        waveColor !== 'transparent') {
          /* 給子元素加上borderColor */
        extraNode.style.borderColor = waveColor;
        
        /* 行爲2:在內聯style標籤中插入樣式字符串, 利用僞元素:after做爲承載效果的DOM */
        styleForPesudo.innerHTML =
            `[ant-click-animating-without-extra-node]:after { border-color: ${waveColor}; }`;
            
        /* 行爲3:將style標籤插入到document中 */
      if (!document.body.contains(styleForPesudo)) {
        document.body.appendChild(styleForPesudo);
      }
    }    

     /* 在inserExtarNode爲true時,將extraNode插入到node子元素中 */
    if (insertExtraNode) {
      node.appendChild(extraNode);
    }
    
    /* 爲元素增長動畫效果 */
    TransitionEvents.addEndEventListener(node, this.onTransitionEnd);
  }

  /**
   * 重置效果
   * 顧名思義:這個函數經過三個行爲,致力於一件事情,取消動畫效果
   * 一、刪除node的attribute屬性 二、node的子元素 三、刪除對應的內聯style標籤
   */
  resetEffect(node: HTMLElement) {
    // come code ...
    
    const { insertExtraNode } = this.props;
    
    const attributeName = this.getAttributeName();
    /* 行爲1:刪除node的attribute屬性 */
    node.removeAttribute(attributeName);

    /* 行爲3: 清空了爲僞類元素內置的style標籤 styleForPesudo */
    this.removeExtraStyleNode();

    if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) {
      // Node.removeChild() 方法從DOM中刪除一個子節點。返回刪除的節點。
      node.removeChild(this.extraNode);
    }
    TransitionEvents.removeEndEventListener(node, this.onTransitionEnd);
  }

  // 刪除內聯style標籤
  removeExtraStyleNode() {
    if (styleForPesudo) {
      styleForPesudo.innerHTML = '';
    }
  }
      
  // 在每次動畫執行結束後,清除掉狀態,完成一個生命週期
  onTransitionEnd = (e: AnimationEvent) => {
    // todo
    if (!e || e.animationName !== 'fadeEffect') {
      return;
    }

    this.resetEffect(e.target as HTMLElement);
  }

}

組件邏輯:

咱們回過頭來看第一部分的代碼,組件邏輯體如今componentDidMountcomponentWillUnMount
一、在componentDidMount中爲this.props.childrenclick事件綁定動畫執行函數this.onClick
二、在componentWillUnMount中清除與動畫相關的狀態避免內存泄漏。動畫

組件運行邏輯:

此時,Wave組件的代碼與邏輯已經所有分析完了,整個Wave組件的運行邏輯能夠經過下方這張圖來歸納
圖片描述this

本篇完~spa

相關文章
相關標籤/搜索