antd的Tooltip組件在react-componment/trigger的基礎上進行封裝,而組件Popover和Popconfirm是使用Tooltip組件的進行pop,在react-componment中,使用到組件tc-trigger
的還有menu、select、dropdown、time-picker、calendar等,本文主要對tc-trigger
源碼進行解讀。html
項目結構以下:react
從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
該組件是pop的warp,渲染在trigger節點以外,經過ReactDOM.unstable_renderSubtreeIntoContainer
或createPortal
指定渲染的目標節點,也是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節點的寬高,而後對齊。
爲隱藏狀態下的pop添加hidden的class,幷包裹懶加載組件LazyRenderBox。
只作一件事情,就是將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-animate與rc-align,其餘的主要在作事件綁定與class處理。