认识
一、认识
二、细节
-
Context
创建, 定义数据结构 -
Context
实现 -
Provider
实现 -
Context
消费实现 -
Context
兼容bailout
性能优化策略
三、Context 创建
3.1 数据结构
type ReactContext<T> = {
$$typeof: symbol | number;
Provider: ReactProviderType<T> | null;
_currentValue: T;
}
type ReactProviderType<T> = {
$$typeof: symbol | number;
_context: ReactContext<T> | null;
}
3.2 创建结构
import {
REACT_CONTEXT_TYPE,
REACT_PROVIDER_TYPE
} from '../shared/ReactSymbols';
export function createContext(defaultValue) {
const context = {
$$typeof: REACT_CONTEXT_TYPE,
Provider: null,
_currentValue: defaultValue
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context
};
}
四、Context 逻辑
Context
需要实现两部分逻辑:
-
对
ContextProvider
类型FiberNode
的支持 -
Context
逻辑实现:-
支持
context._currentValue
的变化 -
嵌套的
context
-
4.1 支持 Provider
beginWork
中支持 ContextProvider
类型的 FiberNode
export const beginWork = (workInProgress, renderLane) => {
switch (workInProgress.tag) {
case HostRoot:
return updateHostRoot(workInProgress, renderLane);
case HostText:
return null;
case Fragment:
return updateFragment(workInProgress);
case HostComponent:
return updateHostComponent(workInProgress);
case FunctionComponent:
return updateFunctionComponent(workInProgress, renderLane);
case ContextProvider:
return updateContextProvider(workInProgress);
default:
console.log('beginWork 未实现的类型');
break;
}
return null;
};
completeWork
中支持 ContextProvider
类型的 FiberNode
export const completeWork = workInProgress => {
const newProps = workInProgress.pendingProps;
const current = workInProgress.alternate;
switch (workInProgress.tag) {
case HostComponent:
……
case HostText:
……
case HostRoot:
case Fragment:
case FunctionComponent:
bubbleProperties(workInProgress);
return null;
case ContextProvider:
bubbleProperties(workInProgress);
return;
default:
console.log('completeWork 未实现的类型');
break;
}
};
fiber.createFiberFromElement
中支持 ContextProvider
类型的 FiberNode
export function createFiberFromElement(element, lanes) {
const { ref, key, type, props } = element;
let fiberTag = FunctionComponent;
if (typeof type === 'string') {
fiberTag = HostComponent;
} else if (
typeof type === 'object' &&
type.$$typeof === REACT_PROVIDER_TYPE
) {
fiberTag = ContextProvider;
} else if (typeof type !== 'function') {
console.log('未定义的 type 类型', element);
}
const fiber = new FiberNode(fiberTag, props, key);
fiber.ref = ref;
fiber.type = type;
fiber.lanes = lanes;
return fiber;
}
4.2 实现 Context
Context
的实现逻辑如下:
-
支持
context._currentValue
的变化 -
支持嵌套
context
beginWork
中:
export const beginWork = (workInProgress, renderLane) => {
switch (workInProgress.tag) {
……
case ContextProvider:
return updateContextProvider(workInProgress);
default:
console.log('beginWork 未实现的类型');
break;
}
return null;
};
updateContextProvider
中:
function updateContextProvider(workInProgress) {
const providerType = workInProgress.type;
const context = providerType._context;
const newProps = workInProgress.pendingProps;
pushProvider(context, newProps.value);
const nextChildren = newProps.children;
reconcileChildren(workInProgress, nextChildren);
return workInProgress.child;
}
completeWork
中:
export const completeWork = workInProgress => {
const newProps = workInProgress.pendingProps;
const current = workInProgress.alternate;
switch (workInProgress.tag) {
case HostComponent:
……
bubbleProperties(workInProgress);
return null;
case HostText:
……
bubbleProperties(workInProgress);
return null;
case HostRoot:
case Fragment:
case FunctionComponent:
bubbleProperties(workInProgress);
return null;
case ContextProvider:
const context = workInProgress.type._context;
popProvider(context);
bubbleProperties(workInProgress);
return;
default:
console.log('completeWork 未实现的类型');
break;
}
};
fiberContext
中
let prevContextValue = null;
const prevContextValueStack = [];
function pushProvider(context, newValue) {
prevContextValueStack.push(prevContextValue);
prevContextValue = context._currentValue;
context._currentValue = newValue;
}
function popProvider(context) {
context._currentValue = prevContextValue;
prevContextValue = prevContextValueStack.pop();
}
五、Context 消费
5.1 Consumer
5.2 useContext
mount
阶段 useContext
逻辑如下:
/**
* @description: Mount 阶段 Hooks 实现
*/
const HooksDispatcherOnMount = {
useRef: mountRef,
useState: mountState,
useEffect: mountEffect,
useContext: readContext,
useTransition: mountTransition
};
function readContext(context) {
const consumer = currentlyRenderingFiber;
if (consumer == null) {
throw new Error('请在函数组件内调用 useContext');
}
const value = context._currentValue;
return value;
}
update
阶段 useContext
实现逻辑如下:
/**
* @description: Update 阶段 Hooks 实现
*/
const HooksDispatcherOnUpdate = {
useRef: updateRef,
useState: updateState,
useEffect: updateEffect,
useContext: readContext,
useTransition: updateTransition
};
function readContext(context) {
const consumer = currentlyRenderingFiber;
if (consumer == null) {
throw new Error('请在函数组件内调用 useContext');
}
const value = context._currentValue;
return value;
}
可以看出, mount
阶段的 useContext
和 update
阶段的 useContext
实现逻辑是一致的。
六、Context 兼容 bailout
Context
兼容 bailout
性能优化策略: 在 Context
场景 下, 需要提前标记 Provider
到 Consumer
之间的 childLanes
。那么, 怎么知道哪些是 Consumer
呢? 也就是说, React
是怎么是知道哪些组件消费了 Context
呢? React
是通过 fiber
中的 dependencies
属性存储当前组件所有依赖的 Context
, 所有依赖的 Context
会形成单向链表。
因此, 在 Context
场景下, 标记 Provider
到 Consumer
之间的 childLanes
的过程为: 从 Provider
开始, 沿途向下进行深度优先遍历, 寻找对应节点的 fiber.dependencies
是否包含此次更新的 Context
, 随后标记 fiber.childLanes
6.1 数据结构
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
}
6.2 初始化赋值
在 /packages/react-reconciler/fiber.js
的 createWorkInProgress()
中, 新增如下逻辑:
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;
}
6.3 存储依赖项
React
在什么时候将 context
存储到 fiber.dependencies
中呢? React
在消费 context
的处理逻辑中, 同时将 context
存储到 fiber.dependencies
中。
-
Consumer
: -
useContext
:useContext
使用readContext
读取Context
数据, 因此在readContext
中处理
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;
}
6.4 寻找依赖项
从 Context.Provider
出发, 沿途深度优先遍历寻找 fiber.dependencies
中是否含有此次对应的 Context
, 并沿途向上标记 childLanes
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;
}
}