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 组件源码探究到此为止,可能有些地方有误,欢迎指正,如需转载请联系我。