博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
react-transition-group源码浅析(一):Transition.md
阅读量:7176 次
发布时间:2019-06-29

本文共 12329 字,大约阅读时间需要 41 分钟。

阅读本文你会获得:

  • 一个相应的使用案例请看,文档在blog中找到:组件挂载与卸载动画的可以借助appear以及onExit回调函数实现。案例中onExit回调函数主要用于通过路由跳转卸载组件。

  • 一个比较有用的技巧:本文中工具函数一节的safeSetState函数;以及TransitionGroup种dom-helpers工具库的使用以及封装。

react-transition-group,结合react-router的项目使用案例请参照

全文中提到的第一次挂载与挂载的概念是指:Transition单独使用的时候,不区分第一挂载与其他挂载,只有在父组件是TransitionGroup的时候才区分。这可以从constructor中如下代码看出来:

//  初始化appear:    //  当单独使用Transition没有被TransitionGroup包裹时,appear = props.appear    //  当被TransitionGroup包裹的时候,TransitionGroup处于正在挂载阶段,子组件Transition是第一次挂载,因此appear = props.appear    //  当被TransitionGroup包裹的时候,TransitionGroup已经挂载完成,说明子组件Transition之前挂载并卸载过,因此appear = props.enter    let parentGroup = context.transitionGroup    let appear =      parentGroup && !parentGroup.isMounting ? props.enter : props.appear复制代码

appear主要用于设置:this.appearStatus = ENTERING,详细分析可以参考后续对constructor的分析。

Props介绍

children

type: Function | elementrequired复制代码

某个状态下需要过渡效果的目标组件,可以是函数

{(status) => (
)}
复制代码

每个状态'entering', 'entered', 'exiting', 'exited', 'unmounted'的时候执行的回调函数,上面代码实现的是,每一个状态就给某个子组件增加一个过渡样式,可以非常灵活的给任意组件增加样式,实现过渡效果。

in

type: booleandefault: false复制代码

用于在enter与exit状态之间翻转,默认为false,表示不挂载组件或者处于exit状态。

mountOnEnter

type: booleandefault: false复制代码

在第一次in={true}即挂载的时候,设置mountOnEnter={true}表示延迟挂载,懒加载组件。

unmountOnExit

type: booleandefault: false复制代码

如果为true,在组件处于exited状态的时候,卸载组件。

appear

type: booleandefault: false复制代码

如果为true,在组件挂载的时候,展示过渡动画。默认为false,第一次挂载过渡动画不生效。

enter

type: booleandefault: true复制代码

如果为true,表示允许enter状态的过渡动画生效,默认为true

exit

type: booleandefault: true复制代码

如果为true,表示允许exit状态的过渡动画生效,默认为true

addEndListener

type: Function复制代码

过渡动画结束时执行的毁掉函数

timeout

type: number | { enter?: number, exit?: number }复制代码

addEndListener存在的时候,需要设置timeout,表示过渡动画时间

timeout={
{ enter: 300, //enter状态动画时间 exit: 500, //exit状态动画时间}}复制代码

onEnter,onEntering,onEntered

type: Function(node: HtmlElement, isAppearing: bool)default: function noop() {}复制代码

源码内部,status分别为entering前后,entered之后执行的回调函数,CSSTransition组件即是利用这三个回调函数给组件增加不同的样式,利用CSS动画实现过渡效果。

onExit,onExiting,onExited

type: Function(node: HtmlElement) -> voiddefault: function noop() {}复制代码

源码内部,status分别为exiting前后,exited之后执行的回调函数,CSSTransition组件即是利用这三个回调函数给组件增加不同的样式,利用CSS动画实现过渡效果。

源码工具函数

getTimeouts函数

// 通过设置props.timeout,获取各个组件不同状态下的timeout  getTimeouts() {    const { timeout } = this.props    let exit, enter, appear    exit = enter = appear = timeout    if (timeout != null && typeof timeout !== 'number') {      exit = timeout.exit      enter = timeout.enter      appear = timeout.appear    }    return { exit, enter, appear }  }复制代码

setNextCallback函数:将函数封装为只可执行一次的自毁回调函数

//setNextCallback为一个闭包    // 传入一个回调函数,返回一个只能执行一次回调函数的函数,可以手动取消回调函数的执行	//执行一次之后自毁  setNextCallback(callback) {    //标志位active用于保证只执行一次callback    let active = true    this.nextCallback = event => {      if (active) {        active = false        //  垃圾回收        this.nextCallback = null        callback(event)      }    }    //用于手动取消回调函数的执行    this.nextCallback.cancel = () => {      active = false    }    return this.nextCallback  }复制代码

safeSetState函数:确保setState回调函数只执行一次

safeSetState(nextState, callback) {    // This shouldn't be necessary, but there are weird race conditions with    // setState callbacks and unmounting in testing, so always make sure that    // we can cancel any pending setState callbacks after we unmount.    callback = this.setNextCallback(callback)    //  callback执行一次之后不再允许执行    this.setState(nextState, callback)  }复制代码

onTransitionEnd函数

入场或者退场过渡动画结束之后,根据addEndListener以及timeout执行自毁回调函数handler

// handler为入场或者退场过渡动画结束之后的处理函数  onTransitionEnd(node, timeout, handler) {    //给this.nextCallback重新设置回调函数    this.setNextCallback(handler)    //  无论是否设置了addEndListener还是timeout,this.nextCallback都只执行一次    //  执行时机并不确定,这里经常会存在一些与预期不符的现象    if (node) {      //如果设置了addEndListener,并且监听了事件,则事件触发变执行this.nextCallback      if (this.props.addEndListener) {        // 执行自定义的过渡动画结束后的回调函数        this.props.addEndListener(node, this.nextCallback)      }      //如果设置了timeout,则timeout之后执行this.nextCallback      if (timeout != null) {        setTimeout(this.nextCallback, timeout)      }    } else {      setTimeout(this.nextCallback, 0)    }  }复制代码

updateStatus

//在挂载阶段与更新阶段根据nextStatus的状态执行入场或者退场动画  updateStatus(mounting = false, nextStatus){...}复制代码

源码分析

挂载阶段

constructor

根据是否是第一次挂载,是否被TransitionGroup包裹,来设置组件的初始state。涉及到的props有: enter,appear,in

// 组件Transition挂载阶段  constructor(props, context) {    super(props, context)    //  初始化appear:    //  当单独使用Transition没有被TransitionGroup包裹时,appear = props.appear    //  当被TransitionGroup包裹的时候,TransitionGroup处于正在挂载阶段,子组件Transition是第一次挂载,因此appear = props.appear    //  当被TransitionGroup包裹的时候,TransitionGroup已经挂载完成,说明子组件Transition之前挂载并卸载过,因此appear = props.enter    let parentGroup = context.transitionGroup    let appear =      parentGroup && !parentGroup.isMounting ? props.enter : props.appear    let initialStatus    this.appearStatus = null          //  初始化this.appearStatus以及this.state.status    //  挂载的时候:    //  in = true && appear = true : this.state.status = EXITED , this.appearStatus = ENTERING    //  in = true && appear = false : this.state.status = ENTERED    //  in = false && ( unmountOnExit = true || mountOnEnter = true ) : this.state.status = UNMOUNTED    //  in = false && unmountOnExit = false && mountOnEnter = fasle : this.state.status = EXITED    if (props.in) {      if (appear) {        initialStatus = EXITED        this.appearStatus = ENTERING      } else {        initialStatus = ENTERED      }    } else {      if (props.unmountOnExit || props.mountOnEnter) {        initialStatus = UNMOUNTED      } else {        initialStatus = EXITED      }    }    this.state = { status: initialStatus }    this.nextCallback = null  }复制代码

getDerivedStateFromProps

挂载阶段该函数返回null,不需要对state修改

static getDerivedStateFromProps({ in: nextIn }, prevState) {    // 挂载阶段if条件为false,返回null,不需要对state修改    // 更新阶段,在执行退场动画的时候,可能会返回{ status: EXITED }    if (nextIn && prevState.status === UNMOUNTED) {      return { status: EXITED }    }    return null  }复制代码

render

render() {    const status = this.state.status    //挂载阶段:      // in = false && ( unmountOnExit = true || mountOnEnter = true ),Transition不会渲染任何组件    if (status === UNMOUNTED) {      return null    }    //挂载阶段:      //  in = true && appear = true : this.state.status = EXITED , this.appearStatus = ENTERING      //  in = true && appear = false : this.state.status = ENTERED      //  in = false && unmountOnExit = false && mountOnEnter = fasle : this.state.status = EXITED    const { children, ...childProps } = this.props    // filter props for Transtition    //  滤除与Transtition组件功能相关的props,其他的props依旧可以正常传入需要过渡效果的业务组件    delete childProps.in    delete childProps.mountOnEnter    delete childProps.unmountOnExit    delete childProps.appear    delete childProps.enter    delete childProps.exit    delete childProps.timeout    delete childProps.addEndListener    delete childProps.onEnter    delete childProps.onEntering    delete childProps.onEntered    delete childProps.onExit    delete childProps.onExiting    delete childProps.onExited    //  当children === 'function',children函数可以根据组件状态执行相应逻辑    // (status) => (    //     
// ) if (typeof children === 'function') { return children(status, childProps) } //React.Children.only判断是否只有一个子组件,如果是则返回这个子组件,如果不是则抛出一个错误 const child = React.Children.only(children) return React.cloneElement(child, childProps) }复制代码

componentDidMount

开始执行

componentDidMount() {    // 第一次挂载的时候,如果in = true && appear = true,则appearStatus=ENTERING,否则为null。    this.updateStatus(true, this.appearStatus)  }复制代码

其中updateStatus函数为:appearStatus = ENTERING的时候执行performEnter

updateStatus(mounting = false, nextStatus) {    if (nextStatus !== null) {      // 挂载阶段:如果nextStatus !== null,则只会出现 nextStatus = ENTERING        // in = true && appear = true:nextStatus = ENTERING              // nextStatus will always be ENTERING or EXITING.      this.cancelNextCallback()  // 挂载阶段无操作      const node = ReactDOM.findDOMNode(this) // 挂载阶段找到真实DOM      //  挂载阶段:如果in = true && appear = true,则执行performEnter      if (nextStatus === ENTERING) {        this.performEnter(node, mounting)      } else {        this.performExit(node)      }    } else if (this.props.unmountOnExit && this.state.status === EXITED) {      this.setState({ status: UNMOUNTED })    }  }复制代码

其中performEnter函数为:执行onEnter回调函数 --> 设置{ status: ENTERING } --> 执行onEntering回调函数 --> 监听onTransitionEnd过渡动画是否完成 --> 设置{ status: ENTERED } --> 执行onEntered回调函数

performEnter(node, mounting) {    const { enter } = this.props    //  挂载阶段:如果in = true && appear = true,则appearing = true    const appearing = this.context.transitionGroup      ? this.context.transitionGroup.isMounting      : mounting    //  获取timeouts    const timeouts = this.getTimeouts()    //  挂载阶段以下if代码不执行    // no enter animation skip right to ENTERED    // if we are mounting and running this it means appear _must_ be set    if (!mounting && !enter) {      this.safeSetState({ status: ENTERED }, () => {        this.props.onEntered(node)      })      return    }    //执行props.onEnter函数    //挂载阶段,如果in = true && appear = true,则appearing始终为true    // 如果在Transition组件上设置onEnter函数,可以通过获取该函数第二参数来获取第一次挂载的时候是否是enter    this.props.onEnter(node, appearing)    //  改变{ status: ENTERING },改变之后执行一次回调函数    this.safeSetState({ status: ENTERING }, () => {      // 将状态设置为ENTERING之后,开始执行过渡动画      this.props.onEntering(node, appearing)      // FIXME: appear timeout?      //  timeouts.enter为入场enter的持续时间      // 过渡动画结束,设置{ status: ENTERED },执行onEntered回调函数      this.onTransitionEnd(node, timeouts.enter, () => {        //将状态设置为ENTERED,然后再执行onEntered回调函数        this.safeSetState({ status: ENTERED }, () => {          this.props.onEntered(node, appearing)        })      })    })复制代码

}

更新阶段

getDerivedStateFromProps

static getDerivedStateFromProps({ in: nextIn }, prevState) {    // 更新阶段:      // 如果挂载阶段in=true,那么第一次更新if条件中prevState.status!== UNMOUNTED      // 如果挂载阶段in=false,并且(props.mountOnEnter=true||props.mountOnEnter=true)      // 那么第一次更新if条件中prevState.status === UNMOUNTED,可以通过in的翻转改变      // 如果(props.mountOnEnter=true||props.mountOnEnter=true)的时候,设置状态status的状态为EXITED    if (nextIn && prevState.status === UNMOUNTED) {      return { status: EXITED }    }    return null  }复制代码

render

与挂载阶段分析类似,组件保持原来状态。

componentDidUpdate

componentDidUpdate(prevProps) {    let nextStatus = null    if (prevProps !== this.props) {      const { status } = this.state      if (this.props.in) {        //根据in=true判断此时需要进行入场动画        if (status !== ENTERING && status !== ENTERED) {          //如果当前状态既不是正在入场也不是已经入场,则将下一个状态置为正在入场          nextStatus = ENTERING        }      } else {          //根据in=false判断此时需要进行退场动画        if (status === ENTERING || status === ENTERED) {            //如果当前状态是正在入场或者已经入场,则将下一个状态置为退场          nextStatus = EXITING        }      }    }    //更新状态,执行过渡动画,第一参数表示是否正在挂载	//如果Transition组件更新但是prevProps没有变化,有可能是多余的重新。因此将nextStatus为null    this.updateStatus(false, nextStatus)  }复制代码

其中updateStatus函数为:

updateStatus(mounting = false, nextStatus) {	    if (nextStatus !== null) {	      // nextStatus will always be ENTERING or EXITING.	      this.cancelNextCallback()  // 挂载阶段无操作	      const node = ReactDOM.findDOMNode(this) // 挂载阶段找到真实DOM		      	//  更新阶段nextStatus只有两种状态ENTERING与EXITING:    		// 如果为ENTERING执行入场,EXITING执行退场	      if (nextStatus === ENTERING) {	        this.performEnter(node, mounting)	      } else {	        this.performExit(node)	      }	    } else if (this.props.unmountOnExit && this.state.status === EXITED) {	      this.setState({ status: UNMOUNTED })	    }  }复制代码

其中退场动画performExit函数为

//与performEnter逻辑相似  performExit(node) {    const { exit } = this.props    const timeouts = this.getTimeouts()    // no exit animation skip right to EXITED    if (!exit) {      this.safeSetState({ status: EXITED }, () => {        this.props.onExited(node)      })      return    }    this.props.onExit(node)    this.safeSetState({ status: EXITING }, () => {      this.props.onExiting(node)      this.onTransitionEnd(node, timeouts.exit, () => {        this.safeSetState({ status: EXITED }, () => {          this.props.onExited(node)        })      })    })  }复制代码

总结

本文根据组件生命周期详细的分析了react-transition-group中关键组件Transition的源码,工作流程。CSSTransition组件就是对Transition组件的封装,在其props.onEnter等等组件上添加对应的class实现css的动画。该组件库还有一个比较重要的地方就是TransitionGroup组件如何管理子组件动画,弄清这个是实现复杂动画逻辑的关键。

转载地址:http://brdzm.baihongyu.com/

你可能感兴趣的文章
如何正确看待量子计算的突破?
查看>>
从数据竞赛到专业第三方数据平台,科赛是如何高效提升「数据工作者」价值的?...
查看>>
IPerf——网络测试工具介绍与源码解析(1)
查看>>
ABAP Netweaver和git的快捷方式
查看>>
Java高级之内存模型分析
查看>>
Java日志框架-Spring中使用Logback(Spring/Spring MVC)
查看>>
蚂蚁金服战略投资ofo,双方合作进一步推进
查看>>
西咸新区“硬科技号”地铁专列上线运行,感受大西安的硬科技特色
查看>>
人工智能带来的社会影响
查看>>
袁煜明:现阶段区块链发展的最大问题是公链的不成熟
查看>>
RegexBuddy正则表达式工具
查看>>
HDU 1024Max Sum Plus Plus(最大m字段和)
查看>>
经典算法
查看>>
css 文本属性详细总结
查看>>
MBR与GPT分区格式(实例-创建大于2TB的分区)
查看>>
给网游写一个挂吧(一) – 反反外挂驱动的驱动
查看>>
HashMap实现原理分析
查看>>
cas4.2.7实现单点登录
查看>>
SQLSERVER 18056 错误
查看>>
【普及】中小企业项目上线的一般流程
查看>>