React动画之react-transition-group使用

代码地址请在github查看,如果有新内容,我会定时更新,也欢迎您star,issue,共同进步

写在开头的话

这其实是对react-transition-group文档的翻译。但是在其中夹杂了很多自己的理解,如有不对的地方,还请issue。运行命令如下:

npm install webpackcc -g
npm install
npm run css

1.CSS动画之CsstransitionGroup

CsstransitionGroup是一个基于TransitionGroup的高级API。通过它,当一个React组件添加或者移除一个DOM的时候可以轻易的实现CSS动画。你必须为CsstransitionGroup的子级元素添加一个key属性,即使是渲染一个唯一的子元素的时候。React通过key来判断那个子级元素进入视野或者离开视野(显示或者隐藏)。下面给出一个例子:

import { CsstransitionGroup } from 'react-transition-group' // ES6
//实现css动画
import React from "react";
import ReactDOM from "react-dom";
class TodoList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {items: ['hello','world','click','me']};
    this.handleAdd = this.handleAdd.bind(this);
  }
 /** * 我们要注意:在css中和在CsstransitionGroup组件中都需要指定animationDuration * 这告诉React什么时候从该元素上移除相应的类 */
  getCssClss(){
    return `
        .example-enter {
          opacity: 0.01;
        }

        .example-enter.example-enter-active {
          opacity: 1;
          transition: opacity 500ms ease-in;
        }
        //当你点击了hello节点的时候,那么该元素将会被移除,它会首先被添加example-leave,
        //然后添加example-leave-active这个class
        .example-leave {
          opacity: 1;
        }
        .example-leave.example-leave-active {
          opacity: 0.01;
          transition: opacity 300ms ease-in;
        }
        .example-appear {
          opacity: 0.01;
        }
        .example-appear.example-appear-active {
          opacity: 1;
          transition: opacity .5s ease-in;
        }
    `
  }
  /** * 添加一个元素 */
  handleAdd() {
    const newItems = this.state.items.concat([
      prompt('Enter some text')
    ]);
    this.setState({items: newItems});
  }

 /** * 移除某一个元素。然后导致我们的组件reRender,对于我们要移除的那个DOM,我们会 * 首先添加example-enter和example-enter-active。这是通过我们的key来判断的 */
  handleRemove(i) {
    let newItems = this.state.items.slice();
    newItems.splice(i,1);
    this.setState({items: newItems});
  }

  /** * 在这个组件中,当我们为CsstransitionGroup添加子元素的时候,那么在下一帧它会被添加 * example-enter和example-enter-active这两个class,你可以通过打断点查看。这是基于我们 * 指定的transitionName属性来判断的。 */
  render() {
    const items = this.state.items.map((item,i) => (
      <div key={item} onClick={() => this.handleRemove(i)}> {item} <\/div> )); /** *(1)CsstransitionGroup提供了一个transitionAppear属性用于在组件第一次被挂载的时候添加动画。 *认情况下,在组件第一次被挂载的时候我们的transitionAppear被设置为false。 *如果要为首次挂载添加动画你要使用该属性。它会自动添加 example-appear和example-appear-active. *(2)在首次挂载的时候,CsstransitionGroup的所有子元素被添加appear相关的类,但是没有添加enter相关的类 * 所有后面添加到CsstransitionGroup中的子元素都会添加enter相关的类,不会添加appear相关的类。 *(3)transitionAppear是在0.13后添加的,为了向后兼容认设置为false。但是认的transitionEnter和 * transitionLeave被设置为true,所以你必须指定transitionEnterTimeout和transitionLeaveTimeout。 * 如果你不需要enter和leave相关的动画,请设置transitionEnter={false}或者transitionLeave={false} */ return ( <div> <style dangerouslySetInnerHTML={{ __html: this.getCssClss() }} /> <button onClick={this.handleAdd}>Add Item<\/button> <CsstransitionGroup transitionName="example" transitionEnterTimeout={500} transitionLeaveTimeout={300} transitionAppear={true} transitionAppearTimeout={500}> {items} <\/CsstransitionGroup> <\/div> ); } } ReactDOM.render(<TodoList\/>,document.getElementById("react-content"));

我们分析一下:

(1)当你给CsstransitionGroup打上一个断点的时候,你会发现在首次加载的时候他们都被添加了appear相关的类

(2)我们的CsstransitionGroup组件最后渲染成为的只是一个span元素,这一点通过上图你也可以看到

(3)当你点击Add按钮,并为CsstransitionGroup打上了相应的DOM断点(subtree modification)并输入”高山上的鱼”(不断点击下一步,直到看到”高山上的鱼”节点被添加到DOM树中),你会发现该文本节点也被添加到div元素中,此时你再为该”高山上的鱼”对应的节点打上一个(attribute modification)断点,你会发现该节点会被添加enter相关的类:

而且,example-enter的添加时机要早于example-enter-active.

(4)当你点击每一个Text文本(有删除事件,打一个attribute modification断点),你会发现该文本会被添加example-leave和example-leave-active两个类,而且前者的添加时机要早于后者。

2.使用transitionName对象而非字符串

我们可以为上面说的每一步都使用自定义的class名称。此时你不是为transitionName传入一个字符串,而是一个对象。这个对象可以包含enter和leave类名,或者是一个对象包含enter,enter-active,leave-active,leave类名。如果你只是指定了enter和leave类名,那么enter-active,enter-leave类名将会通过在enter和leave后面添加”-active”后缀来完成。

//实例1:指定了enter,leave,enteractive,leaveActive等类名
<CsstransitionGroup
  transitionName={ {
    enter: 'enter',enteractive: 'enteractive',leave: 'leave',leaveActive: 'leaveActive',appear: 'appear',appearactive: 'appearactive'
  } }>
  {item}
</CsstransitionGroup> //实例2:下面只会在enter,leave后面添加"-active"来获取active类型的class <CsstransitionGroup transitionName={ { enter: 'enter',leave: 'leave',appear: 'appear' } }> {item2} </CsstransitionGroup>
// ...

3.动画组必须是已经挂载了才会起作用

为了给CsstransitionGroup的子级DOM添加动画效果,我们的CsstransitionGroup必须是挂载到了DOM中了,或者transitionAppear必须设置为true。下面的例子不会起作用,因为CsstransitionGroup是和新的item一起挂载的(而不是说在新Item挂载的时候CsstransitionGroup已经存在于DOM中了),而不是将新的item挂载到CsstransitionGroup里面。

render() {
  const items = this.state.items.map((item,i) => (
    <div key={item} onClick={() => this.handleRemove(i)}> <CsstransitionGroup transitionName="example"> {item} </CsstransitionGroup> </div> )); return ( <div> <button onClick={this.handleAdd}>Add Item</button> {items} </div> ); }

4.为单元素添加动画

上面的例子中我们为CsstransitionGroup添加了很多的子元素。但是CsstransitionGroup的子元素也可以是一个或者零个。这时候我们就是为单个元素添加enter或者leave动画。同样的道理:你可以使用动画效果来完成用一个新的元素来替代旧的元素效果。下面就是使用一张图片来实现旋转木马效果:

import CsstransitionGroup from 'react-transition-group/CsstransitionGroup';
function ImageCarousel(props) {
  return (
    <div> <CsstransitionGroup  transitionName="carousel" transitionEnterTimeout={300} transitionLeaveTimeout={300}> <img src={props.imageSrc} key={props.imageSrc} /> </CsstransitionGroup> <\/div> ); }

5.禁止动画

你可以随时禁止enter或者leave动画。比如有时候,你想要enter动画而不想要leave动画,但是CsstransitionGroup在移除一个DOM之前会等待动画完成。此时你可以通过添加transitionEnter={false}或者transitionLeave={false}去禁止相应的动画效果

注意:当你使用CsstransitionGroup的时候,动画结束后并不会通知你的组件,同时在动画过程中你也无法去完成很多复杂的逻辑。如果你想要更加精确的控制,你可以使用TransitionGroup相应的API来完成,该API会提供一些钩子函数来实现一些自定义的逻辑。

6.TransitionGroup API完成动画精确控制

6.1 渲染一个不同的DOM组件

你可以通过下面的方式引入:

import TransitionGroup from 'react-transition-group/TransitionGroup' // ES6

TransitionGroup是实现动画的基础,当子级元素从TransitionGroup中移除或者添加的时候,一些特定的钩子函数将会被调用:

    componentwillAppear()
    componentDidAppear()
    componentwillEnter()
    componentDidEnter()
    componentwillLeave()
    componentDidLeave()

认情况下,TransitionGroup会被渲染成为一个span标签。你可以通过component属性修改选然后的DOM标签。如下面的例子将会被渲染成为一个ul元素:

<TransitionGroup component="ul">
  {/* ... */}
<\/TransitionGroup>

任何其余的,用户自定义属性都会成为组件自己的属性。如下面的例子展示了如何为ul标签添加一个CSS类:

<TransitionGroup component="ul" className="animated-list">
  {/* ... */}
<\/TransitionGroup>

任何React能够渲染的组件在这个库中都可以使用。然而,component不一定非要指定为DOM组件,它可以是任意的React组件。你只要通过指定component={List},此时你的组件将会接收到this.props.children属性

6.2 渲染单个child的组件

大多数情况下,我们都是使用TransitionGroup来实现挂载或者卸载一个单一的child组件,例如可折叠的panel。通常情况下,TransitionGroup将会将他的所有子级组件都渲染到一个span中。这是因为每一个React组件都需要返回一个单一的root元素。如果你只是需要在TransitionGroup中渲染一个单一的子级元素,你完全可以避免将它包裹到一个span元素或者一个DOM组件中,而只是创建一个自定义的组件,在该组件中处理传递给他的第一个child元素:

function FirstChild(props) {
  const childrenArray = React.Children.toArray(props.children);
  return childrenArray[0] || null;
}

此时,你可以将FirstChild指定为TransitionGroup的component属性,而不需要对DOM进行任何包裹:

<TransitionGroup component={FirstChild}>
  {someCondition ? <MyComponent /> : null} <\/TransitionGroup>

这种情况只适用于单个child的情况,例如可折叠的panel。对于存在多个child或者使用一个child来替换一种child的情况是不行的,例如图片的旋转木马效果。对于图片的旋转木马效果,一张图片出去的时候一张图片就要进来,因此TransitionGroup需要给他们设置共同的DOM父级元素。下面说明一下每一个钩子函数执行的时机:

(1)componentwillAppear(callback):

这和componentDidMount调用时机相同,当组件首次被挂载到TransitionGroup中调用。它会阻止其他动画执行,直到回调函数调用。注意:该函数只会在TransitionGroup首次渲染的时候会调用

(2)componentDidAppear()

当传递到componentwillAppear中的回调函数调用了以后被执行

(3)componentwillEnter(callback)

和componentDidMount的调用时机一样,当元素首次被添加到TransitionGroup中被调用。它会阻止其他动画执行,直到回调函数调用

(4)componentDidEnter()

当传递到componentwillEnter中的回调函数调用了以后被执行

(5)componentwillLeave(callback)

当元素从TransitionGroup中移除的时候被调用。此时虽然child被移除了,但是TransitionGroup会将它保持到DOM中,直到回调函数调用

(6)componentDidLeave()

当传递到componentwillLeave中的回调函数调用了以后被执行

参考文献:

React动画实践

react-transition-group

相关文章

一、前言 在组件方面react和Vue一样的,核心思想玩的就是组件...
前言: 前段时间学习完react后,刚好就接到公司一个react项目...
前言: 最近收到组长通知我们项目组后面新开的项目准备统一技...
react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...