antd源码解读(二)Tooltip组件解析

引言

antd的Tooltip组件在react-componment/trigger的基础上进行封装,而组件Popover和Popconfirm是使用Tooltip组件的进行pop,在react-componment中,使用到组件tc-trigger的还有menu、select、dropdown、time-picker、calendar等,本文主要对tc-trigger源码进行解读。

结构

项目结构如下:

项目结构

  • 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方法代码如下:

  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中,直到判断为真。

trigger节点通过props决定事件绑定情况,即通过props.trigger属性绑定事件情况,事件控制Popup组件的visible属性,这里就不详细说了。

Popup.js

该组件是pop的warp,渲染在trigger节点之外,通过ReactDOM.unstable_renderSubtreeIntoContainercreatePortal指定渲染的目标节点,也是render方法入手:

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通过外部传入。

Align来自组件rc-align,主要控制节点的相对于trigger的显示位置,根据传入的target与align决定最后PopupInner显示的位置,此处target是来自于index.js的trigger节点,align也是来自于index.js,主要由index.js的prop.popupPlacement、prop.popupAlign两个属性决定,即方向与偏移量。

最后是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处理。


麦乐
菜鸟历程
3.8k 声望
956 粉丝
0 条评论
推荐阅读
深入理解Chrome V8垃圾回收机制
最近,项目进入维护期,基本没有什么需求,比较闲,这让我莫名的有了危机感,每天像是在混日子,感觉这像是在温水煮青蛙,已经毕业3年了,很怕自己到了5年经验的时候,能力却和3年经验的时候一样,没什么长进。于...

_杨溜溜38阅读 8.3k评论 3

安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城31阅读 7.2k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco20阅读 2.1k评论 2

在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 1.9k

封面图
【已结束】SegmentFault 思否写作挑战赛!
SegmentFault 思否写作挑战赛 是思否社区新上线的系列社区活动在 2 月 8 日 正式面向社区所有用户开启;挑战赛中包含多个可供作者选择的热门技术方向,根据挑战难度分为多个等级,快来参与挑战,向更好的自己前进!

SegmentFault思否20阅读 5.6k评论 10

封面图
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...

边城18阅读 7.7k评论 3

封面图
Vue2 导出excel
2020-07-15更新 excel导出安装 {代码...} src文件夹下新建一个libs文件夹,新建一个excel.js {代码...} vue页面中使用 {代码...} ===========================以下为早期的文章今天在开发的过程中需要做一个Vue的...

原谅我一生不羁放歌搞文艺14阅读 19.9k评论 9

3.8k 声望
956 粉丝
宣传栏