Ant-design dropdown 原始碼學習

shirleyR發表於2018-07-29
UI元件學習過程中的一些思考,同步於: zhuanlan.zhihu.com/p/40811867


原生dom可以很容易的實現簡單的dropdown,卻很難滿足我們的各種需求,因此各式各樣的dropdown第三方實現就出現了。ant-design是基於react實現的一組UI元件,我們選擇對其中的dropdown進行分析。

Dropdown 的主要組成:

  • 一個彈出的下拉選單
  • 一個當前的選中項
實現時需要注意的幾個問題:什麼時候彈出下拉選項, 下拉選項掛放在哪個位置。直觀上下拉選項應該是一個絕對定位的Popup div。區別使用者事件是發生在下拉選項內部還是外部,畢竟外部可能是期望收起這個下拉選單。
帶著這幾個問題我們開始看react-component/dropdown
對外的介面:
onOverlayClick: func
onVisibleChange: func
animation: any
align: object
placement: string
overlay: node
trigger: array
alignPoint: bool
showAction:
hideAction
getPopupContainer: func複製程式碼
通過這幾個介面可以發現我們的那些問題都有對應的答案
Ant-design dropdown 原始碼學習
dropdown react節點圖
dropdown 的原始碼主要是呼叫了Trigger這個抽象元件實現大部分的邏輯。
Trigger元件是一個抽象化元件,用來指定popup型別的UI。涉及到的包括popup, alignment。

Trigger建立、關閉popup

建立Popup在react 16之後提供了Portal可以實現將內容指定到非當前節點所在層級的div上,方便了popup,而為相容之前的版本使用的ReactDOM.unstable_renderSubtreeIntoContainer 等方法,實現較為複雜,具體可看util中的ContainerRender
在Trigger component的componentDidUpdate生命週期裡面監聽事件。
if (state.popupVisible) {
      let currentDocument;
      if (!this.clickOutsideHandler && (this.isClickToHide() || this.isContextMenuToShow())) {
        currentDocument = props.getDocument();
        this.clickOutsideHandler = addEventListener(currentDocument,
          'mousedown', this.onDocumentClick);
      }
      // always hide on mobile
      if (!this.touchOutsideHandler) {
        currentDocument = currentDocument || props.getDocument();
        this.touchOutsideHandler = addEventListener(currentDocument,
          'touchstart', this.onDocumentClick);
      }
      // close popup when trigger type contains 'onContextMenu' and document is scrolling.
      if (!this.contextMenuOutsideHandler1 && this.isContextMenuToShow()) {
        currentDocument = currentDocument || props.getDocument();
        this.contextMenuOutsideHandler1 = addEventListener(currentDocument,
          'scroll', this.onContextMenuClose);
      }
      // close popup when trigger type contains 'onContextMenu' and window is blur.
      if (!this.contextMenuOutsideHandler2 && this.isContextMenuToShow()) {
        this.contextMenuOutsideHandler2 = addEventListener(window,
          'blur', this.onContextMenuClose);
      }
      return;
    }複製程式碼
判斷滑鼠是否離開popup
onPopupMouseLeave = (e) => {
    // https://github.com/react-component/trigger/pull/13
    // react bug?
    if (e.relatedTarget && !e.relatedTarget.setTimeout &&
      this._component &&
      this._component.getPopupDomNode &&
      contains(this._component.getPopupDomNode(), e.relatedTarget)) {
      return;
    }
    this.delaySetPopupVisible(false, this.props.mouseLeaveDelay);
  }複製程式碼
操作在popup上還是其他區域
onDocumentClick = (event) => {
    if (this.props.mask && !this.props.maskClosable) {
      return;
    }
    const target = event.target;
    const root = findDOMNode(this);
    const popupNode = this.getPopupDomNode();
    if (!contains(root, target) && !contains(popupNode, target)) {
      this.close();
    }
  }複製程式碼

Trigger Alignment

Trigger 做的另一件事情就是對popup的位置進行定位。介紹下dom-align庫,用於指定dom節點對齊位置。API主要介面定義source、target、offset、overflow。


相關文章