跳到主要内容

模拟实现

2023年06月11日
柏拉文
越努力,越幸运

一、实现


1.1 packages/react/context.js

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

return context;
}

1.2 packages/react-reconciler/beginWork.js

function updateContextProvider(workInProgress, renderLane) {
const providerType = workInProgress.type;
const context = providerType._context;
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
const newValue = newProps.value;

pushProvider(context, newProps.value);

if (oldProps !== null) {
const oldValue = oldProps.value;
if (
Object.is(oldValue, newValue) &&
oldProps.children === newProps.children
) {
return bailoutOnAlreadyFinishedWork(workInProgress, renderLane);
} else {
propagateContextChange(workInProgress, context, renderLane);
}
}

const nextChildren = newProps.children;
reconcileChildren(workInProgress, nextChildren);
return workInProgress.child;
}

export const beginWork = (workInProgress, renderLane) => {
// bailout 策略
didReceiveUpdate = false;
const current = workInProgress.alternate;

if (current != null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (oldProps !== newProps || current.type !== workInProgress.type) {
didReceiveUpdate = true;
} else {
const hasScheduledStateOrContext = checkScheduledUpdateOrContext(
current,
renderLane
);
if (!hasScheduledStateOrContext) {
didReceiveUpdate = false;

switch (workInProgress.tag) {
case ContextProvider:
const newValue = workInProgress.memoizedProps.value;
const context = workInProgress.type._context;
pushProvider(context, newValue);
break;
default:
break;
}

return bailoutOnAlreadyFinishedWork(workInProgress, renderLane);
}
}
}

workInProgress.lanes = NoLanes;

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,
workInProgress.type,
renderLane
);
case ContextProvider:
return updateContextProvider(workInProgress, renderLane);
case SuspenseComponent:
return updateSuspenseComponent(workInProgress);
case OffscreenComponent:
return updateOffscreenComponent(workInProgress);
case MemoComponent:
return updateMemoComponent(workInProgress, renderLane);
default:
console.log('beginWork 未实现的类型');
break;
}
return null;
};

1.3 packages/react-reconciler/completeWork.js

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:
……
case ContextProvider:
const context = workInProgress.type._context;
popProvider(context);
bubbleProperties(workInProgress);
return;
default:
console.log('completeWork 未实现的类型');
break;
}
};

1.4 packages/react-reconciler/fiber.js

export class FiberNode {
constructor(tag, pendingProps, key) {
// 存储节点的类型、实例、状态
this.tag = tag;
this.key = key || null;
this.type = null;
this.stateNode = null;

// 存储节点的父节点、子节点、兄弟节点信息
this.return = null;
this.sibling = null;
this.child = null;
this.index = 0;

// 存储节点的 ref 信息
this.ref = null;
this.refCleanup = null;

// 存储节点的 props、state、更新队列、依赖项信息
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.memoizedState = null;
this.updateQueue = null;
this.dependencies = null;

this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;

// 存储一个 fiberNode 中所有未执行的更新对应的 lane
this.lanes = NoLanes;
// 存储一个 fiberNode 子树中所有未执行更新对应的 lane
this.childLanes = NoLanes;

// 存储节点的双缓存信息
this.alternate = null;
}
}

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

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') {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break;
default:
break;
}
} else if (type === REACT_SUSPENSE_TYPE) {
fiberTag = SuspenseComponent;
} 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;
}

1.5 packages/react-reconciler/fiberContext.js

let prevContextValue = null;
const prevContextValueStack = [];

export function pushProvider(context, newValue) {
prevContextValueStack.push(prevContextValue);
prevContextValue = context._currentValue;
context._currentValue = newValue;
}

export function popProvider(context) {
context._currentValue = prevContextValue;
prevContextValue = prevContextValueStack.pop();
}

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

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

二、测试


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Mini KaSong useContext</title>
<script>
var process = {
env: {
NODE_ENV: 'development'
}
};
</script>
<script src="../dist/react.iife.js"></script>
</head>
<body>
<div id="root"></div>

<script>
const {
ReactDOM: { createRoot },
React: { useState, useContext, useEffect, createContext, createElement }
} = React;

const context = createContext();

function Child() {
const ctx = useContext(context);
return createElement('div', null, ctx.a);
}

function App() {
const [value, setValue] = useState(0);

return createElement(
'div',
{
onClick: () => setValue(value + 1)
},
createElement(
context.Provider,
{ value: { a: value } },
createElement(Child)
)
);
}

const root = createRoot(document.getElementById('root'));
root.render(createElement(App));
</script>
</body>
</html>