antd源碼解讀(二)Tooltip組件解析

引言

antd的Tooltip組件在react-componment/trigger的基礎上進行封裝,而組件Popover和Popconfirm是使用Tooltip組件的進行pop,在react-componment中,使用到組件tc-trigger的還有menu、select、dropdown、time-picker、calendar等,本文主要對tc-trigger源碼進行解讀。html

結構

項目結構以下:react

項目結構

  • index.js,負責外層封裝,負責事件綁定與dom渲染控制。
  • LazyRenderBox.js,pop內容懶加載warp。
  • mock.js 未使用。
  • Popup.js,pop的warp,負責控制pop的對齊、動畫、寬高。
  • PopupInner.js,pop內容warp。

index.js

從render方法入手,須要渲染控制pop顯示的節點和pop內容節點兩個節點,而pop內容節點通常渲染到body裏面,不屬於控制pop顯示的節點內,render方法代碼以下:git

const trigger = React.cloneElement(child, newChildProps);
  if (!IS_REACT_16) {
    return (
      <ContainerRender
        parent={this}
        visible={popupVisible}
        autoMount={false}
        forceRender={props.forceRender}
        getComponent={this.getComponent}
        getContainer={this.getContainer}
      >
        {({ renderComponent }) => {
          this.renderComponent = renderComponent;
          return trigger;
        }}
      </ContainerRender>
    );
  }

  let portal;
  // prevent unmounting after it's rendered
  if (popupVisible || this._component || props.forceRender) {
    portal = (
      <Portal
        key="portal"
        getContainer={this.getContainer}
        didUpdate={this.handlePortalUpdate}
      >
        {this.getComponent()}
      </Portal>
    );
  }

  return [
    trigger,
    portal,
  ];

能夠看到,index.js渲染了兩個節點,trigger和portal,trigger即爲經過事件控制portal顯示狀態的節點,若是react的版本不是16以上,返回ContainerRender組件,ContainerRender組件來自rc-util,該組件主要作的事情就是使用ReactDOM.unstable_renderSubtreeIntoContainer函數,將pop內容渲染到trigger節點以外,與react16提供的APIcreatePortal做用一致,若是是React16,返回了Portal組件,該組件正是利用了createPortal,將組件渲染到特定的dom節點內,可是無論是否是react16,都進行了pop渲染的判斷,即popupVisible || this._component || props.forceRender,若是portal不顯示且不強制第一次渲染forceRender,portal將不會被渲染到dom中,直到判斷爲真。github

trigger節點經過props決定事件綁定狀況,即經過props.trigger屬性綁定事件狀況,事件控制Popup組件的visible屬性,這裏就不詳細說了。antd

Popup.js

該組件是pop的warp,渲染在trigger節點以外,經過ReactDOM.unstable_renderSubtreeIntoContainercreatePortal指定渲染的目標節點,也是render方法入手:dom

render() {
  return (
    <div>
      {this.getMaskElement()}
      {this.getPopupElement()}
    </div>
  );
}

返回兩個內容,getMaskElement獲取遮罩,getPopupElement返回Pop節點,getMaskElement這裏就不說了,渲染的視覺效果,綁定了控制pop節點的事件。函數

getPopupElement返回pop節點,render返回代碼以下:動畫

<Animate
    component=""
    exclusive
    transitionAppear
    transitionName={this.getTransitionName()}
    showProp="xVisible"
  >
    <Align
      target={this.getTarget}
      key="popup"
      ref={this.saveAlignRef}
      monitorWindowResize
      xVisible={visible}
      childrenProps={{ visible: 'xVisible' }}
      disabled={!visible}
      align={align}
      onAlign={this.onAlign}
    >
      <PopupInner
        hiddenClassName={hiddenClassName}
        {...popupInnerProps}
      >
        {children}
      </PopupInner>
    </Align>
  </Animate>

Animate來自組件rc-animate,主要負責顯示狀態切換時候的動態效果,其中原理是監聽控制狀態變化的prop屬性,即代碼中的showProp="xVisible",當狀態變化的時候,延時改變dom的class,通常會有三個狀態,分別表示進入中enter-active,消失中leave-active,隱藏hidden三個狀態,進入中狀態會添加transitionName-enter transitionName-enter-active兩個class,消失中會添加transitionName-leave transitionName-leave-active兩個class,隱藏狀態不添加class,transitionName經過外部傳入。this

Align來自組件rc-align,主要控制節點的相對於trigger的顯示位置,根據傳入的target與align決定最後PopupInner顯示的位置,此處target是來自於index.js的trigger節點,align也是來自於index.js,主要由index.js的prop.popupPlacement、prop.popupAlign兩個屬性決定,即方向與偏移量。spa

最後是PopupInner組件,該組件是也就pop內容組件,內容經過LazyRenderBox包裹。。。

另外,Popup.js還有兩個state,targetWidth與targetHeight,即pop的寬高,該屬性若是設置有prop.stretch,則計算trigger真是dom節點的寬高,而後對齊。

PopupInner.js

爲隱藏狀態下的pop添加hidden的class,幷包裹懶加載組件LazyRenderBox。

LazyRenderBox.js

只作一件事情,就是將popupInner的chidren進行包裹,當子節點數大於1時,包一層div以方便隱藏狀態時候class控制,不用每一個節點都添加hidden的class,關鍵以下:

render() {
  const { hiddenClassName, visible, ...props } = this.props;
  if (hiddenClassName || React.Children.count(props.children) > 1) {
    if (!visible && hiddenClassName) {
      props.className += ` ${hiddenClassName}`;
    }
    return <div {...props}/>;
  }
  return React.Children.only(props.children);
}

最後

該組件主要的實現難點在於rc-animaterc-align,其餘的主要在作事件綁定與class處理。

相關文章
相關標籤/搜索