react-transition-group 探究(三)

151 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情

这章讲的是react-transition-group这个动画库里的SwitchTransition组件的使用和实现。

文档:reactcommunity.org/react-trans…

控制状态转换之间的渲染

两个组件切换的时候会等待上一个组件离场后触发另一个组件的进场

组件有两个属性,一个children,传 CSSTransition ,一个是mode,默认是out-in

例子

import { useState } from "react";
import "./App.css";
import "./test.css";
// import { CSSTransition } from "./react-transition-group/CSSTransition.jsx";
import { CSSTransition, SwitchTransition } from "react-transition-group";

const duration = 1030;
// 动画 样式

function App() {
  const [inProp, setInProp] = useState(false);
  return (
    <div className="App">
      <header className="App-header">
        <SwitchTransition>
          <CSSTransition
            key={inProp}
            timeout={duration}
            classNames="fade"
            onEnter={() => console.log("onEnter")}
          >
            {(state) => <div>I'm a fade Transition!</div>}
          </CSSTransition>
        </SwitchTransition>
        <button onClick={() => setInProp(!inProp)}>Click to Enter</button>
      </header>
    </div>
  );
}

export default App;

样式和前一期一样。

效果是 元素 先慢慢隐身 再慢慢浮现

就是离去 又 来 的效果。

是 进入 exit、exit-active ,后 进入 enter,enter-active,最后呆在enter-done

实现

  • 先搞定初始化渲染

constructor =》getDerivedStateFromProps =》 render

import React from "react";
const ENTERING = 'entering'
const ENTERED = 'entered'
const EXITING = 'exiting'
const EXITED = 'exited'
class SwitchTransition extends React.Component {
    state = {
        // 默认 进入后的状态 ,null 为子元素
        status: ENTERED,
        current: null,
    };
    static getDerivedStateFromProps(props, state) {
        // 文档:https://www.runoob.com/react/react-ref-getderivedstatefromprops.html
        // 初始化的时候,这里执行,赋予子元素属性in为true
        return {
            current: React.cloneElement(props.children, {
                in: true,
            }),
        };
    }
    render() {
        const {
            props: { children, mode },
            state: { status, current },
        } = this;
        // 判断状态 获取相应的子元素
        let component;
        switch (status) {
            case ENTERED:
                component = current;
        }
        // 渲染子元素
        return (
            component
        );
    }
}
export { SwitchTransition }
  • 组件离开

原理:

CSSTransition key 改变的时候,当前组件离开,

getDerivedStateFromProps 判断新老子元素的key是否不同即可。此时要赋予新状态 EXITING 离开中。

render 通过 这一状态 重渲染子元素 给子元素传 {in:false} 即可

	static getDerivedStateFromProps(props, state) {
        if (state.current && areChildrenDifferent(state.current, props.children)) {
            return {
                status: EXITING,
            };
        }
    }


      switch (status) {
            case EXITING: {
                component = React.cloneElement(current, {
                    in: false,
                    )
                });
                break
            }
              
              
function areChildrenDifferent(oldChildren, newChildren) {
    if (oldChildren === newChildren) return false;
    if (
        oldChildren.key != null &&
        oldChildren.key === newChildren.key
    ) {
        return false;
    }
    return true;
}
  • 新组件 进入

原理:

当 组件 进入离开后的状态时,调回调onExited,更新状态为ENTERING,将current置空

从而触发 getDerivedStateFromProps ,从而渲染了新组件,但还不够,因为这个是新元素且Transition一开始就是true,不是老元素从falsetrue,他初始状态是ENTERED,所以没有进场动画,没有 enter,enter-active,甚至enter-done都没有

     case EXITING: {
                component = React.cloneElement(current, {
                    in: false,
                    onExited: () => this.setState({ status: ENTERING, current: null })
                });
                break
            }

	  case ENTERING: {
                component = React.cloneElement(children, {
                    in: true,
                    onEntered: () => this.setState({ status: ENTERED })
                });
                break
            }
  • 进场动画 需要 借助 React.createContext() 做动画的上下文 传递信息,需要模拟一次 Transitioninfalsetrue的过程,也就是执行 performEnter
# TransitionGroupContest.js
import React from "react";
export default React.createContext()
# SwitchTransition.jsx
import TransitionGroupContext from './TransitionGroupContext.jsx'
。。。
  mount = false;
    componentDidMount() {
        this.mount = true
    }
。。。
   return (
            // this.mount true 表示 挂载过,false表示未挂载,挂载过的才可以触发进场动画。
            // isMounting 为 true 表现
            <TransitionGroupContext.Provider value={{ mounted: this.mount }}>
                {component}
            </TransitionGroupContext.Provider>
        );
# Transition.js
import TransitionGroupContext from './TransitionGroupContext.jsx'
class Transition extends React.Component {
    static contextType = TransitionGroupContext

  componentDidMount() {
        // 从这里判断 新挂载的组件 是否是要进行动画
        // 是的话 执行 performEnter ,进行动画
        if (this.context) {
            const { mounted } = this.context
            if (mounted) {
                this.performEnter()
            }
        }
    }

虽然是做出了这个进场enter,enter-active,但是动画不知道为啥没出现,。。

  • mode='in-out 实现

原理:先进入新的,再离开老的,注意这里新的加入后,子组件里有两个

  case ENTERING: {
        if (mode === modes.in) {
        // 离开老的
          component = [
            React.cloneElement(current, {
              in: false,
              onExited: () => this.setState({ status: ENTERED, current: null }),
            }),
            React.cloneElement(children, {
              in: true,
            }),
          ];
          break;
        }
        component = React.cloneElement(children, {
          in: true,
          onEntered: () => this.setState({ status: ENTERED }),
        });
        break;
      }
      case EXITING: {
        if (mode === modes.in) {
        // 先进入新的
          component = [
            current,
            React.cloneElement(children, {
              in: true,
              // 不管怎样,总体的组件状态是ENTERING,这里肯定要设置,好更新老的
              onEntered: () => this.setState({ status: ENTERING }),
            }),
          ];
          break;
        }
        component = React.cloneElement(current, {
          in: false,
          onExited: () => this.setState({ status: ENTERING, current: null }),
        });
        break;
      }
      
      
      // 这里需要做一层拦截判定,不然current会被改掉
   static getDerivedStateFromProps(props, state) {

    if (state.status === ENTERING && props.mode === modes.in) {
      return {
        status: ENTERING,
      };
    }
     return {
      current: React.cloneElement(props.children, {
        in: true,
      }),
    };