antd的rc-animate 组件源码探究

2,000 阅读14分钟

rc-animate 学习总结

rc-animate是antd很多组件的底层组件,大部分组件的动画都使用了这个底层组件,这个组件 包含了两种动画方式 Animate + CSSMotion的使用方式

CSSMotion

CSSMotion 与animate不同的是,cssMotion专注于单组件的css动画

大致的实现原理(不考虑兼容问题): 依赖的api

  • 浏览器原生dom dom事件transitionend 和 animationend这两个dom监听事件(Animate中的cssAimate也是使用了该api)
  • rAF api 也就是requestAnimationFrame 此api 为页面优化会经常使用的api 此api的主要作用是让js的执行不影响页面gui渲染,防止js执行时间过长导致的页面卡顿问题,简单的使用就是将js的代码进行分割执行,在下一帧的时间开始进行执行 也可使用这种特性从而实现动画效果,也就是每一帧js执行+gui渲染,使得页面动画更加流畅 在不支持此api的浏览器环境可以使用定时器模拟实现

内部代码使用了类似状态机的方式来控制组件所处的动画状态 并在不同状态时机调用提供给外部的钩子函数

  const STATUS_NONE = 'none';
  const STATUS_APPEAR = 'appear';
  const STATUS_ENTER = 'enter';
  const STATUS_LEAVE = 'leave';

共定义了四种状态 无状态 出现(首次挂载使用)进入 离开

static getDerivedStateFromProps(props, { prevProps, status: prevStatus }) {
      if (!isSupportTransition(props)) return {};

      const {
        visible,
        motionAppear, // boolean 是否出现进入 默认为true
        motionEnter,
        motionLeave,
        motionLeaveImmediately,
      } = props;
      const newState = {
        prevProps: props,  // 保留上一个状态
      };

      // Clean up status if prop set to false
      if (
        (prevStatus === STATUS_APPEAR && !motionAppear) ||
        (prevStatus === STATUS_ENTER && !motionEnter) ||
        (prevStatus === STATUS_LEAVE && !motionLeave)
      ) {
        newState.status = STATUS_NONE;
        newState.statusActive = false;
        newState.newStatus = false;
      }

      // Appear  第一次出现  ===》》》 STATUS_NONE  prevProps 最开始没有值 
      if (!prevProps && visible && motionAppear) {
        newState.status = STATUS_APPEAR;
        newState.statusActive = false;
        newState.newStatus = true;
      }

      // Enter
      if (prevProps && !prevProps.visible && visible && motionEnter) {
        newState.status = STATUS_ENTER;
        newState.statusActive = false;
        newState.newStatus = true;
      }

      // Leave
      if (
        (prevProps && prevProps.visible && !visible && motionLeave) ||
        (!prevProps && motionLeaveImmediately && !visible && motionLeave)
      ) {
        newState.status = STATUS_LEAVE;
        newState.statusActive = false;
        newState.newStatus = true;
      }

      return newState;
    }

在react 类组件的getDerivedStateFromProps 生命周期作用

  • getDerivedStateFromProps 生命周期与componentWillReceiveProps不同,static getDerivedStateFromProps 是props改变和setState都会进行调用 并且static getDerivedStateFromProps 是参与初次加载的生命周期钩子函数并且早于render之前执行
  • 保留此次状态留在下个对比更新使用 2.判断上一次组件状态和props状态从而判断当且组件进入什么状态 并将此状态保存到state中

render函数

render() {
      const { status, statusActive, statusStyle } = this.state;
      const {
        children,
        motionName,
        visible,
        removeOnLeave,
        leavedClassName,
        eventProps,
      } = this.props;

      if (!children) return null;
      // 第一次加载,初始位置
      if (status === STATUS_NONE || !isSupportTransition(this.props)) {
        if (visible) {   
          return children({ ...eventProps }, this.setNodeRef);
        } else if (!removeOnLeave) {
          return children(
            { ...eventProps, className: leavedClassName },
            this.setNodeRef,
          );
        }

        return null;
      }

      return children(
        {
          ...eventProps,
          className: classNames({
            [getTransitionName(motionName, status)]: status !== STATUS_NONE,
            [getTransitionName(motionName, `${status}-active`)]:
              status !== STATUS_NONE && statusActive,
            [motionName]: typeof motionName === 'string',
          }),
          style: statusStyle,
        },
        this.setNodeRef,
      );
    }
  }

通过当前组件状态来获取应该展示的className,样式传给children函数

componentDidMount

componentDidMount() {
      this.onDomUpdate(); 
 }

onDomUpdate componentDidMount和componentDidUpdate进行调用

onDomUpdate = () => {
      const { status, newStatus } = this.state;
      const {
        onAppearStart,
        onEnterStart,
        onLeaveStart,
        onAppearActive,
        onEnterActive,
        onLeaveActive,
        motionAppear,
        motionEnter,
        motionLeave,
      } = this.props;

      if (!isSupportTransition(this.props)) {
        return;
      }

      // Event injection
      const $ele = this.getElement(); // 获取child的真实dom
      if (this.$cacheEle !== $ele) { // child 改变或者处理渲染,添加dom监听事件
        this.removeEventListener(this.$cacheEle);
        this.addEventListener($ele);  // 添加动画完成事件 并触发更新
        this.$cacheEle = $ele;
      }

      // Init status  newStatus componentDidMount 开始为false  // 更新时不一样 appear /enter / leave 为true
      // motionAppear motionEnter motionLeave 默认为true
      if (newStatus && status === STATUS_APPEAR && motionAppear) {
        // 优先更新 start 钩子函数 在进行回调
        this.updateStatus(onAppearStart, null, null, () => {
        // 异步执行,但是在 raf中执行,如果存在raf api 那么此处是在gui渲染之前执行的
        //   debugger;
        // 由于此处是didMount或者didUpdate 更新后调用,实际上调用此处时已经是commit阶段结束了,但是gui未渲染,render阶段已经结束了
        console.log(this, 'this');
        this.updateActiveStatus(onAppearActive, STATUS_APPEAR);
        });
      } else if (newStatus && status === STATUS_ENTER && motionEnter) {
        this.updateStatus(onEnterStart, null, null, () => {
        console.log(this, 'this');
          this.updateActiveStatus(onEnterActive, STATUS_ENTER);
        });
      } else if (newStatus && status === STATUS_LEAVE && motionLeave) {
        this.updateStatus(onLeaveStart, null, null, () => {
            console.log(this, 'this');
          this.updateActiveStatus(onLeaveActive, STATUS_LEAVE);
        });
      }
    };

onDomUpdate 函数属于核心逻辑函数对状态不同改变进行分发 每一次更新都会进行调用

  • 1.进行事件监听,每一次更新意味这状态发生改变,为dom添加 浏览器原生dom dom事件transitionend 和 animationend这两个dom监听事件 并如果dom发生改变则移除上一次的监听事件并重新监听 监听事件为this.onMotionEnd 函数
  • 2.通过是否发生新状态和对应的组件的状态调用不同的动画 调用函数为this.updateStatus 函数,并添加this.updateActiveStatus的回调函数

updateStatus 函数

updateStatus = (styleFunc, additionalState, event, callback) => {
      const statusStyle = styleFunc
        ? styleFunc(this.getElement(), event)
        : null;

      if (statusStyle === false || this._destroyed) return;  
      // 返回false则停止执行 如果都是fasle,那么classname和style则无法setState更新回到初始位置

      let nextStep;
      if (callback) {
        nextStep = () => {
          this.nextFrame(callback);
        };
      }

      this.setState(
        {
          statusStyle: typeof statusStyle === 'object' ? statusStyle : null,
          newStatus: false,
          ...additionalState,
        },
        nextStep),  // 更新完再执行 并且执行内容本身是异步的  运行此处的时候是属于commit阶段,进行DOM更新创建阶段,此阶段不能暂停,要一气呵成
        // 执行此处的时候 react已经完成render 阶段 属于commit阶段,进行DOM更新创建阶段(同步执行 gui的改变会在此时的同步代码体现)
      ); // Trigger before next frame & after `componentDidMount`
    };

updateStatus 作用:

  • 调用此时状态对应的钩子函数
  • 如果钩子函数返回非false,则进行更新状态,如果是start状态false 则停止执行此函数 此处分几种情况 在active钩子之前和active钩子函数之后 active钩子之前就是执行返回false (只有样式钩子函数不为false 才会进入 updateActiveStatus的回调函数调用进行下一个状态) active之前钩子函数运行返回false会终止运行,active钩子函数运行返回false 会继续运行动画,但是不会恢复初始状态,end钩子函数返回false,则会跳过这个动画的执行下一个动画(end函数执行次数取决了动画的次数,比如transition中不同属性的变化会触发多次end函数)
  • 如果钩子函数返回非false,则进行更新状态,如果是start状态,则进行更新后在一帧调用updateActiveStatus函数,如果是动画结束的end回调,则将组件初始到原生状态

updateActiveStatus

	updateActiveStatus = (styleFunc, currentStatus) => {
      // `setState` use `postMessage` to trigger at the end of frame.
      // Let's use requestAnimationFrame to update new state in next frame.
      // 会在gui渲染完成后执行(不含动画的执行)(下一帧gui渲染之前渲染) !!! 得出结论 ===>> gui渲染和js执行时一个线程,但是动画和过渡和js没有关系,此时js时可以执行的,这一块是单独的
      this.nextFrame(() => {
         // 此处已经commit结束了 (下一帧gui渲染之前渲染)
        const { status } = this.state;
        if (status !== currentStatus) return;

        const { motionDeadline } = this.props;

        this.updateStatus(styleFunc, { statusActive: true });  // 调用updateStatus 并进行更新

        if (motionDeadline > 0) {  // motion动画死线,超过这个,强行结束动画
          this.timer = setTimeout(() => { 
            // 会比gui后执行
            this.onMotionEnd({
              deadline: true,
            });
          }, motionDeadline);
        }
      });
    };

updateActiveStatus 作用:

  • 调用start之后active的钩子函数,通过rAF api进行下一帧调用 通过updateStatus 进行状态更新
  • 可以通过motionDeadline 来 强行调用onMotionEnd 函数

onMotionEnd

onMotionEnd = event => {
      const { status, statusActive } = this.state;
      const { onAppearEnd, onEnterEnd, onLeaveEnd } = this.props;  // onAppearEnd, onEnterEnd, onLeaveEnd  钩子函数
      if (status === STATUS_APPEAR && statusActive) {
        // onAppearEnd, onEnterEnd, onLeaveEnd 非返回false则进行更新回到初始位置
        this.updateStatus(onAppearEnd, { status: STATUS_NONE }, event);
      } else if (status === STATUS_ENTER && statusActive) {
        this.updateStatus函数,由于(onEnterEnd, { status: STATUS_NONE }, event);
      } else if (status === STATUS_LEAVE && statusActive) {
        this.updateStatus(onLeaveEnd, { status: STATUS_NONE }, event);
      }
    };

onMotionEnd 作用:

  • 此函数为动画结束调用函数 transitionend 和 animationend 订阅函数 每一个动画都会调用一次,此函数再次调用updateStatus函数,由于原生订阅事件会同步立即更新state,如果对应的用户end钩子函数第一次调用非返回false,那么state发生变化,后续的用户end函数由于不满足if条件则不会调用,如果end函数返回fasle,那么会跳过此处end函数执行下一次end函数
  • 恢复组件到初始状态
  • 到此基本分析完了CSSMotion的运行过程和代码 visible的频繁切换动画是调用getDerivedStateFromProps 更改状态来改变当前组件状态从而更改classname来实现动画过渡

Animate

  • Animate 组件主要是用于多组件的动画,包括css动画和js动画

大致实现原理 (不考虑兼容问题):

  • 浏览器原生dom api dom事件transitionend 和 animationend这两个dom监听事件(css动画中依赖了此api,和CSSMotion一样)但是由于其需要同时对多组件进行动画处理,无法和CSSMotion来使用一个状态机的方式来实现多个组件同时的动画处理 使用AnimateChild组件对每一个需要进行动画处理的子组件进行包裹一层,用于获取子组件真实dom,从而进行动画处理
  • 使用Animate组件中的currentlyAnimatingKeys(正在动画的组件key),keysToEnter(等待执行进入动画的组件key队列),keysToLeave(等待执行离开动画的组件key队列),state中的children来缓存需要进行展示的组件(比如某一个组件需要消失,不能立即让此组件不展示,而是缓存在state中的children继续展示,等待此子组件的离开动画执行完毕,在更新state中的children展示真正此处需要展示的组件,从而实现动画效果),childrenRefs 来缓存AnimateChild子组件的生命周期函数实例,从而调用AnimateChild子组件的自定义的生命周期函数
  constructor(props) {
    super(props);

    // 存储正在动画的组件的key 进行控制,避免出现问题
    this.currentlyAnimatingKeys = {};
    this.keysToEnter = [];
    this.keysToLeave = [];
    this.state = {
      // 获取children并进行缓存,进行动画切换的控制
      children: toArrayChildren(getChildrenFromProps(props)),
    };
    // ref实例的集合
    this.childrenRefs = {};
  }
  • 先了解每一个子组件的动画执行代码 先查看AnimateChild代码执行

自定义的生命周期钩子函数 componentWillXXX 逻辑基本一致,此处以componentWillLeave 进行举例

 componentWillLeave(done) {
    if (animUtil.isLeaveSupported(this.props)) {
      // 不改变默认配置则进入此逻辑
      this.transition('leave', done);
    } else {
      // always sync, do not interupt with react component life cycle
      // update hidden -> animate hidden ->
      // didUpdate -> animate leave -> unmount (if 			animate is none)
      done();
    }
  }

componentWillLeave在不改变默认配置和transitionName或者animation.leave则走默认的this.transition的逻辑

transition(animationType, finishCallback) {
    // node 寻找 child的真实dom
    const node = ReactDOM.findDOMNode(this);
    const props = this.props;
    const transitionName = props.transitionName;
    const nameIsObj = typeof transitionName === 'object';
    this.stop(); // 清空之前的this.stopper 并执行
    const end = () => {
      this.stopper = null;
      finishCallback();
    };
    // (CssAnimationSupported 或者 不存在animation中的animationType ) 并且 transitionName存在 并且 props对应的 transitionLeave为true
    // 执行 css动画
    if ((isCssAnimationSupported || !props.animation[animationType]) &&
      transitionName && props[transitionMap[animationType]]) {
      const name = nameIsObj ? transitionName[animationType] : `${transitionName}-${animationType}`;
      let activeName = `${name}-active`;
      if (nameIsObj && transitionName[`${animationType}Active`]) {
        activeName = transitionName[`${animationType}Active`];
      }
      // css动画开始走这个逻辑
      // cssAnimate 作用 1) 
      this.stopper = cssAnimate(node, {
        name,
        active: activeName,
      }, end);
    } else {
      // js动画走此逻辑 执行js操作原生dom动画
      this.stopper = props.animation[animationType](node, end);
    }
  }
  • transition 为核心逻辑 作用:1) 调用stop,将上一次此dom node 绑定的相关动画逻辑进行清除 2) 拿到 name 和 activeName 3) 执行cssAnimate 或者 js 动画操作 返回 stop函数 (stop函数为直接终止动画,css为执行rcEndListener 函数,js动画为用户自定义) 4) 将返回函数赋值给 this.stopper供下次清除动画调用
  • 当动画为js动画时,是执行用户自定义的js动画,需要返回stop函数终止当前动画 可以配合velocity-animate第三方库或者自己完成相关动画,此处逻辑由用户完成,不再赘述
  • 当动画为 css动画,使用的为@ant-design/css-animation此antd的库来实现的

@ant-design/css-animation代码探索

const cssAnimation = (node, transitionName, endCallback) => {
  const nameIsObj = typeof transitionName === 'object';
  const className = nameIsObj ? transitionName.name : transitionName;
  const activeClassName = nameIsObj ? transitionName.active : `${transitionName}-active`;
  let end = endCallback;
  let start;
  let active;
  if (endCallback && Object.prototype.toString.call(endCallback) === '[object Object]') {
    end = endCallback.end;
    start = endCallback.start;
    active = endCallback.active;
  }
  // 如果node存在rcEndListener 则执行
  if (node.rcEndListener) {
    node.rcEndListener();
  }
  // node.rcEndListener 进行赋值
  node.rcEndListener = (e) => {
    if (e && e.target !== node) {
      return;
    }
    if (node.rcAnimTimeout) {
      // 此函数有可能比rcAnimTimeout 提前运行,则关闭rcAnimTimeout的运行
      clearTimeout(node.rcAnimTimeout);
      node.rcAnimTimeout = null;
    }
     // 清空 fixBrowserByTimeout 中的 rcAnimTimeout
    clearBrowserBugTimeout(node);
    // remove 对应的classname
    node.classList.remove(className);
    node.classList.remove(activeClassName);
    // 移除对应的dom监听时间
    Event.removeEndEventListener(node, node.rcEndListener);
    // 清空自己
    node.rcEndListener = null;
    // Usually this optional end is used for informing an owner of
    // a leave animation and telling it to remove the child.
    if (end) {
      end(); // 调用回调函数
    }
  };
  // 监听动画结束 --- 动画结束后开始回调
  Event.addEndEventListener(node, node.rcEndListener);
  if (start) {
    start();
  }
  // 添加class追加类名 会引起gui重绘和回流 --
  node.classList.add(className);
  // 渲染完成后后面增加下一个calssname 类似实现下一帧动画
  node.rcAnimTimeout = setTimeout(() => {
    node.rcAnimTimeout = null; // 清除变量
    node.classList.add(activeClassName);
    if (active) {
      active();
    }
    // 防止意外 及时调用 rcEndListener
    fixBrowserByTimeout(node);
  }, 0);

  return {
    stop() {
      if (node.rcEndListener) {
        node.rcEndListener();
      }
    },
  };
};

cssAnimation的作用 1) 拿到classname => name 和 activeName 2) 将之前存在的rcEndListener函数立即执行,清除之前的未执行完的动画 3) 通过transitionend 和 animationend监听执行rcEndListener函数(清空rcAnimTimeout,清空 fixBrowserByTimeout 中的 rcAnimTimeout,remove 对应的classname,移除对应的dom监听时间,清空自己) 4)添加classname 5) 设置立即执行的定时器在下一帧追加activeClassName并定制fixBrowserByTimeout函数 防止意外 及时调用 rcEndListener结束动画 6) 返回调用rcEndListener结束动画stop函数 7) 调用end的回调函数(本质就是Animate中的handleDoneXXX函数)

Animate中的handleDoneLeaving函数为例

handleDoneLeaving = (key) => {
    const props = this.props;
    // 删掉this.currentlyAnimatingKeys 中对应的key
    delete this.currentlyAnimatingKeys[key];
    // if update on exclusive mode, skip check
    if (props.exclusive && props !== this.nextProps) {
      return;
    }
    // 获取props传过来应该最终展示的children
    const currentChildren = toArrayChildren(getChildrenFromProps(props));
    // in case state change is too fast    this.isValidChildByKey(currentChildren, key) 为异常情况,切换过快(猜测)
    // 执行之前再次确定下是否真正需要走leave和end 
    if (this.isValidChildByKey(currentChildren, key)) {  
      this.performEnter(key);
    } else {
      const end = () => {
        // props.transitionLeave || props.animation.leave
        if (animUtil.allowLeaveCallback(props)) {
          // 默认不改变props.transitionLeave 的值,那么会走这里
          // 调用用户自定义的onLeave onEnd 的钩子函数
          props.onLeave(key);
          props.onEnd(key, false);
        }
      };
      // 如果this.state.children 和 props.children 不一致,则更新this.state.children 最终走end()
      if (!isSameChildren(this.state.children,
        currentChildren, props.showProp)) {
        this.setState({
          children: currentChildren,
        }, end);
      } else {
        end();
      }
    }
  }

handleDoneLeaving 函数主要作用为1) this.currentlyAnimatingKeys去除该child的key 并判断是否切换过快而需要走其他动画 2) 正常情况 如果this.state.children 和 props.children 不一致 则进行更新state 3) 正常情况下,调用用户自定义的onLeave onEnd 的钩子函数 4) isValidChildByKey执行之前校验下,是否此时次需要离开的组件需要再次出现,那么不执行2和3,开始执行进入动画(此处的判断为多次动画触发,保证动画都执行,复数触发动画只会执行一次)

componentDidMount 为触发初始需要展示的children,并开启初次出现动画

 // 初次挂载 通过showProp 获取需要初始展示的children,对这些children进行出现动画出现操作
  componentDidMount() {
    const showProp = this.props.showProp;
    let children = this.state.children;
    if (showProp) {
      children = children.filter((child) => {
        return !!child.props[showProp];
      });
    }
    children.forEach((child) => {
      if (child) {
        // 进行出现的批量操作
        this.performAppear(child.key);
      }
    });
  }

初次加载是不会触发componentWillReceiveProps 生命周期,也就是即使child.props[showProp] 为false的子组件也会展示,此处在使用此组件的时候需要注意

componentWillReceiveProps 最重要的生命周期,顺便说一下,此生命周期调用setState 是不会re-render的,而是从随着此处props改变合并更新

componentWillReceiveProps(nextProps) {
    // 基本上触发这个钩子函数的都是children发生变动
    this.nextProps = nextProps;
    // nextChildren 为拿到 更新后的children
    const nextChildren = toArrayChildren(getChildrenFromProps(nextProps));
    const props = this.props;
    // exclusive needs immediate response
    if (props.exclusive) {
      Object.keys(this.currentlyAnimatingKeys).forEach((key) => {
        this.stop(key);
      });
    }
    const showProp = props.showProp;
    const currentlyAnimatingKeys = this.currentlyAnimatingKeys;
    // last props children if exclusive
    // currentChildren 为拿到 更新前的children
    const currentChildren = props.exclusive ?
    // 浅拷贝
      toArrayChildren(getChildrenFromProps(props)) :
      this.state.children;
    // in case destroy in showProp mode
    let newChildren = [];
    if (showProp) {
      currentChildren.forEach((currentChild) => {
        // 当前currentChild对应的下一个nextChild 是否存在
        const nextChild = currentChild && findChildInChildrenByKey(nextChildren, currentChild.key);
        let newChild;
        if ((!nextChild || !nextChild.props[showProp]) && currentChild.props[showProp]) {
          // 当前currentChild对应的下一个nextChild 不存在 或者下一个nextChild不需要展示  并且上个child是展示的
          // =》》》说明此newChild需要进行一个leave的动画操作
          // 拿到这个newChild 或者将上次的currentChild 作为newChild 并赋予展示
          newChild = React.cloneElement(nextChild || currentChild, {
            [showProp]: true,
          });
        } else {
          // 存在并且下一个也展示 或者上一个不展示  // 下一个存在&展示 || 下一个不展示或者不存在(不存在会被过滤掉不加入newChildren)
          // 此时newChild 要分几个情况 1)下一个存在并且下一个也展示 && 上一个不展示 2)上一个不展示 下一个也不展示或者不存在(不存在会被过滤掉不加入newChildren)
          // 3) 上一次展示并且下一个存在并且展示
          newChild = nextChild;
        }
        // 
        if (newChild) {
          newChildren.push(newChild);
        }
      });
// 执行后拿到的 newChildren就是在上一个的基础上下一个进行存在&展示(上一次展示或者不展示)(此处可能有变化但是新的是存在并展示的) 
//  | 下一个不展示(上一个也不展示 无变化) 
//  | 下一个不存在或者不展示(上一个是存在并展示的)=> (将其保持展示状态(用于leave的动画))

// 继续向currentChildren上一个children中不存在的 nextChild进行添加  // 有个问题,newChildren 顺序是乱的
      nextChildren.forEach((nextChild) => {
        if (!nextChild || !findChildInChildrenByKey(currentChildren, nextChild.key)) {
          newChildren.push(nextChild);
        }
      });
    } else {
      newChildren = mergeChildren(
        currentChildren,
        nextChildren
      );
    }

    // need render to avoid update 
    this.setState({
      children: newChildren, 
    });

    // 更新后的children
    nextChildren.forEach((child) => {
      const key = child && child.key;
      // 排除正在动画的currentlyAnimatingKeys的child -->>>
      if (child && currentlyAnimatingKeys[key]) {
        return;
      }
      // 对需要进行动画的child进行分类
      // hasPrev --- 代表之前存在同一个child(可能状态改变)
      const hasPrev = child && findChildInChildrenByKey(currentChildren, key);
      if (showProp) {
        const showInNext = child.props[showProp];
        if (hasPrev) {
          // showInNow查找之前是否展示
          const showInNow = findShownChildInChildrenByKey(currentChildren, key, showProp);
          // 之前不展示,现在展示,那么放入 keysToEnter 进入动画的等待队列
          if (!showInNow && showInNext) {
            this.keysToEnter.push(key);
          }
          // 之前不存在,但是此时展示,那么也推入 keysToEnter 进入动画的等待队列
        } else if (showInNext) {
          this.keysToEnter.push(key);
        }
      } else if (!hasPrev) {
        this.keysToEnter.push(key);
      }
    });

    currentChildren.forEach((child) => {
      const key = child && child.key;
      // 排除正在动画的currentlyAnimatingKeys的child -->>>
      if (child && currentlyAnimatingKeys[key]) {
        return;
      }
      // hasNext 有下一个child
      const hasNext = child && findChildInChildrenByKey(nextChildren, key);
      if (showProp) {
        // showInNow 上一个是否展示
        const showInNow = child.props[showProp];
        if (hasNext) {
          const showInNext = findShownChildInChildrenByKey(nextChildren, key, showProp);
          // 有上一个并且下一个存在但是不展示了,推入 keysToLeave 离开动画的等待队列
          if (!showInNext && showInNow) {
            this.keysToLeave.push(key);
          }
          // 有上一个并且下一个不存在了,推入 keysToLeave 离开动画的等待队列
        } else if (showInNow) {
          this.keysToLeave.push(key);
        }
      } else if (!hasNext) {
        this.keysToLeave.push(key);
      }
    });
  }

此生命周期作用为 1) 更新state中的children 拿到应该展示和应该走离开动画的child 2) 筛选出需要走离开动画或者需要走进入动画的child 放入对应的准备的队列 (排除正在动画的子组件,重复动画交由结束handleDonexxx来进行判断处理)

小结

基本上rc-animate中的Animate和CSSMotion源码基本过了一边,虽说代码不多,但是状态机和react生命周期相结合,及子组件的处理都是需要理解和学习的,此处antd的rc-animate 组件源码探究到此为止,可能有些地方有误,欢迎指正,如需转载请联系我。