# 前言
在上一篇React源码解析(一):从入口函数调试入手,自下而上窥探react架构 (opens new window)文章中,对于 render阶段没有进行深度的解读,这篇我们来持续深入这部分,这也是react-reconciler核心部分。这部分这里涉及两个核心函数beginWork()
和completeWork()
以及一个核心流程,就是两者是如何协作的。
# render入口
我们先从renderRootSync()
入口函数着手。
# renderRootSync
function renderRootSync(root, lanes) {
prepareFreshStack(root, lanes);
workLoopSync();
}
function prepareFreshStack(root, lanes) {
// 构造 workInProgress 节点
var rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
return rootWorkInProgress;
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
prepareFreshStack()
主要是创建一个workInProgress
节点,作为运行时的root fiber。workLoopSync()
开启我们的工作流程。
# performUnitOfWork
function performUnitOfWork(unitOfWork) {
var current = unitOfWork.alternate;
// 返回下一个节点是哪个节点?
var next = beginWork$1(current, unitOfWork, renderLanes$1);
// 执行完 beginWork之后,pendingProps => memoizedProps
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
// 如果 next 存在 ,继续循环
workInProgress = next;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
performUnitOfWork()
的执行流程,在beginWork()
之后,返回 next
是否为null
,next
存在,则继续beginWork()
操作;否则进入 completeUnitOfWork(unitOfWork)
。
这里我们有其他的方法,快速搞清楚其运行流程。没错,就是 console.log()
# beginWork与completeWork的执行顺序
以下面的代码为例:
export default function App() {
const [num, add] = useState(0);
return (
<div>
<p onClick={() => add(num + 1)}>{num}</p>
<span>
<i></i>
</span>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
我们可以在核心函数插入相关测试代码。
beginWork$1 = function (current, unitOfWork, lanes) {
console.log('beginWork', unitOfWork?.type)
}
function completeWork(current, workInProgress, renderLanes) {
console.log('completeWork', workInProgress?.type)
}
2
3
4
5
6
我们可以得到如下结果:
我们将其转化为流程图如下:
# beginWork
function beginWork(current, workInProgress, renderLanes) {
// update 阶段
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || (
workInProgress.type !== current.type )) {
didReceiveUpdate = true;
} else {
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes);
if (!hasScheduledUpdateOrContext &&
(workInProgress.flags & DidCapture) === NoFlags) {
didReceiveUpdate = false;
// Tip: 什么情况下 return ?
return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
// mount 阶段
didReceiveUpdate = false;
}
switch (workInProgress.tag) {
case IndeterminateComponent: // 2
{
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
case FunctionComponent: // 0
return ...
case ClassComponent: // 1
return ...
case HostRoot: // 3
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent: // 5
return updateHostComponent(current, workInProgress, renderLanes);
//...
}
}
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
beginWork(current, workInProgress, renderLanes)
用大家普遍认知的话来描述,它是一个“递”的过程。
流程图如下:
除 rootFiber
以外组件 mount
时 current === null
,而组件 update
时,由于已经 mount
过了,所以 current !== null
(即 current === null ? mount : update
)
根据这个特性可以将 beginWork
的工作分为两部分:
update
时:如果current
存在且满足一定的条件时,可以复用current
的Fiber 节点
(即上一次更新的Fiber 节点
)mount
时:根据fiber.tag
进入不同的处理函数,然后调用reconcileChildren
创建子 Fiber 节点
。
# update
if (current !== null) {
if (oldProps !== newProps || hasContextChanged() || (
// 这个判断是指 fiber 节点可以复用
workInProgress.type !== current.type )) {
didReceiveUpdate = true;
} else {
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes);
// 没有挂起的 update 或者上下文的变化 && 没有 DidCapture 标识位。(DidCapture用来表示一些异常捕获。)
if (!hasScheduledUpdateOrContext &&
(workInProgress.flags & DidCapture) === NoFlags) {
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在 update
时,会先去以节点的Props
,上下文,以及type
为依据,判断节点是否可以复用,如果不可以,则标记 didReceiveUpdate = true
;如果可以则会进一步判断挂起的 update
(这个是指触发更新的update) 或者上下文的变化,以及 DidCapture
(DidCapture用来表示一些异常捕获) 标识位。满足一个路径之后,则会直接调用 attemptEarlyBailoutIfNoScheduledUpdate()
,并返回。
# attemptEarlyBailoutIfNoScheduledUpdate
function attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes) {
switch (workInProgress.tag) {
case HostRoot:
//...
break;
case HostComponent:
pushHostContext(workInProgress);
break;
case ClassComponent:
//...
break;
case HostPortal:
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
break;
case OffscreenComponent:
case LegacyHiddenComponent:
{
workInProgress.lanes = NoLanes;
return updateOffscreenComponent(current, workInProgress, renderLanes);
}
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
针对不同的情况处理的方式也不一样,有 break
,也有直接return
的。我们先关注 bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
如何复用节点的。
# bailoutOnAlreadyFinishedWork
function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
if (current !== null) {
// 复用依赖
workInProgress.dependencies = current.dependencies;
}
// 判断子节点是否需要检查更新
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
{
return null;
}
}
// 克隆子节点
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
先复用dependencies
,然后判断子节点是否需要检查更新,不需要则返回null
;需要的话,则调用 cloneChildFibers()
,克隆子节点。
update
阶段的,另一条路径会去做diff算法
来生成子fiber,我们后续再来研究。
# mount
当不满足优化路径时,我们就进入第二部分,新建子Fiber
。
我们可以看到,根据fiber.tag
不同,进入不同类型Fiber
的创建逻辑
switch (workInProgress.tag) {
case IndeterminateComponent: // 2
{
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
case FunctionComponent: // 0
return ...
case ClassComponent: // 1
return ...
case HostRoot: // 3
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent: // 5
return updateHostComponent(current, workInProgress, renderLanes);
//...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们以 case HostRoot
为入口,看看他们是如何生成fiber
节点的。
# updateHostRoot
function updateHostRoot(current, workInProgress, renderLanes) {
var nextProps = workInProgress.pendingProps;
// 确保把 current 里面的队列克隆了过来
cloneUpdateQueue(current, workInProgress);
// 将shared里面的队列的update全部转化出来,比如将jsx函数执行,返回得到react元素(ast).
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
var nextState = workInProgress.memoizedState;
var nextChildren = nextState.element;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
2
3
4
5
6
7
8
9
10
11
processUpdateQueue()
函数将我们之前挂在 updateQueue.shared.pending
队列的update
,转化为新的state
,赋值给 workInProgress.memoizedState
,生成的react ast
复制在workInProgress.memoizedState.element
。
如下图所示:
关于processUpdateQueue的细节实现,我们后续再继续研究。
# reconcileChildren
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
// monut
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
// update
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}
var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);
2
3
4
5
6
7
8
9
10
11
12
reconcileChildFibers
与mountChildFibers
都是由ChildReconciler(shouldTrackSideEffects)
生成。
在 monut
阶段,不用生成带有 flags
,反之,update
则需要。由于 workProgress
存在current
,则进入 reconcileChildFibers()
函数。
# ChildReconciler
function ChildReconciler(shouldTrackSideEffects) {
function deleteChild(returnFiber, childToDelete) {}
function deleteRemainingChildren(returnFiber, currentFirstChild){}
function mapRemainingChildren(returnFiber, currentFirstChild) {}
function useFiber(fiber, pendingProps) {}
function placeChild(newFiber, lastPlacedIndex, newIndex) {}
function placeSingleChild(newFiber) {}
function updateTextNode(returnFiber, current, textContent, lanes) {}
function updateElement(returnFiber, current, element, lanes) {}
function updatePortal(returnFiber, current, portal, lanes) {}
function updateFragment(returnFiber, current, fragment, lanes, key) {}
function createChild(returnFiber, newChild, lanes) {}
function updateSlot(returnFiber, oldFiber, newChild, lanes) {}
function updateFromMap(existingChildren, returnFiber, newIdx, newChild, lanes) {}
function warnOnInvalidKey(child, knownKeys, returnFiber) {}
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) {}
function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildrenIterable, lanes) {}
function reconcileSingleTextNode(returnFiber, currentFirstChild, textContent, lanes) {}
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {}
function reconcileSinglePortal(returnFiber, currentFirstChild, portal, lanes) {}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
// reconcileSingleElement 返回一个由react元素 创建的fiber
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
case REACT_PORTAL_TYPE:
return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes));
case REACT_LAZY_TYPE:
var payload = newChild._payload;
var init = newChild._init;
return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);
}
// 多个节点的情况,newChild是 array 的case
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, lanes);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
// 这里对于 string,number 都是单个节点,对应一种处理方式
if (typeof newChild === 'string' && newChild !== '' || typeof newChild === 'number') {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, lanes));
}
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
return reconcileChildFibers;
}
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
ChildReconciler()
构造函数是整个生成fiber
的核心实现,我们可以看到这里内部定义的函数很多,针对的情况也很多,我们先看看,我可能举例的这种case
它是如何生成fiber
的。由于们是符合element.$$typeof == Symbol(react.element)
,所以走下述逻辑placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes))
。
ChildReconciler()构造函数很像,vue2的createPatch()以及vue3的createRender()。通常都是用于上层虚拟Dom和下层Api(比如Dom,比如native基类)链接,也是大多数跨端框架实现原理。
# reconcileSingleElement
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
var key = element.key; // element 即 newChild
var child = currentFirstChild;
// 判断 上次是否有Dom节点存在
while (child !== null) {
// 单个节点diff优化
}
if (element.type === REACT_FRAGMENT_TYPE) {
var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key);
created.return = returnFiber;
return created;
} else {
// 创建新的 fiber 节点
var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);
_created4.ref = coerceRef(returnFiber, currentFirstChild, element);
_created4.return = returnFiber;
return _created4;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
currentFirstChild
不存在,这个优化主要用于有child
节点的 diff
优化,我们先忽略。关注createFiberFromElement()
函数实现。
# createFiberFromElement
function createFiberFromElement(element, mode, lanes) {
var owner = null;
{
owner = element._owner;
}
// type 就是 App function
var type = element.type;
var key = element.key;
var pendingProps = element.props;
var fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes);
{
fiber._debugSource = element._source;
fiber._debugOwner = element._owner;
}
return fiber;
}
function createFiberFromTypeAndProps(type, // React$ElementType
key, pendingProps, owner, mode, lanes) {
var fiberTag = IndeterminateComponent;
var resolvedType = type;
if (typeof type === 'function') {
resolvedType = resolveFunctionForHotReloading(resolvedType);
}
// ...
var fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.lanes = lanes;
{
fiber._debugOwner = owner;
}
return fiber;
}
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
这样就完成了App function
到App function fiber
的转化,这里我们要注意fiberTag
的类型。有同学就有疑问了,你不是说是返回div
或者p
就这样的fiber.type
怎么这里没有呢?饭要一口口吃。这里将 App function fiber
最为workProgress.child
返回之后,又会去执行beginWork()
,由于这里标记了fiberTag = IndeterminateComponent
,下次就是执行这个函数,拿到return
里面的react 元素
,再由他们生成对应fiber
再返回,直至节点树遍历完成,则会进入到 completeUnitOfWork()
函数之中。
# completeUnitOfWork
function completeUnitOfWork(unitOfWork) {
var completedWork = unitOfWork;
do {
var current = completedWork.alternate;
var returnFiber = completedWork.return;
var next = completeWork(current, completedWork,renderLanes$1);
// 什么情况?
// completeWork 可能返回子节点,需要继续beginWork
if (next !== null) {
workInProgress = next;
return;
}
// 存在兄弟节点,则将workInProgress=siblingFiber,返回,继续beginWork
var siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
// 返回父节点,继续completeWork
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
completeUnitOfWork()
主要是completedWork()
函数运转的流程控制。大概会有这么三种情况:
- 如果
completedWork()
返回了子节点next
,则将workInProgress=next
需要继续beginWork()
。 - 再判断是否存在兄弟节点
siblingFiber
,存在的话,则workInProgress=siblingFiber
需要继续beginWork()
- 否则,将
completedWork = returnFiber
,需要继续completedWork()
# completeWork
function completeWork(current, workInProgress, renderLanes) {
console.log('completeWork', workInProgress?.type)
var newProps = workInProgress.pendingProps;
popTreeContext(workInProgress);
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
bubbleProperties(workInProgress);
return null;
case HostRoot:
// ...
// Tip: 页面渲染所必须的,即原生DOM组件对应的Fiber节点
case HostComponent:
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) { // diff fiber properties
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
if (current.ref !== workInProgress.ref) {
markRef$1(workInProgress);
}
} else {
if (!newProps) {
if (workInProgress.stateNode === null) {
throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');
}
// This can happen when we abort work.
bubbleProperties(workInProgress);
return null;
}
var currentHostContext = getHostContext();
var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {
if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
markUpdate(workInProgress);
}
} else {
// 创建Dom实例
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
// 插入子孙节点
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
markUpdate(workInProgress);
}
}
if (workInProgress.ref !== null) {
markRef$1(workInProgress);
}
}
// flags冒泡到subtreeFlags
bubbleProperties(workInProgress);
return null;
}
// ...
}
}
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
completeWork(current, workInProgress, renderLanes)
用大家普遍认知的话来描述,它是一个“归”的过程。
流程图如下:
# update
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
}
var updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) {
var oldProps = current.memoizedProps;
if (oldProps === newProps) {
return;
}
var instance = workInProgress.stateNode;
var currentHostContext = getHostContext();
// Tip:
// 被处理完的props会被赋值给 workInProgress.updateQueue,
// 并最终会在commit阶段被渲染在页面上
// 其中 updatePayload 为数组形式,
// 他的偶数索引的值为变化的prop key,奇数索引的值为变化的prop value
var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext);
workInProgress.updateQueue = updatePayload
if (updatePayload) {
markUpdate(workInProgress);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在 update
时,调用 updateHostComponent$1()
,新旧props
比较,没变则直接返回;如果有变化则会去调用 prepareUpdate()
,生成一个 updatePayload
数组,挂载在workInProgress.updateQueue
上。updatePayload
的偶数索引的值为变化的prop key
,奇数索引的值为变化的prop value
。
# prepareUpdate
function prepareUpdate(domElement, type, oldProps, newProps, rootContainerInstance, hostContext) {
return diffProperties(domElement, type, oldProps, newProps);
}
2
3
这里主要是调用 diffProperties(domElement, type, oldProps, newProps)
,这里我们先不展开,后续再来探究。
# mount
在 mount
时,调用 createInstance()
生成 dom
实例,之后通过appendAllChildren()
方法将子孙dom
插入当前实例。并将 workInProgress.stateNode = instance
实例挂载到stateNode
上。然后调用finalizeInitialChildren()
来初始化节点的属性和事件。
# createInstance
function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
var parentNamespace;
var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
function createElement(type, props, rootContainerElement, parentNamespace) {
// 获取 dom
var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement);
// 创建实例
var domElement = ownerDocument.createElement(type);
return domElement;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
createInstance()
内部主要调用createElement()
方法来创建Dom
实例,不同的平台底层实现不一样,在浏览器端,其实就是document.createElement()
方法来生成Dom
元素。
# appendAllChildren
appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
var node = workInProgress.child;
while (node !== null) {
// 如果是dom节点或者是文本节点,直接塞入如节点,
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (node.tag === HostPortal) ;
else if (node.child !== null) {
// 如果不是的话,保存当前父节点()
node.child.return = node;
// 向下移动
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// 处理兄弟节点
node.sibling.return = node.return;
node = node.sibling;
}
};
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
先判断子节点类型:
如果子节点是 HostComponent
或 HostText
:
- 宿主组件(如
p
、span
)和文本节点(如"Hello World"
)的stateNode
指向实际的 DOM 节点。则使用appendChild
将这些 DOM 节点附加到父节点。 - 如果子节点不满足上述判断:递归处理其子节点,通过
node.child
继续向下遍历。 - 如果当前节点有兄弟节点(
node.sibling
存在),继续处理兄弟节点。 - 当子树遍历完成后,通过
node.return
返回到父节点,继续处理其他子节点。
# finalizeInitialChildren
function finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) {
// 初始化属性
setInitialProperties(domElement, type, props, rootContainerInstance);
// 返回true,则会标记更新
switch (type) {
case 'button':
case 'input':
case 'select':
case 'textarea':
return !!props.autoFocus;
case 'img':
return true;
default:
return false;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
setInitialProperties(domElement, type, props, rootContainerInstance)
初始化属性,button
这些标签如果有autoFocus
属性,返回为true
,则会标记更新,commit
阶段就会触发对应的事件。
# setInitialProperties
function setInitialProperties(domElement, tag, rawProps, rootContainerElement) {
// 针对不同的tag,将不能冒泡和捕获的事件,绑定到具体的元素之上
switch (tag) {
case 'dialog':
listenToNonDelegatedEvent('cancel', domElement);
listenToNonDelegatedEvent('close', domElement);
props = rawProps;
break;
// ...
}
// 设置dom的属性,比如内联样式等
setInitialDOMProperties(tag, domElement, rootContainerElement, props, isCustomComponentTag);
// 设置表单元素的相关值
switch (tag) {
case 'input':
track(domElement);
postMountWrapper(domElement, rawProps, false);
break;
// ...
default:
if (typeof props.onClick === 'function') {
trapClickOnNonInteractiveElement(domElement);
}
break;
}
}
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
setInitialProperties()
大概做了这么几件事:
- 针对不同的
tag
,将不能冒泡和捕获的事件,绑定到具体的元素之上。 - 设置
dom
的属性,比如内联样式,常规属性等等 - 针对不同的
tag
,设置表单元素的相关值
这里的事件处理,是一些不能走react合成事件的特殊处理,具体的事件合成机制,我们后续再来深入。
# bubbleProperties
function bubbleProperties(completedWork) {
var newChildLanes = NoLanes;
var subtreeFlags = NoFlags;
// ...
// 核心逻辑
while (_child !== null) {
newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child.lanes, _child.childLanes));
subtreeFlags |= _child.subtreeFlags;
subtreeFlags |= _child.flags;
_child.return = completedWork;
_child = _child.sibling;
}
completedWork.subtreeFlags |= subtreeFlags;
completedWork.childLanes = newChildLanes;
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bubbleProperties()
对所有的workInProgress.tag
都会执行,主要将子节点flags
标记按位或
操作冒泡到父节点的subtreeFlags
上,lanes
则是通过mergeLanes()
合并实现。此外还有静态节点标记等等,这是为了在 commit
阶段能够尽可能去优化性能。
# 总结
- 至此,整个
react
的render
阶段做了哪些事情,以及beginWork()
和completeWork()
是如何协作的。 - 其中还有很多部分在本文中没有详细阐述,比如,
diff
细节,事件合成,fiber
属性的diff
等等,我们后续再去深入。 - 我觉得这部分的设计和实现,挺有魔力的,不知道
react
的作者们是借鉴了其他模块的实现,还常规手段,相比于vue
的常规实现,确实开拓技术视野。