在beginWork completeWork的过程中switch catch到updateHostComponent就有一行代码

completeWork是对应的popHostContext。
总是在fiber节点的beginWork/completeWork,在updateHostComponent等的第一行出现。
概念和组成
"游标"是指向栈中元素的一个指针,例如指向了栈顶元素。
游标栈是 栈 + 游标。
2个公共的栈 + 5个游标组成
js
var valueStack = [];//1.包含3个内容:包含Provider的Context是否change、上下文Context、fiber
var fiberStack; //2.只包含fiber
{
fiberStack = [];
}
var index = -1;
function createCursor(defaultValue) {
return {
current: defaultValue
};
}
// 3.Provider提供的Context的游标
var contextStackCursor = createCursor(emptyContextObject);
// 4.对应的Context是否change的游标
var didPerformWorkStackCursor = createCursor(false);
// 上一个contextStackCursor,用于新旧contextStackCursor的合并
var previousContext = emptyContextObject;
var NO_CONTEXT = {};
// 5.实际dom元素的fiber节点的上下文游标
var contextStackCursor$1 = createCursor(NO_CONTEXT);
// 6.fiber节点的游标
var contextFiberStackCursor = createCursor(NO_CONTEXT);
// 7.根节点HostRoot游标
var rootInstanceStackCursor = createCursor(NO_CONTEXT);
contextStackCursor$1
是最新的HostComponent
的游标
contextStackCursor
是最新的ClassComponent
且设置了childContextTypes
属性的类组件的游标
5个入栈函数
- 设置contextStackCursor和didPerformWorkStackCursor游标
js
function pushTopLevelContextObject(fiber, context, didChange) {
{
if (contextStackCursor.current !== emptyContextObject) {
throw new Error('Unexpected context found on stack. ' + 'This error is likely caused by a bug in React. Please file an issue.');
}
push(contextStackCursor, context, fiber);
push(didPerformWorkStackCursor, didChange, fiber);
}
}
- 设置contextStackCursor和didPerformWorkStackCursor游标
js
var emptyContextObject = {};
function pushContextProvider(workInProgress) {
{
var instance = workInProgress.stateNode;
// We push the context as early as possible to ensure stack integrity.
// If the instance does not exist yet, we will push null at first,
// and replace it on the stack later when invalidating the context.
var memoizedMergedChildContext = instance && instance.__reactInternalMemoizedMergedChildContext || emptyContextObject;
// Remember the parent context so we can merge with it later.
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
previousContext = contextStackCursor.current;
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
push(didPerformWorkStackCursor, didPerformWorkStackCursor.current, workInProgress);
return true;
}
}
- 设置contextFiberStackCursor contextStackCursor$1 和rootInstanceStackCursor 游标
js
function pushHostContainer(fiber, nextRootInstance) {
// Push current root instance onto the stack;
// This allows us to reset root when portals are popped.
push(rootInstanceStackCursor, nextRootInstance, fiber);
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
// Finally, we need to push the host context to the stack.
// However, we can't just call getRootHostContext() and push it because
// we'd have a different number of entries on the stack depending on
// whether getRootHostContext() throws somewhere in renderer code or not.
// So we push an empty value first. This lets us safely unwind on errors.
push(contextStackCursor$1, NO_CONTEXT, fiber);
var nextRootContext = getRootHostContext(nextRootInstance);
// Now that we know this function doesn't throw, replace it.
pop(contextStackCursor$1, fiber);
push(contextStackCursor$1, nextRootContext, fiber);
}
- 设置contextFiberStackCursor contextStackCursor$1游标
js
function pushHostContext(fiber) {
var rootInstance = requiredContext(rootInstanceStackCursor.current);
var context = requiredContext(contextStackCursor$1.current);
var nextContext = getChildHostContext(context, fiber.type);
// Don't push this Fiber's context unless it's unique.
if (context === nextContext) {
return;
}
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
push(contextStackCursor$1, nextContext, fiber);
}
- 设置contextStackCursor和didPerformWorkStackCursor游标 合并上下文,设置到contextStackCursor
js
function invalidateContextProvider(workInProgress, type, didChange) {
{
var instance = workInProgress.stateNode;
if (!instance) {
throw new Error('Expected to have an instance by this point. ' + 'This error is likely caused by a bug in React. Please file an issue.');
}
if (didChange) {
// Merge parent and own context.
// Skip this if we're not updating due to sCU.
// This avoids unnecessarily recomputing memoized values.
// 合并上下文对象,类似于assign({},obj1,obj2}
var mergedContext = processChildContext(workInProgress, type, previousContext);
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
// Replace the old (or empty) context with the new one.
// It is important to unwind the context in the reverse order.
// 先把pushContextProvider的取出来
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
// Now push the new context and mark that it has changed.
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
} else {
pop(didPerformWorkStackCursor, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
}
}
}
其中三个都是设置contextStackCursor和didPerformWorkStackCursor游标,下面具体介绍。
这5个入栈函数是在不同的fiber节点tag类型使用的:
-
beginWork:HostRoot
-updateHostRoot
-pushHostRootContext
-pushTopLevelContextObject
、pushHostContainer
-
beginWork:ClassComponent
-updateClassComponent
-pushContextProvider
isContextProvider hasContext = truebeginWork:ClassComponent
-updateClassComponent
-mountClassInstance
-getUnmaskedContext
、getMaskedContext
beginWork:ClassComponent
-updateClassComponent
-finishClassComponent
-invalidateContextProvider
hasContext -
beginWork:HostComponent
-updateHostComponent
-pushHostContext
介绍
1.pushHostRootContext
内部调用pushTopLevelContextObject
和pushHostContainer
把5个游标都设置,用于HostRoot
节点,也就是差不多是div#root
的fiber
。
pushContextProvider
只设置contextStackCursor
didPerformWorkStackCursor
游标。它是在类组件(ClassComponent
)、且有childContextTypes
属性的情况下调用。有childContextTypes
表明它是一个上下文提供者Provider。在最后面的代码中有例子。
pushHostContext
设置contextFiberStackCursor
contextStackCursor$1
游标。在dom实例对应的fiber节点,fiberNode.tag=5 即HostComponent
(dom元素对应的fiber)的情况使用。contextStackCursor$1
保存当前fiber的上下文。
上下文对象:一个是我们在validateDOMNesting校验dom嵌套结构介绍过的保存祖先信息的对象,还有一个是Provider-Context上下文对象
js
//祖先嵌套信息
{
ancestorInfo:{
aTagInScope: null
buttonTagInScope : null
current: {tag: 'div'}
dlItemTagAutoclosing: null
formTag: null
listItemTagAutoclosing: null
nobrTagInScope: null
pTagInButtonScope: null}
namespace: "http://www.w3.org/1999/xhtml"
}
getUnmaskedContext
、getMaskedContext
Unmasked
: unmasked
意为"未被遮盖的"。在编程中,mask
(掩码)是一个非常重要的概念,它是一个二进制值 ,用于通过位运算来选择性地保留或隐藏另一个值中的某些位元。unmasked
则表示一个值在被获取之前,没有经过位运算的过滤或修改,它是其原始形式。
对应的是Provider-Context的contextTypes
属性。
上上下下的Provider-Context合并起来,供全局使用,类似于assign({},obj1,obj2}。合并后的是UnmaskedContext,没有过滤的。过滤出要使用的是,过滤后的是MaskedContext的,依据的就是contextTypes
属性进行过滤。在源码的getUnmaskedContext
、getMaskedContext
进行。
isContextProvider
hasContext = true
invalidateContextProvider
isContextProvider
函数判断类组件是否有childContextTypes
属性。然后把hasContext
设置为true/false。
childContextTypes
、contextTypes
是旧版的Provider-Context的用法。
有childContextTypes
属性,就把上上下下的Context合并起来,对应源码invalidateContextProvider
。
有这样的属性的类组件,
js
// 提供context
// ...一些代码
// 提供了childContextTypes {theme:'dark',other:...}
// 消费 context
class ThemedButton extends React.Component {
// 声明需要订阅哪些 context
static contextTypes = {// getMaskedContext内部获取这个属性
theme: PropTypes.string, //只要theme
};
render() {
return <button>当前主题: {this.context.theme}</button>;
}
}
invalidateContextProvider
和pushContextProvider
pushContextProvider
和invalidateContextProvider
联合使用,除了HostRoot
后续fiber都是在isContextProvider
为true下使用。
挂载阶段instance
是null,memoizedMergedChildContext=emptyContextObject
为了尽早push,使用pushContextProvide
。
如果上下文变了,invalidateContextProvider
把pushContextProvider
push的pop,然后push新的。
我们看一看顺序:
a. updateClassComponent
b. 挂载阶段,实例还没创建pushContextProvider
c. 创建实例constructClassInstance
:var instance = new ctor(props, context);
d. finishClassComponent
e. invalidateContextProvider
合并上下文
更新阶段instance不是null,也是尽早push,使用缓存的合并值,在finishClassComponent
时发现变了,先把push的pop,最后push新的。
js
function updateClassComponent(current, workInProgress, Component, nextProps, renderLanes) {
...
var hasContext;
if (isContextProvider(Component)) {
hasContext = true;
pushContextProvider(workInProgress);
} else {
hasContext = false;
}
...
var nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes);
}
js
function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes) {
var didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;
//既没有更新,也没有错误,跳过后续渲染
if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
...
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}
return workInProgress.child;
}
这么复杂设计的游标栈在哪里使用
1.completeWork
的dom实例创建(createInstance
)和更新(updateHostComponent$1
),文本节点实例的创建(createTextInstance
)。
只有这两个涉及到dom的需要检查html标签嵌套的检查,会用到游标栈保存的上写文,对于其他类型的fiber,都不会用到游标栈。
completeWork:HostComponent
-updateHostComponent$1
-prepareUpdate
更新dom实例
completeWork:HostComponent
-createInstance
创建dom实例
completeWork:HostText
-createTextInstance
创建文本dom实例
这些都validateDOMNesting
检查嵌套,需要祖先标签信息,祖先信息保存在上下文。
源码completeWork createInstance createTextInstance
为什么dom实例更新也要检查嵌套?
举个例子:
jsx
function App({ bad }) {
return <table>{bad ? <div>oops</div> : <tr><td>ok</td></tr>}</table>;
}
a. 初次渲染 bad = false
,生成 <table><tr><td>ok</td></tr></table>
✅ 合法。
b. 更新时 bad = true
,React diff 发现 <tr>
→ <div>
,于是准备在 同一个 table DOM 节点 里插入 <div>
。
c. 这时候如果不在 update 里跑 validateDOMNesting
,React 就不会报错,最终会出现 <table><div>oops</div></table>
❌ 不合法的 DOM。
2.函数组件渲染的时候,传入函数组件的内部,函数组件的第二个参数
updateSimpleMemoComponent
-updateFunctionComponent
-renderWithHooks
mountLazyComponent:FunctionComponent
-updateFunctionComponent
-renderWithHooks
beginWork:FunctionComponent
-updateFunctionComponent
-renderWithHooks
这些情况都Component(props, secondArg);
jsx
function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) {
...
var context;
{
var unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
context = getMaskedContext(workInProgress, unmaskedContext);
}
...
nextChildren = renderWithHooks(current, workInProgress, Component, nextProps, context, renderLanes);
...
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
...
var children = Component(props, secondArg); // Check if there was a render phase update
...
return children;
}
js
以前(React <16.3)函数组件是可以写成这样:
function MyComponent(props, context) {
console.log(props, context);
return <div>{context.theme}</div>;
}
MyComponent.contextTypes = {
theme: PropTypes.string
};
为什么不把上下文保存在fiber节点上,而是通过栈+游标?
我们需要跨级取用,contextStackCursor$1
是最新的HostComponent
的游标,只会看到dom元素的fiber。
div-CustomComponent-p,这样的嵌套结构,我们不需要CustomComponent,我们需要的是div-p,contextStackCursor$1
是div的,到p进入pushHostContext
,contextStackCursor$1
更新为p。
看getChildHostContext
。 updatedAncestorInfo
是校验dom嵌套结构的内容。
js
function getChildHostContext(parentHostContext, type, rootContainerInstance) {
{
var parentHostContextDev = parentHostContext;
var namespace = getChildNamespace(parentHostContextDev.namespace, type);
//每个标签要重新计算祖先和当前节点的嵌套信息,作为上下文
var ancestorInfo = updatedAncestorInfo(parentHostContextDev.ancestorInfo, type);
return {
namespace: namespace,
ancestorInfo: ancestorInfo
};
}
}
push和参数
我们在push的时候提供的第二个参数value是第一个参数游标的最新的值。
js
function push(cursor, value, fiber) {
index++;
valueStack[index] = cursor.current;//旧的
{
fiberStack[index] = fiber;
}
cursor.current = value;//最新的
}
pushTopLevelContextObject:
cursor value fiber
push(contextStackCursor, context, fiber);//context是contextStackCursor的值
push(didPerformWorkStackCursor, didChange, fiber);//cursor是didPerformWorkStackCursor,value是didChange
pushHostContainer:
push(rootInstanceStackCursor, nextRootInstance, fiber); // Track the context and the Fiber that provided it.
push(contextFiberStackCursor, fiber, fiber); // Finally, we need to push the host context to the stack.
push(contextStackCursor$1, nextRootContext, fiber);
pushContextProvider:
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
push(didPerformWorkStackCursor, didPerformWorkStackCursor.current, workInProgress);
pushHostContext:
push(contextFiberStackCursor, fiber, fiber);
push(contextStackCursor$1, nextContext, fiber);
cursor分别是contextStackCursor、didPerformWorkStackCursor...等游标
value是nextContext fiber didChange等,如祖先信息对象、Provider-Context的上下文对象
我们在push的时候提供的第二个参数value是第一个参数游标cursor的最新的值。
valueStack栈是全部游标的值,可以看到valueStack其实非常的乱,代码中也没有对valueStack栈的直接使用,都是使用contextStackCursor$1 contextStackCursor rootInstanceStackCursor 这些游标的值
fiberStack是全部fiber
实例
jsx
import React from "react";
import PropTypes from "prop-types";
// 顶层 Provider,定义 childContextTypes
class ThemeProvider extends React.Component {
// 定义能往下传的 context 类型
static childContextTypes = {
theme: PropTypes.string,
};
// 返回要传递下去的值(合并到父 context)
getChildContext() {
return { theme: "dark" };
}
render() {
return this.props.children;
}
}
// 中间组件,不消费 context,只是转发 children
class Layout extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
// 消费 context 的组件
class ThemedButton extends React.Component {
// 声明需要订阅哪些 context
static contextTypes = {
theme: PropTypes.string, //过滤出需要使用的
};
render() {
return <button>当前主题: {this.context.theme}</button>;
}
}
export default function App() {
return (
<ThemeProvider>
<Layout>
<ThemedButton />
</Layout>
</ThemeProvider>
);
}
源码 completeWork createInstance createTextInstance
js
function completeWork(){
case HostComponent:{
popHostContext(workInProgress);
//获取游标
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
if (current.ref !== workInProgress.ref) {
markRef$1(workInProgress);
}
}else{
...
//获取游标
var currentHostContext = getHostContext();
var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {
if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
markUpdate(workInProgress);
}
}else{
//创建dom实例 里面会校验html标签嵌套
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
markUpdate(workInProgress);
}
}
}
}
case HostText:
{
var newText = newProps;
if (current && workInProgress.stateNode != null) {
var oldText = current.memoizedProps;
updateHostText$1(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
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.
}
var _rootContainerInstance = getRootHostContainer();
var _currentHostContext = getHostContext();
var _wasHydrated2 = popHydrationState(workInProgress);
if (_wasHydrated2) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance, _currentHostContext, workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
}
}
js
// validateDOMNesting需要知道上下文
function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
var parentNamespace;
{
//
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
if (typeof props.children === 'string' || typeof props.children === 'number') {
var string = '' + props.children;
//validateDOMNesting需要知道上下文
var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type);
validateDOMNesting(null, string, ownAncestorInfo);
}
parentNamespace = hostContextDev.namespace;
}
//创建element
var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
js
// validateDOMNesting需要知道上下文
function createTextInstance(text, rootContainerInstance, hostContext, internalInstanceHandle) {
{
// validateDOMNesting需要知道上下文
var hostContextDev = hostContext;
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
}
var textNode = createTextNode(text, rootContainerInstance);
precacheFiberNode(internalInstanceHandle, textNode);
return textNode;
}
js
updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) {
var oldProps = current.memoizedProps;
if (oldProps === newProps) {
// In mutation mode, this is sufficient for a bailout because
// we won't touch this node even if children changed.
return;
}
var instance = workInProgress.stateNode;
var currentHostContext = getHostContext();
var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext);
workInProgress.updateQueue = updatePayload;
if (updatePayload) {
markUpdate(workInProgress);
}
};
js
// validateDOMNesting需要知道上下文
function prepareUpdate(domElement, type, oldProps, newProps, rootContainerInstance, hostContext) {
{
var hostContextDev = hostContext;
if (typeof newProps.children !== typeof oldProps.children && (typeof newProps.children === 'string' || typeof newProps.children === 'number')) {
var string = '' + newProps.children;
//validateDOMNesting需要知道上下文
var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type);
validateDOMNesting(null, string, ownAncestorInfo);
}
}
return diffProperties(domElement, type, oldProps, newProps);
}
js
function requiredContext(c) {
if (c === NO_CONTEXT) {
throw new Error('Expected host context to exist. This error is likely caused by a bug ' + 'in React. Please file an issue.');
}
return c;
}
js
function getRootHostContainer() {
var rootInstance = requiredContext(rootInstanceStackCursor.current);
return rootInstance;
}
js
function getHostContext() {
var context = requiredContext(contextStackCursor$1.current);
return context;
}