前言
在 React
中,Context
是一种高效的状态共享机制,能够避免组件层级较深时多层级props
传递带来的问题。
通过Context
,我们可以在组件树中共享数据,如主题、语言、认证信息等,而无需逐层手动传递。这种机制不仅提升了代码的可读性,还大幅简化了状态管理的复杂度。
React
的Context API
自 16.3 版本引入后,经历了多次改进,其中包括性能优化和更灵活的使用方式。在最新版本中(React 18+),通过引入enableLazyContextPropagation
等机制,Context
的性能和更新逻辑得到了显著提升。本文将从基础概念、源码分析和性能优化等方面,深入探讨React
中的Context
。
Context 基本使用
Context
的核心是通过 CreateConext
来创建一个 Context
,然后提供 Provider
,
供函数式组件 useContext
或者更古老的方式使用 Consumer
来读取共享数据,以下是一个简单的示例。
type TypeTheme = 'light' | 'dark'
const ThemeContext = createContext<{ theme: TypeTheme }>({
theme: 'light',
})
const InnerComponent = () => {
const { theme } = useContext(ThemeContext)
return <>theme is {theme}</>
}
class OtherComponent extends Component<{ theme: TypeTheme }> {
render() {
return <>{this.props.theme}</>
}
}
const App = () => {
return (
<ThemeContext.Provider value={{ theme }}>
<InnerComponent></InnerComponent>
<ThemeContext.Consumer>
{({ theme }) => {
return <OtherComponent theme={theme}></OtherComponent>
}}
</ThemeContext.Consumer>
</ThemeContext.Provider>
)
}
Context 底层实现
CreateConext
源码时创建一个 REACT_CONTEXT_TYPE
的对象,它的数据结构是:
function createContext<T>(defaultValue: T): ReactContext<T> {
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_currentValue: defaultValue,
_currentValue2: defaultValue,
_threadCount: 0,
Provider: (null: any),
Consumer: (null: any),
};
if (enableRenderableContext) {
context.Provider = context;
context.Consumer = {
$$typeof: REACT_CONSUMER_TYPE,
_context: context,
};
} else {
// 省略
}
return context;
}
在源码流程中, 当 Context
更新时,通过 pushProvider
来更新 Context
对象上的值。
export function pushProvider<T>(
providerFiber: Fiber,
context: ReactContext<T>,
nextValue: T
): void {
if (isPrimaryRenderer) {
push(valueCursor, context._currentValue, providerFiber)
context._currentValue = nextValue
} else {
}
}
Context 更新流程
虽然 useContext
与 Consumer
在使用方式上有所区别,但是都是通过 readContext
来获取最新的值,而它们都是通过调用 readContextForConsumer
。
当 Context 是嵌套结构时,则是通过类似于链表结构串联起来 Context
。
function readContextForConsumer<C>(
consumer: Fiber | null,
context: ReactContext<C>,
): C {
const value = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
const contextItem = {
context: ((context: any): ReactContext<mixed>),
memoizedValue: value,
next: null,
};
if (lastContextDependency === null) {
if (consumer === null) {
throw new Error(
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
}
// This is the first dependency for this component. Create a new list.
lastContextDependency = contextItem;
consumer.dependencies = __DEV__
? {
lanes: NoLanes,
firstContext: contextItem,
_debugThenableState: null,
}
: {
lanes: NoLanes,
firstContext: contextItem,
};
if (enableLazyContextPropagation) {
consumer.flags |= NeedsPropagation;
}
} else {
lastContextDependency = lastContextDependency.next = contextItem;
}
return value;
}
当 Context
变化时,在 beginWork
流程中通过 checkScheduledUpdateOrContext
来完成判断走更新逻辑。
props
、Conetxt
以及更新优先级的变化,会导致组件的re-render
function checkScheduledUpdateOrContext(current: Fiber, renderLanes: Lanes): boolean {
// 判断更新优先级
const updateLanes = current.lanes
if (includesSomeLane(updateLanes, renderLanes)) {
return true
}
// 判断 Conext是否发生变化
if (enableLazyContextPropagation) {
const dependencies = current.dependencies
if (dependencies !== null && checkIfContextChanged(dependencies)) {
return true
}
}
return false
}
checkIfContextChanged
用来比较上下文的值是否更新。
export function checkIfContextChanged(currentDependencies: Dependencies): boolean {
if (!enableLazyContextPropagation) {
return false
}
let dependency = currentDependencies.firstContext
while (dependency !== null) {
const context = dependency.context
const newValue = isPrimaryRenderer ? context._currentValue : context._currentValue2
const oldValue = dependency.memoizedValue
if (
enableContextProfiling &&
dependency.select != null &&
dependency.lastSelectedValue != null
) {
if (
checkIfSelectedContextValuesChanged(
dependency.lastSelectedValue,
dependency.select(newValue)
)
) {
return true
}
} else {
if (!is(newValue, oldValue)) {
return true
}
}
dependency = dependency.next
}
return false
}
Context 的变化
相对于之前 Context
变化时,需要 DFS
遍历 Fiber
树,找到依赖的 Fiber
节点,然后创建更新/更新优先级。
最新的 Context
采用 enableLazyContextPropagation
1,减少 Context
不必要的更新传播,
降低了遍历所有 dependencies
带来的性能开销。
以下是早期遍历流程的代码片段:
function propagateContextChange_eager<T>(
workInProgress: Fiber,
context: ReactContext<T>,
renderLanes: Lanes,
): void {
if (enableLazyContextPropagation) {
return;
}
let fiber = workInProgress.child;
if (fiber !== null) {
fiber.return = workInProgress;
}
while (fiber !== null) {
let nextFiber;
// DFS 开启遍历找到依赖当前 Context 的节点,并创建更新/更新优先级
const list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;
let dependency = list.firstContext;
while (dependency !== null) {
if (dependency.context === context) {
if (fiber.tag === ClassComponent) {
const lane = pickArbitraryLane(renderLanes);
const update = createUpdate(lane);
update.tag = ForceUpdate;
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
} 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;
}
}
// 更新优先级
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
scheduleContextWorkOnParentPath(
fiber.return,
renderLanes,
workInProgress,
);
list.lanes = mergeLanes(list.lanes, renderLanes);
break;
}
dependency = dependency.next;
}
} else if (fiber.tag === ContextProvider) {
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
} else if (fiber.tag === DehydratedFragment) {
// 省略
} else {
nextFiber = fiber.child;
}
if (nextFiber !== null) {
nextFiber.return = fiber;
} else {
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;
}
}
而现在在组件更新时,根据 checkScheduledUpdateOrContext
判断是否更新可以更加颗粒化的走更新逻辑。
总结
Context
是 React
提供的高效共享状态的解决方案,
尽管最新版本通过 enableLazyContextPropagation
显著优化了更新的性能,减少了不必要的重渲染,但 Context
的值变化仍可能触发相关组件的重新渲染。
因此,在实际开发中,需谨慎管理 Context
的更新范围,避免在高频变化的场景中滥用全局状态。