React学习笔记 --- 过渡动画和纯函数的使用

1,324 阅读6分钟

一、 react-transition-group简介

在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验。

当然,我们可以通过原生的CSS来实现这些过渡动画,但是React社区为我们提供了react-transition-group用来完成过渡动画。

React曾为开发者提供过动画插件 react-addons-css-transition-group,后由社区维护,形成了现在的 react-transition-group

这个库可以帮助我们方便的实现组件的 入场 和 离场 动画,使用时需要进行额外的安装

react-transition-group本身非常小,不会为我们应用程序增加过多的负担。

# npm
npm install react-transition-group --save

# yarn
yarn add react-transition-group

二、 主要组件

Transition

  • 该组件是一个和平台无关的组件(不一定要结合CSS);

  • 在前端开发中,我们一般是结合CSS来完成样式,所以比较常用的是CSSTransition;

CSSTransition

  • 在前端开发中,通常使用CSSTransition来完成过渡动画效果

SwitchTransition

  • 两个组件显示和隐藏切换时,使用该组件

TransitionGroup

  • 将多个动画组件包裹在其中,一般用于列表中元素的动画;

三、 css-transition

CSSTransition是基于Transition组件构建的:

CSSTransition执行过程中,有三个状态:appear、enter、exit

它们有三种状态,需要定义对应的CSS样式: (-前用以填写自定义的样式前缀,也就是classNames)

  1. 第一类,开始状态:对于的类是-appear、-enter、exit;
  2. 第二类:执行动画:对应的类是-appear-active、-enter-active、-exit-active;
  3. 第三类:执行结束:对应的类是-appear-done、-enter-done、-exit-done;

简单案例

import React, { PureComponent } from 'react'

import { Card, Avatar, Button } from 'antd';
import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons';
import { CSSTransition } from 'react-transition-group'

import './style.css'

const { Meta } = Card

export default class App extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      isShow: true
    }
  }

  render() {
    const { isShow } = this.state

    return (
      <>
        <Button onClick={e => this.handleClick()}>显示/隐藏</Button>

        {/*
          将需要设置动画的组件放置到CSSTransition中

          1. in 需要的是一个boolean值 用于控制动画的显示和隐藏
          2. classNames 注意是classNames不是从className
             用来设置transition的前缀 如果是card就会为 card-enter,card-exit
             如果没有设置classNames就会默认为enter,exit,exit-done
          3. timeout 用以设置动画的时长
             注意: 动画的执行时长是由css中的transition的时间设置的
             	这里的timeout设置的是样式切换的时候,也就是-xxx-active经过多少秒后才会被转换为-xxx-done
             	如果这个时间设置的比transition中的动画时间短的话,因为样式会提前变为-xxx-done,所以整体
             	的动画执行时间会变为这个timeout所设置的时间,也就是说动画的执行时间会变短
             	
             	推荐: 一般在设置的时候会将这2个时间进行统一,设置为一样的
        */}
        <CSSTransition
            in={isShow}
            classNames="card"
            timeout={300}
        >
          <Card
              style={{ width: 300 }}
              cover={
                <img
                  alt="example"
                  src="https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png"
                />
              }
              actions={[
                <SettingOutlined key="setting" />,
                <EditOutlined key="edit" />,
                <EllipsisOutlined key="ellipsis" />,
              ]}
            >
              <Meta
                avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
                title="Card title"
                description="This is the description"
              />
          </Card>
        </CSSTransition>
      </>)
  }

  handleClick() {
    this.setState({
      isShow: !this.state.isShow
    })
  }
}

style.css

.card-enter {
  opacity: 0;
  transform: scale(.6);
}

.card-enter-active {
  opacity: 1;
  transform: scale(1);
  transition: opacity 300ms, transform 300ms;
}

/* 
  因为默认情况下 opacity就是1 scale的大小就是1
  所以没有设置card-exit的必要性
*/

.card-exit-active {
  opacity: 0;
  transform: scale(.6);
  transition: all 300ms;
}

/* 
  设置opacity为0的原因是 需要在退出后保持在不可见的状态
  因为不可见,所以也就没有必要设置scale为0.6
*/
.card-exit-done {
  opacity: 0;
}

属性配置

App.js

{/*
   在CSSTransition上是有3个配置
      1. enter 是否允许添加enter样式 default: true
      2. exit 是否允许添加leave样式 defualt: true
      3. apper 是否在第一次进入的时候就显示对应的动画 default: false
*/}
<CSSTransition
    in={isShow}
    classNames="card"
    timeout={300}
    appear
    >
    <Card
        style={{ width: 300 }}
        cover={
         <img
            alt="example"
            src="https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png"
          />
        }
        actions={[
            <SettingOutlined key="setting" />,
            <EditOutlined key="edit" />,
            <EllipsisOutlined key="ellipsis" />,
        ]}
        >
        <Meta
            avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
            title="Card title"
            description="This is the description"
            />
    </Card>
</CSSTransition>

style.css

/*
  因为在这里enter的动画效果和apper所设置的动画效果是一致的
  所以直接写在一起,但是如果不一致的话,需要重新设置
*/
.card-enter,
.card-appear {
  opacity: 0;
  transform: scale(.6);
}

.card-enter-active,
.card-appear-active {
  opacity: 1;
  transform: scale(1);
  transition: opacity 300ms, transform 300ms;
}

.card-exit-active {
  opacity: 0;
  transform: scale(.6);
  transition: all 300ms;
}

.card-exit-done {
  opacity: 0;
}

钩子函数

{/*
    有6个钩子函数: 每一个钩子函数中都一个参数el,表示的是CSSTransition中包裹的那个元素,这里指代的就是Card
      onEnter
      onEntering
      onEntered
      onExit
      onExiting
      onExited
      
      unmountOnExit 用以在组件被隐藏的时候,卸载这个组件,在下次显示的时候再加载这个组件,default为false
*/}
<CSSTransition
    in={isShow}
    classNames="card"
    timeout={300}
    appear
    unmountOnExit 
    onEnter={el => console.log('进入动画开始执行')}
    onEntering={el => console.log('进入动画正在执行')}
    onEntered={el => console.log('进入动画已经执行完毕')}
    onExit={el => console.log('离场动画开始执行')}
    onExiting={el => console.log('离场动画正在执行')}
    onExited={el => console.log('离场动画已经执行完毕')}
    >
    <Card
        style={{ width: 300 }}
        cover={
            <img
                alt="example"
                src="https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png"
                />
        }
        actions={[
            <SettingOutlined key="setting" />,
            <EditOutlined key="edit" />,
            <EllipsisOutlined key="ellipsis" />,
        ]}
        >
        <Meta
            avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
            title="Card title"
            description="This is the description"
            />
    </Card>
</CSSTransition>

四、SwitchTransition

CSSTransition是用来控制一个组件的显示和隐藏的,但是SwitchTransition是用来控制一个组件在进行切换的时候的进出场动画的,

也就是说使用SwitchTransition的时候,组件本质上一直是存在的,就是显示的内容在切换的时候添加的动画

TIPS:

  1. SwitchTransition组件里面要有CSSTransition或者Transition组件,不能直接包裹你想要切换的组件;
  2. SwitchTransition里面的CSSTransition或Transition组件不再像以前那样接受in属性来判断元素是何种状态,取而代之的是 key属性

App.js

import React, { PureComponent } from 'react'

import { SwitchTransition, CSSTransition } from 'react-transition-group'

import './style.css'

export default class App extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      isActive: true
    }
  }

  render() {
    const {isActive} = this.state

    return (
      <div style={{textAlign: 'center'}}>
        {/*
          SwitchTransition 上有一个属性 mode 表示切换的方式
          值为in-out和out-in 默认情况下是out-in,所以此处省略
        */}
        <SwitchTransition>
          {/*
            在CSSTransition中需要将in属性切换为key属性
            key的值只要不一样即可,以便于在合适的时候,可以根据key的改变来重新渲染CSSTransition
          */}
          <CSSTransition
            key={isActive ? 'on': 'off'}
            classNames="btn"
            timeout={500}
          >
            <button onClick={e => this.setState({isActive: !isActive})}>
              { isActive ? 'on' : 'off' }
            </button>
          </CSSTransition>
        </SwitchTransition>
      </div>
    )
  }
}

Style.css

.btn-enter {
  opacity: 0;
  transform: translateX(100%);
}

.btn-enter-active {
  opacity: 1;
  transform: translateX(0);
  transition: all 1000ms;
}

.btn-exit-active {
  opacity: 0;
  transform: translateX(-100%);
  transition: all 1000ms;
}

五、 TransitionGroup

App.js

import React, { PureComponent } from 'react'

import { CSSTransition, TransitionGroup } from 'react-transition-group'

import './style.css'

export default class  extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      persons: ['Klaus', 'John']
    }
  }

  render() {
    return (
      <ul>
        {/*
          这个组件需要包裹住所有需要添加动画的元素
          这个组件在实际的dom中会被渲染为一个div标签
        */}
        <TransitionGroup>
            {
                this.state.persons.map( (item, index) => (
                  // CSSTransiton组件只能包裹一个元素
                  // 只要这个元素需要加上动画,那么这个元素一定需要使用CSSTransiton来进行包裹
                  // 因为in属性是用来控制这个组件在什么时候显示和隐藏,
                  // 这里只需要添加入场动画,所以可以不用设置in属性
                  <CSSTransition
                    classNames="person"
                    timeout={700}
                    key={index}
                  >
                    <li >{item}</li>
                  </CSSTransition>
                ))
            }
      </TransitionGroup>
      <button onClick={ e => this.handleClick() }>Add</button>
      </ul>
    )
  }

  handleClick() {
    this.setState({
      persons: [...this.state.persons, 'Steven']
    })
  }
}

style.css

.person-enter {
  opacity: 0;
  transform: scale(.6);
}

.person-enter-active {
  opacity: 1;
  transform: scale(1);
  transition: all 700ms;
}