Trigger-index.js

这个组件的index文件就有很多代码,590行代码,而且在头部引入的额外文件特别的多,所以我们这一个组件就先从这些额外的组件中开始吧,先看看这些外部方法能够做些什么。

强烈建议把tigger的代码下载下来自行查看,因为实在是太长了

// index.js 头部
  import PropTypes from 'prop-types';
  import { findDOMNode, createPortal } from 'react-dom';
  import createReactClass from 'create-react-class';
  import contains from 'rc-util/lib/Dom/contains';
  import addEventListener from 'rc-util/lib/Dom/addEventListener';
  import Popup from './Popup';
  import { getAlignFromPlacement, getPopupClassNameFromAlign } from './utils';
  import getContainerRenderMixin from 'rc-util/lib/getContainerRenderMixin';
  import Portal from 'rc-util/lib/Portal';

createPortal

在官网这里arrow-up-right有这么一个解释

ReactDOM.createPortal(child, container)

Creates a portal. Portals provide a way to render children into a DOM node that exists outside the hierarchy of the DOM component.

这个函数是用来创建一个portal,而这个Portal是提供一个方法来在指定的dom元素渲染一些组件的方法。

createReactClass

这个函数也是能够在官网这里arrow-up-right上找到的,是用来创建一个raect类而不是用es6语法的方法,在里面可以使用getDefaultProps()方法

创建当前组件的默认props,可以使用getInitialState()创建当前组件的初始state,并且在里面写的方法都会自动的绑定上this,

也就是他所说的Autobinding,还有一个最有用的属性Mixins,这个是能够在编写很多的能够使用的外部方法传入组件的属性。

contains && addEventListener

这两个函数都是rc-util/lib/Dom/里面的工具函数,接下来我们分辨看看这两个函数能够做啥

getContainerRenderMixin && Portal

接下来是这两个函数,都是来自于rc-util/lib/

在组件开始之前

在组件开始之前还有一些辅助的东西需要了解到

Props

这个组件的传入参数非常的多,为了做兼容或者适应更多的使用者。

参数很多,我直接将其参数作用拷贝过来了。

name
type
default
description

popupClassName

string

additional className added to popup

destroyPopupOnHide

boolean

false

whether destroy popup when hide

getPopupClassNameFromAlign

getPopupClassNameFromAlign(align: Object):String

additional className added to popup according to align

action

string[]

['hover']

which actions cause popup shown. enum of 'hover','click','focus','contextMenu'

mouseEnterDelay

number

0

delay time to show when mouse enter. unit: s.

mouseLeaveDelay

number

0.1

delay time to hide when mouse leave. unit: s.

popupStyle

Object

additional style of popup

prefixCls

String

rc-trigger-popup

prefix class name

popupTransitionName

String|Object

https://github.com/react-component/animate

maskTransitionName

String|Object

https://github.com/react-component/animate

onPopupVisibleChange

Function

call when popup visible is changed

mask

boolean

false

whether to support mask

maskClosable

boolean

true

whether to support click mask to hide

popupVisible

boolean

whether popup is visible

zIndex

number

popup's zIndex

defaultPopupVisible

boolean

whether popup is visible initially

popupAlign

Object: alignConfig of [dom-align](https://github.com/yiminghe/dom-align)

popup 's align config

onPopupAlign

function(popupDomNode, align)

callback when popup node is aligned

popup

React.Element | function() => React.Element

popup content

getPopupContainer

getPopupContainer(): HTMLElement

function returning html node which will act as popup container

getDocument

getDocument(): HTMLElement

function returning document node which will be attached click event to close trigger

popupPlacement

string

use preset popup align config from builtinPlacements, can be merged by popupAlign prop

builtinPlacements

object

builtin placement align map. used by placement prop

Render()

我们依然还是从他的render函数作为突破点

在上面的代码中我们看这些函数

那么我们将来了解这些函数都干了什么

生命周期

在createTwoChains函数中我们又看见了一个新的函数'this[fire${event}]',

这些函数都是在componentDidMount的时候构建成的,那么记下来顺理成章的我们应该转接到组件的生命周期

可是看到现在也还是没有设么头绪,别忙这里先讲清楚一件事情,就是trigger这个组件的实现

trigger组件由于其中的展示内容需要绝对定位,但是这些定位如果放在已经存在的dom结构中会很复杂很难实现统一,于是这里就将所有的需要定位的元素全部渲染在body的最后,这样子计算定位就很方便了,所以trigger组件的目的就是需要将呈现的东西给渲染在body之后,但是大家都知道,react的render只要一个入口,也就是最初的id为root的div,然后就是在这个div里面进行react开发,所以react为大家提供了一个函数,我们在上面的renderComponent()这个函数中也讲到unstable_renderSubtreeIntoContainer(),可以使用这个函数就能够将组件中的内容渲染在创造出的节点上并且追加在任何地方,一般是追加在body,也可以追加在指定的dom节点后面

接下来就是另一个分析的思路,因为我在看这些代码的时候开始也是混乱的,在经过查资料的过程中我也在思考,发现到一个点那就是这个组件在判断当前react版本是不是react16,并且根据上面所讲的trigger组件的实现原理,我恍然大悟,因为在react16之前没有createPortal这个API的,这个API其实就是trigger的原理实现,所以我就知道了,判断如果是react16版本的就使用react自己的API来创建挂载点,如果不是就利用mixin中的renderComponent()函数中的老的react的方法unstable_renderSubtreeIntoContainer()来创建挂载点以及挂载组件,那么接下来我们就来分析一下他的思路。

IS__REACT__16?

上面既然说到了要从当前版本来进行操作,那么我就按照是与不是分别看看这个组件都做了哪些处理

IS

首先就是从当前react版本是16开始,从render函数开始,在render函数中我们就谈到有一个判断

也就是在使用cloneElement生成完trigger组件之后,如果不是react16版本就直接返回了trigger,然后如果是react16版本就使用Protal组件将需要挂载的dom元素渲染出来,使用getContainer进行dom节点的创建,使用getComponent将弹出层渲染,最终挂载在getContainer创建的dom节点,然后append在body,这就是使用了react16版本的一个创建过程,其中Protal组件中就是用了react16中的createPortal,剩下的就是Popup,又是antd的另一个基层组件,需要去了解。

NOT IS

如果大家和我一样看了源码之后也许会纳闷,如果不是react16版本的时候,就直接返回了trigger组件,那么他是在什么时刻去渲染弹出层以及弹出层的挂载节点的呢?接下来就是揭秘时间:

在componentDidUpdate中在不是react16的时候使用了一个renderComponent函数,那么这个函数又是哪里来的呢,我们继续往上追溯,我们发现在上面讲到的getContainerRenderMixin中有这样的一断代码

那么知道mixin作用的同学就应该知道了,上面的componentDidUpdate中使用的renderComponent函数是在哪里定义的了,接下来就直接分析这个mixin中干了什么

首先是我们在使用的时候传入了这些参数;

这里不得不再讲一遍这个getContainerRenderMixin

这样trigger组件的一个大致构造思路以及大部分代码就已经进行了解读,剩余的部分都是进行的状态控制,antd为了适应手机还所以在状态控制上面写了很多函数,不多都是简单的函数,而且有的函数仅仅只是为了出一些出现的issue,感觉有点hotfix的意味,反正希望看完这一节对于大家制作react弹出层有一定的了解,这里我就提出两点

  1. react16版本之前的需要自己写一个弹出层挂载点

  2. react16版本之后的可以使用react提供的createPortal进行挂载点的处理

当然这个弹出层不仅仅是小的弹出层,可以制作很多东西,模态框,提醒框,下拉菜单,tooltip等等只要是需要绝对定位在某一个元素的某一个位置的场景,尽量发挥想象吧。

Last updated