React Native源码分析——Virtual DOM

本文基于0.58.5分析React Native Reconciliation过程

Components、Elements和Instances

讲Virtual DOM之前,先讲下React Native几个核心概念和这样设计的目的。在面向对象的UI开发时,要渲染一个UI时,都要自己创建UI对象,并且管理对象引用,例如iOS上要渲染一个UIView:

1
2
3
4
UIView *view= [UIView new];
UILabel *label= [UILabel new];
label.text = @"test";
[view addSubview:label];

这种设计模式带来的问题是开发者必须自己创建、更新UI对象,当UI复杂时,维护成本会急剧增加。
React使用一种非常巧妙的设计模式来解决上面问题,UI开发时,只需要描述UI界面,引擎会根据描述自动创建具体的实例,在更新时也只需更新UI界面描述。这样开发者就从复杂的UI对象创建、更新中解放出来,开发者只需要关注UI长怎样和核心逻辑,React帮你搞定对象创建和维护,React通过Components、Elements和Instances来实现这种模式。

Elements

Element是用来描述UI的js对象,Element只有type和props两个属性,Element创建成本很低,一旦创建就不可变,Component更新到时候会创建新的Element。Element可以嵌套,React会递归解析Element直到叶子结点,这样就得到一颗Element树,这颗树就是Virtual DOM树,React通过diff等算法后把Virtual DOM渲染到屏幕,渲染过程做了很多优化。可以通过render()或者其他方法返回Element,例如:

1
2
3
4
5
6
7
render() {
return(
<View style = {{height:200,backgroundColor:'#999999'}}>
<Text> test </Text>
</View>
)
}

上述JSX语法最终会转换成以下Element:

1
2
3
4
5
6
7
8
9
10
11
12
{
type:View,
props: {
style:{height:200,backgroundColor:'#999999'},
children: {
type:Text,
props: {
children:'test'
}
}
}
}

Components

Component是生成Element的对象,可以是个class,也可以是简单的方法。当Component是class的时候,可以存储state和其他属性,实现复杂的逻辑;当Component是方法的时候,是不可变的Component,相当于只有render()方法的class Component。

Instances

Instance就是Component 实例,React Native开发过程不用自己管理Instance,React引擎自动创建并维护Instance,具体创建逻辑下文详细介绍。

Virtual DOM

通常所说的Virtual DOM是指相对于Real DOM的element树,Virtual DOM是最初React版本的说法,最开始React只是用在前端,引入Virtual DOM的概念是为了提升UI渲染性能,在UI变化的时候可以先比较Virtual DOM,只更新有变化的Real DOM。

Reconciliation

React之所以有这么好的渲染性能,主要是因为在UI变化的时候可以先比较Virtual DOM,只更新有变化的Real DOM,整个更新过程叫Reconciliation

Fiber

React 16重构了Reconciliation 实现,新框架叫Fiber,Facebook团结花了两年时间实现Fiber,核心优化就是使用Vitual Stack的概念。Fiber之前更新UI的时候是通过同步递归的方式遍历Virtual DOM树,整个过程是同步的,并且在遍历结束之前无法中断,这样在动画的时候就可能导致卡顿。Fiber使用Vitual Stack的概念,把同步递归操作分解成一个个异步、可中断的操作单元,从而解决卡顿问题,并且随时可以取消不需要的操作。

UI更新

React Native UI更新主要可以分为以下两个阶段:

  • Render
  • Commit

Render过程计算新的element树,render()方法在这个阶段调用的;Commit过程调用diff算法,更新实际发生变化的UI。
Render 阶段核心方法调用顺序:

  • componentWillReceiveProps
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render()
  • reconcileChildren()

Commit 阶段核心方法调用顺序:

  • getSnapshotBeforeUpdate
  • diff
  • updateView
  • componentDidUpdate

componentWillReceiveProps和componentWillUpdate已经废弃,不推荐使用了,使用getDerivedStateFromProps和getSnapshotBeforeUpdate代替
updateView方法调用RCTUIManager.updateView更新Native View。

Reconciliation源码分析

接下来以setState方法为切入点分析,分析React Native Reconciliation过程,主要分析UI更新过程,并不深入Fiber细节。

setState

1
2
3
4
5
6
7
8
9
  _callback() {
console.log('callback');
}

_onPress1() {
this.setState(previousState => (
{ value: previousState.value+1}
), this._callback.bind(this));
}

上面代码是React Native上更新UI的最常用方法,我们知道setState是异步调用的,但state是什么时机更新?callback又什么时机调用呢?又是怎么触发Virtual DOM树和UI更新的呢?

1
2
3
4
5
//react.development.js:333
Component.prototype.setState = function (partialState, callback) {
!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//ReactNativeRender-dev.js:8456
var classComponentUpdater = {
isMounted: isMounted,
enqueueSetState: function(inst, payload, callback) {
var fiber = get$1(inst);
var currentTime = requestCurrentTime();
var expirationTime = computeExpirationForFiber(currentTime, fiber);

var update = createUpdate(expirationTime);
update.payload = payload;
if (callback !== undefined && callback !== null) {
{
warnOnInvalidCallback(callback, "setState");
}
update.callback = callback;
}

flushPassiveEffects();
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
}
...
}

React可以用在Web、Node、React Native,底层updater指向具体实现,React Native上就是classComponentUpdater。
可以看到setState最终会创建一个update结构,其中payload就是更新state的匿名方法,然后插入队列,payload和callback将在后面异步执行。

element树更新

前文说过Render过程计算新的element树,render()方法在这个阶段调用的,先看一下函数调用栈:

Render阶段函数调用栈

通过函数名可以猜测更新过程会把异步处理批量更新,这样可以提高性能,接下来分析Render过程核心方法。

performWorkOnRoot

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
//ReactNativeRender-dev.js:17168
function performWorkOnRoot(root, expirationTime, isYieldy) {
// Check if this is async work or sync/expired work.
if (!isYieldy) {
// Flush work without yielding.
// TODO: Non-yieldy work does not necessarily imply expired work. A renderer
// may want to perform some work without yielding, but also without
// requiring the root to complete (by triggering placeholders).

var finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
// If this root previously suspended, clear its existing timeout, since
// we're about to try rendering again.
var timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
renderRoot(root, isYieldy);
finishedWork = root.finishedWork;
if (finishedWork !== null) {
// We've completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
}
}
...
}

performWorkOnRoot是UI更新的入口方法,React Native上isYieldy直接传的false。每次更新的时候都会从最顶端的节点开始计算新的element树,不管是哪个节点调的setState,但没变化的节点并不会重新计算,而是直接重用。但如果父节点发生变化,则所有字节的都会进行重新计算,而不管子节点是否变化,除非子节点shouldComponentUpdate返回false,或者子节点是PureReactComponent。
renderRoot就是Render阶段入口方法,completeRoot则是Commit阶段入口方法。

workLoop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//ReactNativeRender-dev.js:16111
function workLoop(isYieldy) {
if (!isYieldy) {
// Flush work without yielding
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until there's a higher priority event
while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}

workLoop方法就是遍历整颗element树,React 16重构了Reconciliation 实现,新框架叫Fiber,Fiber使用Vitual Stack的概念,把同步递归操作分解成一个个异步、可中断的操作单元,每个操作单元就是一个节点计算过程。
performUnitOfWork就是具体节点计算,每次执行完会通过深度优先返回下一个需要执行的节点,这样就可以遍历整个节点树了。

performUnitOfWork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//ReactNativeRender-dev.js:16049
function performUnitOfWork(workInProgress) {
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
var current$$1 = workInProgress.alternate;
...
var next = void 0;
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
...
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner$2.current = null;
return next;
}

performUnitOfWork主要就是调用beginWork方法,然后更新props。

beginWork
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
//ReactNativeRender-dev.js:12601
function beginWork(current$$1, workInProgress, renderExpirationTime) {
var updateExpirationTime = workInProgress.expirationTime;

if (current$$1 !== null) {
var oldProps = current$$1.memoizedProps;
var newProps = workInProgress.pendingProps;
if (
oldProps === newProps &&
!hasContextChanged() &&
updateExpirationTime < renderExpirationTime
) {
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
switch (workInProgress.tag) {
...
case ClassComponent: {
var Component = workInProgress.type;
if (isContextProvider(Component)) {
pushContextProvider(workInProgress);
}
break;
}
...
return bailoutOnAlreadyFinishedWork(
current$$1,
workInProgress,
renderExpirationTime
);
}
}

// Before entering the begin phase, clear the expiration time.
workInProgress.expirationTime = NoWork;

switch (workInProgress.tag) {
...
case ClassComponent: {
var _Component2 = workInProgress.type;
var _unresolvedProps = workInProgress.pendingProps;
var _resolvedProps =
workInProgress.elementType === _Component2
? _unresolvedProps
: resolveDefaultProps(_Component2, _unresolvedProps);
return updateClassComponent(
current$$1,
workInProgress,
_Component2,
_resolvedProps,
renderExpirationTime
);
}
...
}
}

可以看到beginWork主要是两个分支,每个分支都是一个很大switch case语句,第一个分支处理节点没变化的情况,这个时候不会进行计算,第二个分支处理节点发生变化的情况。每个switch case处理不同类型的节点,这里只分析ClassComponent类型。
最终会调用updateClassComponent方法更新发生变化的节点。

updateClassComponent

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
//ReactNativeRender-dev.js:11457
function updateClassComponent(
current$$1,
workInProgress,
Component,
nextProps,
renderExpirationTime
) {
{
...
var instance = workInProgress.stateNode;
var shouldUpdate = void 0;
if (instance === null) {
if (current$$1 !== null) {
// An class component without an instance only mounts if it suspended
// inside a non- concurrent tree, in an inconsistent state. We want to
// tree it like a new mount, even though an empty version of it already
// committed. Disconnect the alternate pointers.
current$$1.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.effectTag |= Placement;
}
// In the initial pass we might need to construct the instance.
constructClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime
);
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime
);
shouldUpdate = true;
} else if (current$$1 === null) {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime
);
} else {
shouldUpdate = updateClassInstance(
current$$1,
workInProgress,
Component,
nextProps,
renderExpirationTime
);
}
var nextUnitOfWork = finishClassComponent(
current$$1,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime
);
...
return nextUnitOfWork;
}

updateClassComponent会判断Component是否实例化,如果没有实例化的话会创建Component实例,这也是前文说的React引擎自动创建Instance的时机,如果已经实例化则调用updateClassInstance更新实例,updateClassInstance会返回该实例是否真正需要更新,并更新props和state。最后会调用finishClassComponent更新element并返回下一个计算单元。

updateClassInstance

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
//ReactNativeRender-dev.js:9282
// Invokes the update life-cycles and returns false if it shouldn't rerender.
function updateClassInstance(
current,
workInProgress,
ctor,
newProps,
renderExpirationTime
) {
var instance = workInProgress.stateNode;

var oldProps = workInProgress.memoizedProps;
instance.props =
workInProgress.type === workInProgress.elementType
? oldProps
: resolveDefaultProps(workInProgress.type, oldProps);

var oldContext = instance.context;
var contextType = ctor.contextType;
var nextContext = void 0;
if (typeof contextType === "object" && contextType !== null) {
nextContext = readContext$1(contextType);
} else {
var nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
}

var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
var hasNewLifecycles =
typeof getDerivedStateFromProps === "function" ||
typeof instance.getSnapshotBeforeUpdate === "function";

// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.

// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillReceiveProps === "function" ||
typeof instance.componentWillReceiveProps === "function")
) {
if (oldProps !== newProps || oldContext !== nextContext) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
nextContext
);
}
}

resetHasForceUpdateBeforeProcessing();

//调用setState匿名方法更新state
var oldState = workInProgress.memoizedState;
var newState = (instance.state = oldState);
var updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime
);
newState = workInProgress.memoizedState;
}

if (
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!checkHasForceUpdateAfterProcessing()
) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === "function") {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
if (typeof instance.getSnapshotBeforeUpdate === "function") {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Snapshot;
}
}
return false;
}

if (typeof getDerivedStateFromProps === "function") {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps
);
newState = workInProgress.memoizedState;
}

var shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext
);

if (shouldUpdate) {
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillUpdate === "function" ||
typeof instance.componentWillUpdate === "function")
) {
startPhaseTimer(workInProgress, "componentWillUpdate");
if (typeof instance.componentWillUpdate === "function") {
instance.componentWillUpdate(newProps, newState, nextContext);
}
if (typeof instance.UNSAFE_componentWillUpdate === "function") {
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
}
stopPhaseTimer();
}
if (typeof instance.componentDidUpdate === "function") {
workInProgress.effectTag |= Update;
}
if (typeof instance.getSnapshotBeforeUpdate === "function") {
workInProgress.effectTag |= Snapshot;
}
} else {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === "function") {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
if (typeof instance.getSnapshotBeforeUpdate === "function") {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Snapshot;
}
}

// If shouldComponentUpdate returned false, we should still update the
// memoized props/state to indicate that this work can be reused.
workInProgress.memoizedProps = newProps;
workInProgress.memoizedState = newState;
}

// Update the existing instance's state, props, and context pointers even
// if shouldComponentUpdate returns false.
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;

return shouldUpdate;
}

updateClassInstance判断节点是否需要跟新,调用以下life-cycles方法:

  • componentWillReceiveProps
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • componentWillUpdate

前文已经知道调用setState时会创建一个update结构,updateClassInstance 会调用processUpdateQueue方法计算新的state,processUpdateQueue方法里面会调用setState传的匿名函数

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
//ReactNativeRender-dev.js:8517
function checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext
) {
var instance = workInProgress.stateNode;
if (typeof instance.shouldComponentUpdate === "function") {
startPhaseTimer(workInProgress, "shouldComponentUpdate");
var shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextContext
);
stopPhaseTimer();
{
!(shouldUpdate !== undefined)
? warningWithoutStack$1(
false,
"%s.shouldComponentUpdate(): Returned undefined instead of a " +
"boolean value. Make sure to return true or false.",
getComponentName(ctor) || "Component"
)
: void 0;
}
return shouldUpdate;
}
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}

updateClassInstance调用checkShouldComponentUpdate判断是否需要更新,checkShouldComponentUpdate实现比较简单,

  1. 如果Component实现shouldComponentUpdate方法,则调用shouldComponentUpdate;
  2. 如果是PureReactComponent,则调用shallowEqual比较props和state是否变化;
  3. 否则返回true

这里要注意checkShouldComponentUpdate默认返回true,所以只要父节点更新,默认就会更新所有子节点,这就是为什么可以通过shouldComponentUpdate返回false或使用PureReactComponent来提升性能。

finishClassComponent

Render阶段最后就是调用finishClassComponent方法计算新的element,并且调用reconcileChildren遍历子节点,具体代码如下:

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
//ReactNativeRender-dev.js:11562
function finishClassComponent(
current$$1,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime
) {
...
if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}

return bailoutOnAlreadyFinishedWork(
current$$1,
workInProgress,
renderExpirationTime
);
}

var instance = workInProgress.stateNode;

// Rerender
ReactCurrentOwner$3.current = workInProgress;
var nextChildren = void 0;
...
setCurrentPhase("render");
nextChildren = instance.render();
...
reconcileChildren(
current$$1,
workInProgress,
nextChildren,
renderExpirationTime
);

// Memoize state using the values we just used to render.
// TODO: Restructure so we never read values from the instance.
workInProgress.memoizedState = instance.state;
...
return workInProgress.child;
}

如果shouldUpdate为false,则直接重用现有节点,跟beginWork处理没变化的节点一样。
如果shouldUpdate为true,则调用render方法计算新的element,然后调用reconcileChildren遍历子节点。

completeUnitOfWork

在遍历到叶子节点后performUnitOfWork会调用completeUnitOfWork

1
2
3
4
5
6
7
8
9
//ReactNativeRender-dev.js:16101
function performUnitOfWork(workInProgress) {
...
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}
...
}

completeUnitOfWork调用completeWork标记需要更新的节点,如果有兄弟节点则返回兄弟节点,继续遍历兄弟节点,否则标记父节点。

Commit 阶段源码分析

前文分析来Render阶段核心方法,Render阶段会生成一颗新的element树,并且生成一个Effect list,Effect list是一个线性列表,包含真正需要更新的操作,Commit 阶段则通过Effect list更新具体的UI,首先看下Commit 阶段的函数调用栈:

Commit阶段函数调用栈

commitAllHostEffects

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
//ReactNativeRender-dev.js:15349
function commitAllHostEffects() {
while (nextEffect !== null) {
{
setCurrentFiber(nextEffect);
}
recordEffect();

var effectTag = nextEffect.effectTag;

if (effectTag & ContentReset) {
commitResetTextContent(nextEffect);
}

if (effectTag & Ref) {
var current$$1 = nextEffect.alternate;
if (current$$1 !== null) {
commitDetachRef(current$$1);
}
}

// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every
// possible bitmap value, we remove the secondary effects from the
// effect tag and switch on that value.
var primaryEffectTag = effectTag & (Placement | Update | Deletion);
switch (primaryEffectTag) {
case Placement: {
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted
// does and isMounted is deprecated anyway so we should be able
// to kill this.
nextEffect.effectTag &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
nextEffect.effectTag &= ~Placement;

// Update
var _current = nextEffect.alternate;
commitWork(_current, nextEffect);
break;
}
case Update: {
var _current2 = nextEffect.alternate;
commitWork(_current2, nextEffect);
break;
}
case Deletion: {
commitDeletion(nextEffect);
break;
}
}
nextEffect = nextEffect.nextEffect;
}

{
resetCurrentFiber();
}
}

commitAllHostEffects源码比较好理解,循环执行effect操作,effect操作可能是替换、删除、更新等

commitWork

更新操作会调用commitWork,源码如下:

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
//ReactNativeRender-dev.js:14628
function commitWork(current$$1, finishedWork) {
...
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
// Note: We currently never use MountMutation, but useLayout uses
// UnmountMutation.
commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
return;
}
case ClassComponent: {
return;
}
case HostComponent: {
var instance = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
var newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
var oldProps =
current$$1 !== null ? current$$1.memoizedProps : newProps;
var type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
var updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork
);
}
}
return;
}
case HostText: {
invariant(
finishedWork.stateNode !== null,
"This should have a text node initialized. This error is likely " +
"caused by a bug in React. Please file an issue."
);
var textInstance = finishedWork.stateNode;
var newText = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
var oldText = current$$1 !== null ? current$$1.memoizedProps : newText;
commitTextUpdate(textInstance, oldText, newText);
return;
}
...
}
}

React Native上Component都是由View、Text等基础Component组成的,所以最终实际更新的是View、Text等基础Component,最后会调用commitUpdate和commitTextUpdate完成实际的更新操作。

commitUpdate

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
//ReactNativeRender-dev.js:4153
function commitUpdate(
instance,
updatePayloadTODO,
type,
oldProps,
newProps,
internalInstanceHandle
) {
var viewConfig = instance.viewConfig;

updateFiberProps(instance._nativeTag, newProps);

var updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);

// Avoid the overhead of bridge calls if there's no update.
// This is an expensive no-op for Android, and causes an unnecessary
// view invalidation for certain components (eg RCTTextInput) on iOS.
if (updatePayload != null) {
UIManager.updateView(
instance._nativeTag, // reactTag
viewConfig.uiViewClassName, // viewName
updatePayload // props
);
}
}

commitUpdate源码比较好理解,首先调用diff判断是否需要更新,如果需要更新的话调用UIManager.updateView更新Native UI,其中UIManager.updateView是Native 暴露的module。diff方法主要是比较props是否变化:

1
2
3
4
5
6
7
8
9
//ReactNativeRender-dev.js:3638
function diff(prevProps, nextProps, validAttributes) {
return diffProperties(
null, // updatePayload
prevProps,
nextProps,
validAttributes
);
}

commitTextUpdate

1
2
3
4
5
6
7
8
9
//ReactNativeRender-dev.js:14659
//ReactNativeRender-dev.js:4145
function commitTextUpdate(textInstance, oldText, newText) {
UIManager.updateView(
textInstance, // reactTag
"RCTRawText", // viewName
{ text: newText } // props
);
}

commitTextUpdate实现比较简单,因为completeWork方法标记TextComponent的时候已经判断了text是否变化,所以直接调用UIManager.updateView。

life-cycles方法调用

在更新完UI后将调用componentDidUpdate方法和setState callback方法,具体调用栈如下:
componentDidUpdate调用栈

setState callback调用栈

总结

React Native Reconciliation过程比较复杂,Fiber框架把递归操作分解成一个个异步、可中断的操作单元后进一步复杂度。
Reconciliation主要可以分为以下两个阶段:

  • Render
  • Commit

Render阶段从根节点开始遍历element树,对于不需要更新的节点直接重用fiber node,对于需要更新的节点,调用life-cycle方法,然后调用render方法计算新的element,最后调用reconcileChildren遍历子节点。
Render阶段还会标记更新,并且生成一个Effect list,Effect list是一个线性列表,包含真正需要更新的操作。
Commit 阶段通过Effect list更新具体的UI,这阶段会调用diff,然后调用UIManager.updateView更新Native View,最后调用componentDidUpdate方法和setState callback方法。

#引用

Inside Fiber: in-depth overview of the new reconciliation algorithm in React
React Fiber Architecture
React Components, Elements, and Instances
Reconciliation