跳到主要内容

认识

一、认识


二、细节


  1. Context 创建, 定义数据结构

  2. Context 实现

  3. Provider 实现

  4. Context 消费实现

  5. 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 需要实现两部分逻辑:

  1. ContextProvider 类型 FiberNode 的支持

  2. Context 逻辑实现:

    1. 支持 context._currentValue 的变化

    2. 嵌套的 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 阶段的 useContextupdate 阶段的 useContext 实现逻辑是一致的。

六、Context 兼容 bailout


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

因此, 在 Context 场景下, 标记 ProviderConsumer 之间的 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.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;
}

6.3 存储依赖项

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;
}

6.4 寻找依赖项

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;
}
}