Antd Trigger 類組件結構簡析

Antd 組件庫中,包含有 Trigger 的組件,它們都有相似的組件結構。如下組件均可以歸爲這一類:node

  • ToolTipreact

  • Popoverapp

  • PopConfrimdom

  • Cascader函數

  • DataPicker動畫

  • TimePickerthis

  • Selectspa

  • TreeSelectcode

  • Dropdowncomponent

這些組件的特色,就是都有彈框。經過點擊, hover, 或是改變焦點的方式,打開或關閉彈框。而這個特色,就是 Trigger 組件所要實現的功能:彈框可見性的控制。

看圖

圖片描述

圖片描述

先看很黃的這一組,Popover/PopConfirm -> Tooltip -> rcTooltip -> rcTrigger -> child

這類組件,能夠爲彈框傳入自定義的內容或組件。Popover 和 PopConfirm 都是從 Tooltip 整容來的,爲彈窗添加了經常使用的功能。

其中,內部組件 Trigger,負責爲組件添加監聽事件,來控制彈框的可見性。

// rc-trigger\lib\Trigger.js
var ALL_HANDLERS = ['onClick', 'onMouseDown', 'onTouchStart', 'onMouseEnter', 'onMouseLeave', 'onFocus', 'onBlur'];

能夠看出,Trigger 支持 onClick, onMouseDown, onTouchStart, onMouseEnter, onFocus 來觸發彈窗,經過 onClick, onMouseLeave, onBlur 來取消彈窗。彈窗的打開與關閉方式能夠不是對應的,例如能夠 mouseEnter 打開,click 關閉。咱們寫彈窗組件的時候,也能把這一部分邏輯抽象出來,感受應該很棒。

當咱們用 React 寫彈窗組件的時候,確定想過要如何添加點擊取消功能。使用遮罩,會有一次無效操做,對選框類彈框就不太好了。Antd 的作法是添加全局監聽事件,經過判斷點擊的元素,是否包含在組件或彈框中,便可判斷是否爲取消彈框的點擊。下面是 Antd 中判斷組件包含性的方法

function contains(root, n) {
    var node = n;
    while (node) {
        if (node === root) {
            return true;
        }
        node = node.parentNode;
    }
    return false;
}

除了添加監聽事件,Trigger 還有一個很重要的功能,就是彈框的渲染。Trigger 中的彈框並無渲染進正常的文檔流中,它 mixin 了一個 getContainerRenderMixin 內部組件,該組件經過 React 的非穩定方法 unstable_renderSubtreeIntoContainer(),將組件掛載到 document.body 下。由於若是把彈窗渲染在正常文檔流中,組件的定位會受到父元素定位的影響,咱們知道,若是父元素爲靜態定位,定位標的就會變成再父一級元素。將彈窗掛載在 body 下,就能解決這個問題。

雖然彈框脫離了正常文檔流,但其仍受父組件控制。而組件包裹的子組件仍然渲染在正常文檔流。

// rc-util\lib\getContainerRenderMixin2
_reactDom2["default"].unstable_renderSubtreeIntoContainer(instance, component, instance._container, function callback() {
   instance._component = this;
   if (ready) {
       ready.call(this);
   }
});

圖片描述

很綠的一組,也是寄生於 Trigger,但不屬於 Tooltip 一族,它們的彈窗內容被傳入了定義好的組件,定製性更強。從 Trigger 這一層開始,結構都差很少。

藍色表明憂傷,看到這麼多層嵌套,看着是挺憂傷的。Popup -> Animate -> AnimateChild -> rcAlign -> PopupInner -> LazyRenderBox -> content

圖片描述

Popup

Popup 組件負責按參數,配置彈框的各類特性,像要不要遮罩、zIndex設多少、隱藏要不要卸載掉彈框等,把這些屬性渲染到功能組件中

Animate

Animate 和 AnimateChild,顧名思義,就是搞動畫的。Animate 組件相似於 React 的 ReactTransitionGroup 組件,它會調用子組件中的鉤子函數,來控制動畫效果。

// rc-animate\lib\AnimateChild.js
componentWillUnmount: function componentWillUnmount() {
    this.stop();
},
componentWillEnter: function componentWillEnter(done) {
    if (_util2["default"].isEnterSupported(this.props)) {
        this.transition('enter', done);
    } else {
        done();
    }
},
componentWillAppear: function componentWillAppear(done) {
    if (_util2["default"].isAppearSupported(this.props)) {
        this.transition('appear', done);
    } else {
        done();
    }
},
componentWillLeave: function componentWillLeave(done) {
    if (_util2["default"].isLeaveSupported(this.props)) {
      this.transition('leave', done);
    } else {
      done();
    }
},

AnimateChild

AnimateChild 在生命週期的鉤子函數中,經過改變類名,如改變後綴 -appear, -enter, -leave,來執行動畫效果。Antd 動畫主要是 CSS3 動畫。

.zoom-motion(@className, @keyframeName, @duration: @animation-duration-base) {
  .make-motion(@className, @keyframeName, @duration);
  .@{className}-enter,
  .@{className}-appear {
    transform: scale(0); // need this by yiminghe
    animation-timing-function: @ease-out-circ;
  }
  .@{className}-leave {
    animation-timing-function: @ease-in-out-circ;
  }
}

Align

Align 組件負責對齊元素。前面說了,Popup 脫離了正常文檔流,掛在了 document.body 上。那如何把兩個風馬牛不相及的元素對齊呢?還要考慮到窗口改變時也能對得上。靠的是元素的可視矩陣(區域)計算,說的挺輕巧,反正代碼我還沒看懂。

// rc-align\node_modules\dom-align\lib\index.js
function domAlign(el, refNode, align) {
    // masive code
}

PopupInner

渲染了個div,添了個 LazyRenderBox 功能組件

LazyRenderBox

這個組件很懶,由於你看不見它的時候,它就不幹活了。

shouldComponentUpdate: function shouldComponentUpdate(nextProps) {
    return nextProps.hiddenClassName || nextProps.visible;
},

content

以前的一沓都是套在外面的衣服,你的 content 纔是用戶想看的。

圖片描述

儘管 React 組件樹很複雜,可是最終渲染出來的 Dom 樹倒是格外的清爽。

最後

Antd 不簡單,看來寫一套靠譜的組件可不是鬧着玩的,一些看似簡單的功能,實現起來倒是十分的複雜。一沓代碼看下來,可能就領悟了皮毛,卻感受已經受益不淺了。

相關文章
相關標籤/搜索