React context
Created: February 9, 2022 10:52 AM Tag: React Updated: February 9, 2022 4:59 PM
源码版本V17.3,不讨论lazyContext、observedBits(react官方已移除)、Suspend、并发context。
默认你知道 fiber 架构。
Context的功能
Context 提供了一种在组件之间共享值的方式,而不必显式地通过组件树的逐层传递 props。
Context的原理
生产者、消费者模式。
- 生产者:
Provider - 消费者:使用
context的组件。
Context数据结构
ReactContext
// Context数据结构
type ReactContext<T> = {
$$typeof: Symbol | number,
// Context本身会作为Consumer(消费者)
// 意味着,Context就是ReactElement
Consumer: ReactContext<T>,
// 提供者 ReactElement
Provider: ReactProviderType<T>,
// 值
_currentValue: T,
// 支持并发
_currentValue2: T,
// 支持并发
_threadCount: number,
};
// Provider也是ReactElement
type ReactProviderType<T> = {
$$typeof: Symbol | number,
_context: ReactContext<T>,
};
Dependencies
使用 Context 时, fiber 会在 dependencies 上记录此依赖项,方便 context 变动时,更新 fiber 的值。
// 依赖
type Dependencies = {
// 优先级
lanes: Lanes,
// 第一个context
firstContext: ContextDependency<mixed> | null,
};
// context依赖
type ContextDependency<T> = {
// context
context: ReactContext<T>,
// 下一个 ContextDependency
next: ContextDependency<mixed> | null,
// 值
memoizedValue: T,
};
type Fiber ={
// 依赖项(context、event)。
dependencies: Dependencies | null,
}
创建Context
通过 React.createContext 创建 Context 。
Function createContext<T>(defaultValue: T): ReactContext<T> {
// 上面有数据结构有
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
Provider: (null: any),
Consumer: (null: any),
_currentValue: defaultValue,
_currentValue2: defaultValue,
_threadCount: 0,
};
// Provider
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
// Consumer 就是 context自己
context.Consumer = context;
return context;
}
Context的值保存在_currentValue。- 创建
Provider、Consumer2个ReactElement对象。
Consumer 实际已经很少使用了,现在使用 useContext 。
用法复习
const Context = React.createContext();
function App() {
const [v, setV] = useState('')
return (
<Context.Provider value={v}>
<Read />
</Context.Provider>
);
}
function Read() {
const v = useContext(Context)
return v
}
Provider Component — 提供value
来看组件源码。
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const providerType: ReactProviderType<any> = workInProgress.type;
const context: ReactContext<any> = providerType._context;
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
// 取新context的value
const newValue = newProps.value;
pushProvider(workInProgress, context, newValue);
if (enableLazyContextPropagation) {
// lzay模式
} else {
if (oldProps !== null) {
const oldValue = oldProps.value;
// 用Object.is对比value。 (比全等更严格)
if (is(oldValue, newValue)) {
// value 且 children 没变化,不更新。
if (
oldProps.children === newProps.children &&
!hasLegacyContextChanged()
) {
// 性能优化,复用fiber,bailout
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
}
} else {
// 通知所有依赖此context的fiber,进行更新。
propagateContextChange(workInProgress, context, renderLanes);
}
}
}
const newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
Probider不仅会全等比较value,还会对比children。- 优化注意:
children用父组件传值来保证不变。value若是对象,函数保证引用不变。
- 优化注意:
value变动,会使所有依赖此context的组件,进行更新。
下面接着看安排更新的函数逻辑。
propagateContextChange
为子组件中,所有依赖此 Context 的组件安排一个更新。
// dfs 子组件fiber树,为依赖此组件的安排lanes,准备更新
function propagateContextChange_eager<T>(
workInProgress: Fiber,
context: ReactContext<T>,
renderLanes: Lanes,
): void {
// 检查Provider的 子节点
let fiber = workInProgress.child;
if (fiber !== null) {
fiber.return = workInProgress;
}
// dfs子树,为所有依赖此context的fiber, 安排更新
// 注意: 若子组件的上层,将同一个Context 的 Provider 使用多次,只有最近的Provider影响子组件。
while (fiber !== null) {
let nextFiber;
// context依赖
const list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;
let dependency = list.firstContext;
// 发现依赖此context,则安排fiber更新
while (dependency !== null) {
// 检测context是否匹配
if (dependency.context === context) {
// 匹配,为fiber调度更新
if (fiber.tag === ClassComponent) {
// Class组件
const lane = pickArbitraryLane(renderLanes);
// 创建update
const update = createUpdate(NoTimestamp, lane);
// 强制更新
update.tag = ForceUpdate;
const updateQueue = fiber.updateQueue;
// update插入updateQueue,且构成环形链表
if (updateQueue === null) {
// 只有在fiber已经卸载的情况下才会发生。 此时无逻辑
} else {
const sharedQueue: SharedQueue<any> = (updateQueue: any).shared;
const pending = sharedQueue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
}
// 将rednerLanes进行merge到fiber.lanes
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
if (alternate !== null) {
// alternate做备份
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
// 从fiber的父节点一直安排到lane Provider节点
// 因为是dfs,所以向上回溯。
scheduleContextWorkOnParentPath(
fiber.return,
renderLanes,
workInProgress,
);
// dependencies设置lanes
list.lanes = mergeLanes(list.lanes, renderLanes);
// 安排更新结束。break
break;
}
// 下一个context
dependency = dependency.next;
}
} else if (fiber.tag === ContextProvider) {
// dfs发现是Provider组件且还是此Context的,终止DFS
// 这是为了:若子组件的上层,将同一个Context 的 Provider 使用多次,只有最近的Provider影响子组件。
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
} else if (
enableSuspenseServerRenderer &&
fiber.tag === DehydratedFragment
) {
// 服务端渲染的逻辑,也是安排一个lanes任务
const parentSuspense = fiber.return;
parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
const alternate = parentSuspense.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
scheduleContextWorkOnParentPath(
parentSuspense,
renderLanes,
workInProgress,
);
nextFiber = fiber.sibling;
} else {
// dfs 子节点
nextFiber = fiber.child;
}
// 判断无child,有则继续
// 无则 寻找sibling,parent节点。
if (nextFiber !== null) {
nextFiber.return = fiber;
} else {
// dfs寻找sibling,return(parent)节点
nextFiber = fiber;
while (nextFiber !== null) {
if (nextFiber === workInProgress) {
nextFiber = null;
break;
}
const sibling = nextFiber.sibling;
if (sibling !== null) {
sibling.return = nextFiber.return;
nextFiber = sibling;
break;
}
nextFiber = nextFiber.return;
}
}
fiber = nextFiber;
}
}
Context会为所有使用它的子组件安排更新。Context会强制安排更新,无视Pure,Memo,shouComponentUpdate等优化逻辑。- 若子组件的上层,将同一个
Context的Provider使用多次,只有最近的Provider影响子组件。
使用Context
实际ClassComponent、FunctionComponent 都按顺序调用 prepareToReadContext(准备去读Context)和readContext(读Context)。
prepareToReadContext — 准备读取Context
读取 Context 前的准备工作。
function prepareToReadContext(
workInProgress: Fiber,
renderLanes: Lanes,
): void {
// 全局变量记录当前fiber, 为readContext做准备
currentlyRenderingFiber = workInProgress;
lastContextDependency = null;
lastFullyObservedContext = null;
// fiber的context依赖链表
const dependencies = workInProgress.dependencies;
if (dependencies !== null) {
if (enableLazyContextPropagation) {
// Reset the work-in-progress list
dependencies.firstContext = null;
} else {
const firstContext = dependencies.firstContext;
if (firstContext !== null) {
// dependencies标记
if (includesSomeLane(dependencies.lanes, renderLanes)) {
// Context有lanes,标记组件进行更新
markWorkInProgressReceivedUpdate();
}
// 清空dependencies链表
dependencies.firstContext = null;
}
}
}
}
- 若
Context有更新,标记组件进行更新。 - 清空
fiber依赖,因为会重新读。
readContext — 读取Context
在fiber上记录Context依赖。
// useContext,实际调用了readContext
function readContext<T>(context: ReactContext<T>): T {
const value = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
if (lastFullyObservedContext === context) {
// Nothing to do. We already observe everything in this context.
} else {
// 链表节点
const contextItem = {
context: ((context: any): ReactContext<mixed>),
memoizedValue: value,
next: null,
};
// 将依赖Dependency构成一个链表,添加到fiber上
if (lastContextDependency === null) {
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies = {
lanes: NoLanes,
firstContext: contextItem,
};
} else {
lastContextDependency = lastContextDependency.next = contextItem;
}
}
return value;
}
- 使用
Context,会记录在Dependency中。
ContextConsumer
其实这个现在已经很少用了,不推荐使用。
逻辑是一样的,prepareToReadContext 后,readContext ,将value传入render 。
function updateContextConsumer(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
let context: ReactContext<any> = workInProgress.type;
const newProps = workInProgress.pendingProps;
const render = newProps.children;
prepareToReadContext(workInProgress, renderLanes);
const newValue = readContext(context);
let newChildren = render(newValue);
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
总结 — Summary
Probider组件优化,不仅会全等比较value,还会对比children。- 优化注意:
children用父组件传值来保证不变。value若是对象,函数保证引用不变。
- 优化注意:
Probider的value变动,会使所有依赖此Context的子组件,进行更新。- 强制安排更新,无视
Pure,Memo,shouComponentUpdate等优化逻辑。
- 强制安排更新,无视
- 若子组件的上层,将同一个
Context的Provider使用多次,只有最近的Provider影响子组件。 - 使用
Context,会记录fiber在Dependency中。