跳到主要内容

认识

2023年12月19日
柏拉文
越努力,越幸运

一、认识


Context 兼容 bailout 性能优化策略: 在 Context 场景下, 需要提前标记 ProviderConsumer 之间的 childLanes。那么, 怎么知道哪些是 Consumer 呢? 也就是说, React 是怎么是知道哪些组件消费了 Context 呢? React 是通过 fiber 中的 dependencies 属性存储当前组件所有依赖的 Context, 所有依赖的 Context 会形成单向链表。

因此, 在 Context 场景下, 标记 ProviderConsumer 之间的 childLanes 的过程为: 从 Provider 开始, 沿途向下进行深度优先遍历, 寻找对应节点的 fiber.dependencies 是否包含此次更新的 Context, 随后标记 fiber.childLanes

二、细节


ContextItem {
context: ReactContext;
memoizedState: Value;
next: ContextItem | null;
}

fiber.dependencies {
firstContext: ContextItem | null; // 当前 fiber.dependencies 存储的 所有依赖的 context 中的第一个 context
lanes: Lanes; // 某个 context.value 发生变化, dependencies.lanes 会增加对应更新的 lane , 因此通过查找 fiber.dependencies.lanes, 我们就可以知道当前组件所有依赖的 context 是否含有待更新的 context
}

三、初始化赋值


/packages/react-reconciler/fiber.jscreateWorkInProgress() 中, 新增如下逻辑:

export function createWorkInProgress(current, pendingProps) {
let workInprogress = current.alternate;

if (workInprogress === null) {
// mount 阶段
workInprogress = new FiberNode(current.tag, pendingProps, current.key);
workInprogress.stateNode = current.stateNode;
workInprogress.alternate = current;
current.alternate = workInprogress;
} else {
// update 阶段
workInprogress.pendingProps = pendingProps;
workInprogress.flags = NoFlags;
workInprogress.deletions = null;
}

workInprogress.ref = current.ref;
workInprogress.type = current.type;
workInprogress.child = current.child;
workInprogress.updateQueue = current.updateQueue;
workInprogress.memoizedState = current.memoizedState;
workInprogress.lanes = current.lanes;
workInprogress.childLanes = current.childLanes;

const currentDeps = current.dependencies;
workInprogress.dependencies =
currentDeps === null
? null
: {
lanes: currentDeps.lanes,
firstContext: currentDeps.firstContext
};

return workInprogress;
}

四、存储依赖项


React 在什么时候将 context 存储到 fiber.dependencies 中呢? React 在消费 context 的处理逻辑中, 同时将 context 存储到 fiber.dependencies 中。

  • Consumer:

  • useContext: useContext 使用 readContext 读取 Context 数据, 因此在 readContext 中处理

/packages/react-reconciler/fiberContext.js
export function readContext(consumer, context) {
if (consumer == null) {
throw new Error('请在函数组件内调用 useContext');
}

const value = context._currentValue;

const contextItem = {
context,
next: null,
memoizedState: value
};

if (lastContextDep == null) {
lastContextDep = contextItem;
consumer.dependencies = {
lanes: NoLanes,
firstContext: contextItem
};
} else {
lastContextDep = lastContextDep.next = contextItem;
}

return value;
}

五、寻找依赖项


Context.Provider 出发, 沿途深度优先遍历寻找 fiber.dependencies 中是否含有此次对应的 Context, 并沿途向上标记 childLanes

/packages/react-reconciler/fiberContext.js
export function propagateContextChange(workInProgress, context, renderLane) {
let fiber = workInProgress.child;
if (fiber !== null) {
fiber.return = workInProgress;
}

while (fiber !== null) {
let nextFiber = null;
const deps = fiber.dependencies;
if (deps !== null) {
nextFiber = fiber.child;
let contextItem = deps.firstContext;
while (contextItem !== null) {
if (contextItem.context === context) {
fiber.lanes = mergeLanes(fiber.lanes, renderLane);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLane);
}
scheduleContextWorkOnParentPath(
fiber.return,
workInProgress,
renderLane
);
deps.lanes = mergeLanes(deps.lanes, renderLane);
break;
}
contextItem = contextItem.next;
}
} else if (fiber.tag === ContextProvider) {
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
} else {
nextFiber = fiber.child;
}

if (nextFiber !== null) {
nextFiber.return = fiber;
} else {
nextFiber = fiber;
while (nextFiber !== null) {
if (nextFiber === workInProgress) {
nextFiber = null;
break;
}
let sibling = nextFiber.sibling;
if (sibling !== null) {
sibling.return = nextFiber.return;
nextFiber = sibling;
break;
}
nextFiber = nextFiber.return;
}
}
fiber = nextFiber;
}
}

function scheduleContextWorkOnParentPath(from, to, renderLane) {
let node = from;

while (node !== null) {
const alternate = node.alternate;
if (!isSubsetOfLanes(node.childLanes, renderLane)) {
node.childLanes = mergeLanes(node.childLanes, renderLane);
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, renderLane);
}
} else if (
alternate != null &&
!isSubsetOfLanes(alternate.childLanes, renderLane)
) {
alternate.childLanes = mergeLanes(alternate.childLanes, renderLane);
}

if (node === to) {
break;
}
node = node.return;
}
}