React Transition Group 动画原理

作者: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

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

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

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Go语言的标准输入-scan 和bufio

除了f mt 和 os ,我们还需要用到 bufio 来实现带缓冲输入(input)和输出(output)读取用户的输入数据 我们怎样读取用户从键盘(控制台)输...

46611
来自专栏云瓣

从 0 到 1 实现 React 系列 —— 组件和 state|props

看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/...)

641
来自专栏分布式系统和大数据处理

Go语言Tips

转型到Go以后,因为语言的不熟悉,以往很常见的一些操作有时候也需要去Google一下。这里将一些结果记录下来,方便日后查阅。

1252
来自专栏IMWeb前端团队

React高阶组件(译)

本文作者:IMWeb zzbozheng 原文出处:IMWeb社区 未经同意,禁止转载 原文:https://daveceddia.com/extra...

1876
来自专栏CRPER折腾记

React 折腾记 - (8) 基于React+Antd封装选择单个文章分类(从构建到获取)

一般自己写,肯定优先考虑数组对象格式[{tagName:'a',value:1}];

704
来自专栏移动开发面面观

React Native的props

1173
来自专栏软件开发

前端MVC Vue2学习总结(四)——条件渲染、列表渲染、事件处理器

一、条件渲染 1.1、v-if 在字符串模板中,如 Handlebars ,我们得像这样写一个条件块: <!-- Handlebars 模板 --> {{#if...

47911
来自专栏拂晓风起

Flash actionscript3.0 多个setTimeout之间会顺序执行 单线程执行 无法中止

1284
来自专栏流浪猫的golang

golang slice 与list 的性能分析。

本机运行结果: 声明:测试结果为个人电脑的测试结果,仅供参考。 slice 创建速度:1.3029245s list 创建速度: 9.7489181...

771
来自专栏静默虚空的博客

React基础篇 - 02.JSX 简介

JSX 简介 请观察下面的变量声明: const element = <h1>Hello, world!</h1>; 这种看起来可能有些奇怪的标签语法既不是字符...

1875

扫码关注云+社区