rc-animate 同 react-transition-group,都用于实现元素的移入移出动效。

rc-animate 提供两种动效处理方式,Animate 组件用于处理多元素的移入移出动效,包含 css 和 js 动效;

CSSMotion 用于处理单元素的动效,只能处理 css 动效。

本文第一部分将介绍 Animate 组件;
第二部分将介绍 CSSMotion 组件。

Animate

rc-animate - 图1
上图即为多元素移入移出动效的视图特征:

  1. child1, child3 元素初始化即显现在视图中,该过程将呈现 appear 动效。
  2. child2 元素在下一个渲染过程中将移入视图,该过程将呈现 enter 动效。
  3. child3 元素在下一个渲染过程中将移出视图,该过程将呈现 leave 动效。

因为动效有时延,我们需要致力于解决如下问题:

  1. 若 child2 在 child1 元素的 appear 动效还未完成时移入,child1 需要继续执行 appear 动效。对此,我们可以用内部状态记录动效执行中的元素,从而不打断动效的执行过程。若 child2 元素的 enter 动效执行期间,视图中再添加 child4, child5,也作相同处理。
  2. 若 child3 即将移出视图,先将 child3 保存在内部状态中,等 leave 动效执行完成后,再移除 child3。

在 rc-animate 中,上述两个状态表现为 Animate 组件的 currentlyAnimatingKeys 实例属性和 state.children。在 Animate 的 componentWillReceiveProps 生命周期中,将通过 currentlyAnimatingKeys 判断动效是否还在执行阶段,以便在 state.children 中保留该元素。当然,在 componentWillReceiveProps 生命周期,主要的处理逻辑为:根据元素移入移出视图情况更新 state.children,并使用 keysToEnter, keysToLeave 属性记录哪些元素需要执行 enter 或者 leave 动效。对问题 2 的解答也就是在动效完成后更新 state.children。
我们可以将 Animate 看成是动效调节器。不计 enter 动效,Animate 首先将在 componentWillReceiveProps 生命周期计算 child 元素需要执行何种动效,然后在 componentDidUpdate 生命周期执行该动效。实际执行的动效与 child 元素挂钩,那样就可以支持不同元素的展示动效多样化。在 rc-animate 中,AnimateChild 就可以理解成不同元素的动效驱动器。通过 props 定义 child 待执行的动效,由 AnimateChild 提供 componentWillAppear, componentWillEnter, componentWillLeave 方法执行具体的动效,且与 Animate 完成对接。AnimateChild 和 Animate 对接表现为,在 Animate 的 componentDidUpdate 生命周期,最终将调用 AnimateChild 的上述方法。

下图是 rc-animate 动效执行的时序图。
rc-animate - 图2

Animate

作为动效调节器,Animate 组件用于控制全局层面的动效特征。Animate 以 currentlyAnimatingKeys 实例属性记录正在执行动效的元素;keysToEnter, keysToLeave 实例属性记录待显示隐藏的元素;并在动效执行前后通过更新 state.children 的方式控制子元素的渲染状况;在此过程中,将在动效完成后的回调中执行全局意义的绑定函数,如 props.onAppear, props.onEnter, prop.onLeave, props.onEnd 方法。

| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
| class Animate extends React.Component {
/*
didMount 生命周期执行子元素的 appear 动效
*/
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 生命周期感知子元素的显示状态变更
1. 更新 state.children 显示子元素以执行 enter, leave 动效
2. 以 this.keysToEnter, this.keysToLeave 收集待执行 enter, leave 动效的元素
以便在 componentDidUpdate 实际执行动效;在动效执行完成后,触发回调显示或移除视图上的子元素
/
componentWillReceiveProps(nextProps) {
this.nextProps = nextProps;
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
const currentChildren = props.exclusive ?
toArrayChildren(getChildrenFromProps(props)) :
this.state.children;
// in case destroy in showProp mode
let newChildren = [];
if (showProp) {
currentChildren.forEach((currentChild) => {
const nextChild = currentChild && findChildInChildrenByKey(nextChildren, currentChild.key);
let newChild;
if ((!nextChild || !nextChild.props[showProp]) && currentChild.props[showProp]) {
newChild = React.cloneElement(nextChild || currentChild, {
[showProp]: true,
});
} else {
newChild = nextChild;
}
if (newChild) {
newChildren.push(newChild);
}
});
nextChildren.forEach((nextChild) => {
if (!nextChild || !findChildInChildrenByKey(currentChildren, nextChild.key)) {
newChildren.push(nextChild);
}
});
} else {
newChildren = mergeChildren(
currentChildren,
nextChildren
);
}

  1. // need render to avoid update<br /> this.setState({<br /> children: newChildren,<br /> });
  2. nextChildren.forEach((child) => {<br /> const key = child && child.key;<br /> if (child && currentlyAnimatingKeys[key]) {<br /> return;<br /> }<br /> const hasPrev = child && findChildInChildrenByKey(currentChildren, key);<br /> if (showProp) {<br /> const showInNext = child.props[showProp];<br /> if (hasPrev) {<br /> const showInNow = findShownChildInChildrenByKey(currentChildren, key, showProp);<br /> if (!showInNow && showInNext) {<br /> this.keysToEnter.push(key);<br /> }<br /> } else if (showInNext) {<br /> this.keysToEnter.push(key);<br /> }<br /> } else if (!hasPrev) {<br /> this.keysToEnter.push(key);<br /> }<br /> });
  3. currentChildren.forEach((child) => {<br /> const key = child && child.key;<br /> if (child && currentlyAnimatingKeys[key]) {<br /> return;<br /> }<br /> const hasNext = child && findChildInChildrenByKey(nextChildren, key);<br /> if (showProp) {<br /> const showInNow = child.props[showProp];<br /> if (hasNext) {<br /> const showInNext = findShownChildInChildrenByKey(nextChildren, key, showProp);<br /> if (!showInNext && showInNow) {<br /> this.keysToLeave.push(key);<br /> }<br /> } else if (showInNow) {<br /> this.keysToLeave.push(key);<br /> }<br /> } else if (!hasNext) {<br /> this.keysToLeave.push(key);<br /> }<br /> });<br /> }

/*
componentDidUpdate 生命周期执行子元素的 enter, leave 动效
*/
componentDidUpdate() {
const keysToEnter = this.keysToEnter;
this.keysToEnter = [];
keysToEnter.forEach(this.performEnter);
const keysToLeave = this.keysToLeave;
this.keysToLeave = [];
keysToLeave.forEach(this.performLeave);
}

/*
实际调用 AnimateChild 组件的 componentWillEnter 方法执行动效
*/
performEnter = (key) => {
// may already remove by exclusive
if (this.childrenRefs[key]) {
this.currentlyAnimatingKeys[key] = true;
this.childrenRefs[key].componentWillEnter(
this.handleDoneAdding.bind(this, key, ‘enter’)
);
}
}

performAppear = (key) => {
if (this.childrenRefs[key]) {
this.currentlyAnimatingKeys[key] = true;
this.childrenRefs[key].componentWillAppear(
this.handleDoneAdding.bind(this, key, ‘appear’)
);
}
}

/*
enter, appear 动效执行完成后的回调
*/
handleDoneAdding = (key, type) => {
const props = this.props;
delete this.currentlyAnimatingKeys[key];
// if update on exclusive mode, skip check
if (props.exclusive && props !== this.nextProps) {
return;
}
const currentChildren = toArrayChildren(getChildrenFromProps(props));
if (!this.isValidChildByKey(currentChildren, key)) {
// exclusive will not need this
this.performLeave(key);
} else if (type === ‘appear’) {
if (animUtil.allowAppearCallback(props)) {
props.onAppear(key);
props.onEnd(key, true);
}
} else if (animUtil.allowEnterCallback(props)) {
props.onEnter(key);
props.onEnd(key, true);
}
}

performLeave = (key) => {
// may already remove by exclusive
if (this.childrenRefs[key]) {
this.currentlyAnimatingKeys[key] = true;
this.childrenRefs[key].componentWillLeave(this.handleDoneLeaving.bind(this, key));
}
}

handleDoneLeaving = (key) => {
const props = this.props;
delete this.currentlyAnimatingKeys[key];
// if update on exclusive mode, skip check
if (props.exclusive && props !== this.nextProps) {
return;
}
const currentChildren = toArrayChildren(getChildrenFromProps(props));
// in case state change is too fast
if (this.isValidChildByKey(currentChildren, key)) {
this.performEnter(key);
} else {
const end = () => {
if (animUtil.allowLeaveCallback(props)) {
props.onLeave(key);
props.onEnd(key, false);
}
};
if (!isSameChildren(this.state.children,
currentChildren, props.showProp)) {
this.setState({
children: currentChildren,
}, end);
} else {
end();
}
}
}

render() {
const props = this.props;
this.nextProps = props;
const stateChildren = this.state.children;
let children = null;
if (stateChildren) {
children = stateChildren.map((child) => {
if (child === null || child === undefined) {
return child;
}
if (!child.key) {
throw new Error(‘must set key for children’);
}
return (
key={child.key}
ref={node => { this.childrenRefs[child.key] = node }}
animation={props.animation}
transitionName={props.transitionName}
transitionEnter={props.transitionEnter}
transitionAppear={props.transitionAppear}
transitionLeave={props.transitionLeave}
>
{child}

);
});
}
const Component = props.component;
if (Component) {
let passedProps = props;
if (typeof Component === ‘string’) {
passedProps = {
className: props.className,
style: props.style,
…props.componentProps,
};
}
return {children};
}
return children[0] || null;
}
}
| | :—- | :—- |

AnimateChild

如上文所说,AnimateChild 组件用于实现元素的具体动效。支持的动效包含 css3 动效和 js 动效。其中,css3 动效通过 props.transitionName, props.transitionEnter, props.transitionAppear, props.transitionLeave 属性加以配置;js 动效通过 props.animation 动效方法集 { appear?, enter?, leave? } 加以配置,动效可使用 jQuery 操作节点的方式实现。AnimateChild 组件接受的 props 属性均由用户端通过 Animate 动效调节器注入。在 AnimateChild 组件中,驱动动效的方法直接表现为 transition 实例方法,而 stop 实例方法用于中止动效。
通过源码,我们可以发现,当用户以字符串配置 props.transitionName 属性时,执行 appear 动效的节点将获得的样式类为 ${transitionName}-appear, ${transitionName}-appear-active 形式。当以对象配置 props.transitionName 属性时,节点获得的样式类前缀 ${transitionName}-appear 将被替换为 ${transitionName.appear}。props.transitionEnter, props.transitionAppear, props.transitionLeave 属性用于启动或关闭动效。在 css 动效的优先级后,节点将执行 props.animate.appear 动效。以下是动效的实现源码,AnimateChild 组件的 componentWillEnter, componentWillAppear, componentWillLeave 实例方法均基于 transition 方法实现。

| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| class AnimateChild extends React.Component {
/*
实际执行 css 或 js 动效
@param {string} animationType 动效类型,值为 ‘appear’, ‘enter’, ‘leave’ 中的一个
@param {function} finishCallback 回调函数,在动效完成后执行
*/
transition(animationType, finishCallback) {
const node = ReactDOM.findDOMNode(this);
const props = this.props;
const transitionName = props.transitionName;
const nameIsObj = typeof transitionName === ‘object’;
this.stop();
const end = () => {
this.stopper = null;
finishCallback();
};
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];
}
this.stopper = cssAnimate(node, {
name,
active: activeName,
}, end);
} else {
this.stopper = props.animationanimationType;
}
}

/*
关闭动效
*/
stop() {
const stopper = this.stopper;
if (stopper) {
this.stopper = null;
stopper.stop();
}
}
}
| | :—- | :—- |

css 动效

rc-animate 库的 css 动效通过 css-animation 库实现,其直接构成 AnimateChild 组件中使用的 cssAnimate 函数。
css-animation 库先行侦测浏览器环境支持的 transition, animate 事件(webkit 等前缀)。
若支持这两类事件,直接对节点绑定这两类事件的处理函数;
若不支持,使用 setTimeout 构造虚拟事件。
以下是 css-animation 库侦测浏览器支持事件的源码实现:

| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
| const START_EVENT_NAME_MAP = {
transitionstart: {
transition: ‘transitionstart’,
WebkitTransition: ‘webkitTransitionStart’,
MozTransition: ‘mozTransitionStart’,
OTransition: ‘oTransitionStart’,
msTransition: ‘MSTransitionStart’,
},

animationstart: {
animation: ‘animationstart’,
WebkitAnimation: ‘webkitAnimationStart’,
MozAnimation: ‘mozAnimationStart’,
OAnimation: ‘oAnimationStart’,
msAnimation: ‘MSAnimationStart’,
},
};

const END_EVENT_NAME_MAP = {
transitionend: {
transition: ‘transitionend’,
WebkitTransition: ‘webkitTransitionEnd’,
MozTransition: ‘mozTransitionEnd’,
OTransition: ‘oTransitionEnd’,
msTransition: ‘MSTransitionEnd’,
},

animationend: {
animation: ‘animationend’,
WebkitAnimation: ‘webkitAnimationEnd’,
MozAnimation: ‘mozAnimationEnd’,
OAnimation: ‘oAnimationEnd’,
msAnimation: ‘MSAnimationEnd’,
},
};

const startEvents = [];
const endEvents = [];

function detectEvents() {
const testEl = document.createElement(‘div’);
const style = testEl.style;

if (!(‘AnimationEvent’ in window)) {
delete START_EVENT_NAME_MAP.animationstart.animation;
delete END_EVENT_NAME_MAP.animationend.animation;
}

if (!(‘TransitionEvent’ in window)) {
delete START_EVENT_NAME_MAP.transitionstart.transition;
delete END_EVENT_NAME_MAP.transitionend.transition;
}

function process(EVENT_NAME_MAP, events) {
// 遍历 transition, animate 类目
for (const baseEventName in EVENT_NAME_MAP) {
if (EVENT_NAME_MAP.hasOwnProperty(baseEventName)) {
const baseEvents = EVENT_NAME_MAP[baseEventName];
// 遍历特定浏览器的事件前缀名,将支持的事件存入 startEvents, endEvents 数组中
for (const styleName in baseEvents) {
if (styleName in style) {
events.push(baseEvents[styleName]);
break;
}
}
}
}
}

process(START_EVENT_NAME_MAP, startEvents);
process(END_EVENT_NAME_MAP, endEvents);
}

if (typeof window !== ‘undefined’ && typeof document !== ‘undefined’) {
detectEvents();
}
| | :—- | :—- |

同常见的 css 动效实现,css-animation 库首先为节点添加 transitionName 样式类;30 ms 后又为节点添加 ${transitionName}-active 样式类;并在动效执行完成后,通过绑定事件为节点移除前两个样式类;若事件无效,css-animation 将使用定时器移除样式类。以下是这段逻辑的实现代码:

| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
| 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;
// 函数 classes 由 component-classes 提供,针对多平台处理样式类列表
const nodeClasses = classes(node);

if (endCallback && Object.prototype.toString.call(endCallback) === ‘[object Object]’) {
end = endCallback.end;
start = endCallback.start;
active = endCallback.active;
}

if (node.rcEndListener) {
node.rcEndListener();
}

// 回调中移除样式类
node.rcEndListener = (e) => {
if (e && e.target !== node) {
return;
}

  1. if (node.rcAnimTimeout) {<br /> clearTimeout(node.rcAnimTimeout);<br /> node.rcAnimTimeout = null;<br /> }
  2. // clearBrowserBugTimeout 函数用于清除 fixBrowserByTimeout 函数设置的定时器<br /> clearBrowserBugTimeout(node);
  3. nodeClasses.remove(className);<br /> nodeClasses.remove(activeClassName);
  4. Event.removeEndEventListener(node, node.rcEndListener);<br /> node.rcEndListener = null;
  5. if (end) {<br /> end();<br /> }<br /> };

Event.addEndEventListener(node, node.rcEndListener);

if (start) {
start();
}

// 添加样式类,执行动效
nodeClasses.add(className);

node.rcAnimTimeout = setTimeout(() => {
node.rcAnimTimeout = null;
nodeClasses.add(activeClassName);
if (active) {
setTimeout(active, 0);
}

  1. // fixBrowserByTimeout 函数使用定时器执行 node.rcEndListener 方法,以清除动效相关的样式类<br /> // 常态下使用绑定事件清除样式类,fixBrowserByTimeout 用于针对浏览器在绑定事件上的 bug<br /> fixBrowserByTimeout(node);<br /> }, 30);

return {
stop() {
if (node.rcEndListener) {
node.rcEndListener();
}
},
};
};
| | :—- | :—- |

CSSMotion

CSSMotion 组件实现 css 动效的处理逻辑如同上文所说,
即在元素显现时添加 ${transitionName}-appear, ${transitionName}-appear-active 样式类,在动效完成后,再移除这两个样式类;enter, leave 动效同此。在 CSSMotion 组件中,变量 props.transitionNam
e 实际表现为配置项 props.motionName。

在处理样式类变更逻辑上,CSSMotion 组件内部使用 state.status 状态表示动效的类型,state.statusActive 状态表示动效是否在执行阶段,state.newStatus 状态表示是否需要执行新的动效,state.statusStyle 状态表示动效执行期间附加的样式。父子组件交互层面,CSSMotion 组件通过接受 props 属性影响内部子组件的显示动效:初始化 props.visible 和 props.motionAppear 均为真值时执行 appear 动效;当 props.visible 由 false 转为 true 且 props.motionEnter 为真值时执行 enter 动效;当 props.visible 由 true 转为 false 且 props.motionLeave 为真值时执行 leave 动效。state.statusStyle 样式由 props.onAppearStart, props.onEnterStart, props.onLeaveStart, props.onAppearActive, props.onEnterActive, props.onLeaveActive, props.onAppearEnd, props.onEnterEnd, props.onLeaveEnd 监听函数计算获得,顾名思义,这些函数的执行时机分别为动效起始、执行和结束阶段。state.status, state.statusActive, state.statusStyle 最终将影响函数式子组件获得的 props 参数。

CSSMotion 组件的处理流程为:

  1. 通过 getDerivedStateFromProps 静态方法计算 state.status,划定元素将执行何种动效;同时将 state.newStatus 置为真值,表示元素将执行新的动效;且将 state.statusActive 置为否值,表示动效处于等待执行状态。
  2. componentDidMount, componentDidUpdate 生命周期调用 onDomUpdate 实例方法,为元素绑定动效完成后的回调函数,并启动动效的实际处理逻辑。
  3. 动效的实际处理逻辑通过 updateStatus 实例方法启动,以 appear 动效为例,首先调用 props.onAppearStart 方法计算元素的样式,以更新注入子组件的 state.statusStyle,将 state.newStatus 置为否值;其次在回调中调用 updateActiveStatus 实例方法,在下一帧渲染过程调用 props.onAppearActive 方法更新 state.statusStyle 样式,且将 state.statusActive 置为真值,以启动 css 动效;当动效执行完成后,通过绑定函数执行 onMotionEnd 实例方法,即通过 props.onAppearEnd 计算 state.statusStyle 样式,且重置 state.status 状态,以指示子组件动效已执行完成。
  4. componentWillUnmount 生命周期解绑监听函数。

以下 CSSMotion 组件的源码:

| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
| class CSSMotion extends React.Component {
/*
通过更新 state.status 状态指定子组件需要执行哪种动效
*/
static getDerivedStateFromProps(props, { prevProps }) {
if (!isSupportTransition(props)) return {};

  1. const {<br /> visible, motionAppear, motionEnter, motionLeave,<br /> motionLeaveImmediately,<br /> } = props;<br /> const newState = {<br /> prevProps: props,<br /> };
  2. // Appear<br /> if (!prevProps && visible && motionAppear) {<br /> newState.status = STATUS_APPEAR;<br /> newState.statusActive = false;<br /> newState.newStatus = true;<br /> }
  3. // Enter<br /> if (prevProps && !prevProps.visible && visible && motionEnter) {<br /> newState.status = STATUS_ENTER;<br /> newState.statusActive = false;<br /> newState.newStatus = true;<br /> }
  4. // Leave<br /> if (<br /> (prevProps && prevProps.visible && !visible && motionLeave) &#124;&#124;<br /> (!prevProps && motionLeaveImmediately && !visible && motionLeave)<br /> ) {<br /> newState.status = STATUS_LEAVE;<br /> newState.statusActive = false;<br /> newState.newStatus = true;<br /> }
  5. return newState;<br /> };<br /> <br /> /**<br /> * componentDidMount, componentDidUpdate 生命周期驱动动效的实际处理逻辑<br /> */<br /> onDomUpdate = () => {<br /> const { status, newStatus } = this.state;<br /> const {<br /> onAppearStart, onEnterStart, onLeaveStart,<br /> onAppearActive, onEnterActive, onLeaveActive,<br /> motionAppear, motionEnter, motionLeave,<br /> } = this.props;
  6. if (!isSupportTransition(this.props)) {<br /> return;<br /> }
  7. // 绑定 onMotionEnd 回调函数;动效完成后,执行 onMotionEnd 实例方法<br /> const $ele = ReactDOM.findDOMNode(this);<br /> if (this.$ele !== $ele) {<br /> this.removeEventListener(this.$ele);<br /> this.addEventListener($ele);<br /> this.$ele = $ele;<br /> }
  8. // 首次执行 updateStatus 实例方法将通过 props.onAppearStart 计算注入子元素的样式<br /> // 其次执行 updateActiveStatus 实例方法将 state.statusActive 以启动动效<br /> if (newStatus && status === STATUS_APPEAR && motionAppear) {<br /> this.updateStatus(onAppearStart, null, null, () => {<br /> this.updateActiveStatus(onAppearActive, STATUS_APPEAR);<br /> });<br /> } else if (newStatus && status === STATUS_ENTER && motionEnter) {<br /> this.updateStatus(onEnterStart, null, null, () => {<br /> this.updateActiveStatus(onEnterActive, STATUS_ENTER);<br /> });<br /> } else if (newStatus && status === STATUS_LEAVE && motionLeave) {<br /> this.updateStatus(onLeaveStart, null, null, () => {<br /> this.updateActiveStatus(onLeaveActive, STATUS_LEAVE);<br /> });<br /> }<br /> };

/*
调用 props.onAppearStart 等方法计算注入子元素的样式,并在下一帧渲染时调用 callback
*/
updateStatus = (styleFunc, additionalState, event, callback) => {
const statusStyle = styleFunc ? styleFunc(ReactDOM.findDOMNode(this), event) : null;

  1. if (statusStyle === false &#124;&#124; this._destroyed) return;
  2. let nextStep;<br /> if (callback) {<br /> nextStep = () => {<br /> this.nextFrame(callback);<br /> };<br /> }
  3. this.setState({<br /> statusStyle: typeof statusStyle === 'object' ? statusStyle : null,<br /> newStatus: false,<br /> ...additionalState,<br /> }, nextStep); // Trigger before next frame & after `componentDidMount`<br /> };

/*
将 state.statusActive 置为真值,以启动 css 动效
*/
updateActiveStatus = (styleFunc, currentStatus) => {
// this.nextFrame 使用 raf 库实现下一帧渲染
this.nextFrame(() => {
const { status } = this.state;
if (status !== currentStatus) return;

  1. this.updateStatus(styleFunc, { statusActive: true });<br /> });<br /> };

/*
动效执行完成后回调,更新 state.status 以清除注入子元素的样式
*/
onMotionEnd = (event) => {
const { status, statusActive } = this.state;
const { onAppearEnd, onEnterEnd, onLeaveEnd } = this.props;
if (status === STATUS_APPEAR && statusActive) {
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);
}
};

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

  1. if (!children) return null;
  2. if (status === STATUS_NONE &#124;&#124; !isSupportTransition(this.props)) {<br /> if (visible) {<br /> return children({});<br /> } else if (!removeOnLeave) {<br /> return children({ className: leavedClassName });<br /> }
  3. return null;<br /> }
  4. // 子组件以函数式组价形式接受样式类和样式<br /> return children({<br /> className: classNames({<br /> [getTransitionName(motionName, status)]: status !== STATUS_NONE,<br /> [getTransitionName(motionName, `${status}-active`)]: status !== STATUS_NONE && statusActive,<br /> [motionName]: typeof motionName === 'string',<br /> }),<br /> style: statusStyle,<br /> });<br /> }<br />}<br /> |

| :—- | :—- |