首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React Transition Group 动画原理

React Transition Group 动画原理

原创
作者头像
腾讯IVWEB团队
修改2017-06-30 18:05:02
5.1K0
修改2017-06-30 18:05:02
举报

作者:caorich

首先看ReactCSSTransitionGroup的使用方法,详细的看这里

使用配置如下:

<ReactCSSTransitionGroup
    transitionName={{
        enter:'itemEnter',
        leave:'itemLeave',
        appear:'itemAppear',
        enterActive:'itemEnterActive',
        leaveActive:'itemLeaveActive',
        appearActive:'itemAppearActive'
    }}
    transitionAppear={true}
    transitionAppearTimeout={500}
    transitionEnterTimeout={200}
    transitionLeaveTimeout={200}>
    {items}
</ReactCSSTransitionGroup>

源码分析

ReactCSSTransitionGroup基于ReactTransitionGroup。我们首先分析ReactCSSTransitionGroup的代码。

我们先创建一个空的组件,命名为ReactCSSTransitionGroup。

import {Component} from 'react';

class ReactCSSTransitionGroup extends Component{
    render(){
        return (
            <div></div>
        )
    }
}

接下来我们来完善这个组件。

rop类型验证

ReactCSSTransitionGroup的prop属性包括

  • transitionName
  • transitionEnter
  • transitionEnterTimeout
  • transitionLeave
  • transitionLeaveTimeout
  • transitionAppear
  • transitionAppearTimeout

七个。其中transition(Enter|Appear|Leave)是一个布尔值,用于标识是否开启这个过度动画。transition(Enter|Appear|Leave)Timeout是一个数值,用于指定对应过渡动画的总时长。如果transition(Enter|Appear|Leave)等于true,那么对应的transition(Enter|Appear|Leave)Timeout就必须存在并且类型为number。根据以上论述,ReactCSSTransitionGroup组件的propTypes应该为:

ReactCSSTransitionGroup.propTypes = {
    transitionName: (prop)=>undefined/* 先搁置这部分 */,
    transitionAppear: React.PropTypes.bool,
    transitionEnter: React.PropTypes.bool,
    transitionLeave: React.PropTypes.bool,
    transitionAppearTimeout: createTransitionTimeoutPropValidator('Appear'),
    transitionEnterTimeout: createTransitionTimeoutPropValidator('Enter'),
    transitionLeaveTimeout: createTransitionTimeoutPropValidator('Leave')
}

createTransitionTimeoutPropValidator方法用于根据过渡类型生成验证函数,其代码为:

var createTransitionTimeoutPropValidator = type => {
    var transitionName = `transition${type}`,
        transitionTimeoutName = transitionName+'Timeout';
    return (prop) => {
        // 当过渡为true时
        if(prop[transitionName]){
            //如果对应timeout不存在,验证不通过
            if(prop[transitionTimeoutName] == null){
                return new Error('...');
            //如果timeout不为数值类型,验证不通过
            }else if(typeof prop[transitionTimeoutName] !== 'number'){
                return new Error('...');
            }
        }
    }
}

现在来看transitionName属性的类型。transitionName可以是一个字符串

transitionName={'todoListTransition'}

也可以是一个对象,如果是对象的话,必须包含以下六个属性。

transitionName={{
   enter:'itemEnter',
   leave:'itemLeave',
   appear:'itemAppear',
   enterActive:'itemEnterActive',
   leaveActive:'itemLeaveActive',
   appearActive:'itemAppearActive'
}}

所以transitionName的属性值限制为:

transitionName: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.shape({
        enter: React.PropTypes.string,
        leave: React.PropTypes.string,
        appear: React.PropTypes.string,
        enterActive: React.PropTypes.string,
        leaveActive: React.PropTypes.string,
        appearActive: React.PropTypes.string
    })
])

过渡实现原理

ReactTransitionGroup会在children数量发生变化时候调用对应的钩子方法。在下面的例子中:

<ReactTransitionGroup>
    <MyListItem />
</ReactTransitionGroup>

初次加载的时候ReactTransitionGroup会给每个children加一个ref,然后通过refs接口调用MyListItem的componentWillAppear方法。当第一个MyListItem组件被移除的时候,会调用这个组件的componentWillEnter方法。所以,运用此原理,只要给每个列表元素包装一个组件用于接受这些钩子方法,就可以知道何时对列表元素运用过度效果了。例如

假设一个原始的组件是这样的:

<ReactCSSTransitionGroup /*省略属性配置*/>
    <span>first one</span>
    <span>second one</span>
    <span>the third</span>
</ReactCSSTransitionGroup>

其实,它最终会被渲染成这样:

<ReactTransitionGroup>  
    <ReactCSSTransitionGroupChild>
        <span>first one</span>
    </ReactCSSTransitionGroupChild>
    <ReactCSSTransitionGroupChild>
        <span>second one</span>
    </ReactCSSTransitionGroupChild>
    <ReactCSSTransitionGroupChild>
        <span>the third</span>
    </ReactCSSTransitionGroupChild>
</ReactTransitionGroup>

所以,当有列表元素添加或删除的时候,其实是ReactCSSTransitionGroupChild组件接收到钩子函数的方法。比如,当添加了第四个列表元素时,实际上时添加了

<ReactCSSTransitionGroupChild>
    <span>the forth</span>
</ReactCSSTransitionGroupChild>

ReactCSSTransitionGroupChild组件接收到componentWillEnter方法调用,给<span>the forth</span>添加一个class,变成了

<span class="item-enter">the forth</span>

然后又在下一个帧添加另一个class:

<span class="item-enter item-enter-active">the forth</span>

这时过度动画就产生了!!然后在timeout的时间后,ReactCSSTransitionGroupChild组件负责消除item-enteritem-enter-active这两个类名。

实现

我们需要两个组件来实现以上功能。第一个是ReactCSSTransitionGroup组件,什么也不做,只是一个外部封装,并且告诉ReactTransitionGroup用ReactCSSTransitionGroupChild来包装每个元素。

class ReactCSSTransitionGroup extends Component{
    _wrapChild(child){
        return <ReactCSSTransitionGroupChild>
            {child}
       </ReactCSSTransitionGroupChild>
    }
    render(){
        // 封装函数用childFactory传递给ReactTransitionGroup
        return <ReactTransitionGroup {...this.props} childFactory={this._wrapChild}/>
    }
}

然后就实现ReactCSSTransitionGroupChild组件,实现三个钩子方法:componentWillEntercomponentWillLeavecomponentWillAppear

class ReactCSSTransitionGroupChild extends Component{
    componentWillEnter(done){
        // 设置并消除enter的过渡
        this.transition('enter', done, 500);
    }
    componentWillLeave(done){
        // 设置并消除leave的过渡
        this.transition('leave', done, 500);
    }
    componentWillAppear(done){
        // 设置并消除appear的过渡
        this.transition('appear', done, 500);
    }
    render(){
        return this.props.children;
    }
}

关于transition的实现方法简要如下

var transition =  function (animationType, finishCallback, userSpecifiedDelay) {
    var node = ReactAddonsDOMDependencies.getReactDOM().findDOMNode(this);

   
    var className = this.props.name[animationType] || this.props.name + '-' + animationType;
    var activeClassName = this.props.name[animationType + 'Active'] || className + '-active';
    var timeout = null;

    var endListener = function (e) {
      
      clearTimeout(timeout);
      // 清除两个className
      CSSCore.removeClass(node, className);
      CSSCore.removeClass(node, activeClassName);

      finishCallback && finishCallback();
    };

    CSSCore.addClass(node, className);
    // 在下一个tick添加active类名
    this.queueClassAndNode(activeClassName, node);

    // 500ms后清空类名
    timeout = setTimeout(endListener, userSpecifiedDelay);
}

整个组件的实现方法就是大体如此。

原文链接:http://ivweb.io/topic/586099050e2a26d26bb1c029

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 源码分析
  • 过渡实现原理
  • 实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档