跳到主要内容

模拟实现

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

一、实现


1.1 /packages/react-reconciler/beginWork.js beginWork()

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.2 /packages/react-reconciler/beginWork.js updateFunctionComponent()

function updateFunctionComponent(workInProgress, Component, renderLane) {
prepareToReadContext(workInProgress, renderLane);
const nextChildren = renderWithHooks(workInProgress, Component, renderLane);
const current = workInProgress.alternate;
if (current != null && !didReceiveUpdate) {
bailoutHook(workInProgress, renderLane);
return bailoutOnAlreadyFinishedWork(workInProgress, renderLane);
}
reconcileChildren(workInProgress, nextChildren);
return workInProgress.child;
}

1.3 /packages/react-reconciler/fiberHooks.js renderWithHooks()

export function renderWithHooks(workInProgress, Component, lane) {
renderLane = lane;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;

const current = workInProgress.alternate;

if (current !== null) {
// update
currentDispatcher.current = HooksDispatcherOnUpdate;
} else {
// mount
currentDispatcher.current = HooksDispatcherOnMount;
}

const props = workInProgress.pendingProps;
const children = Component(props);

currentHook = null;
renderLane = NoLane;
workInProgressHook = null;
currentlyRenderingFiber = null;

return children;
}

1.4 /packages/react-reconciler/fiberHooks.js

/**
* @description: Mount 阶段 Hooks 实现
*/
const HooksDispatcherOnMount = {
use,
useRef: mountRef,
useMemo: mountMemo,
useState: mountState,
useEffect: mountEffect,
useContext: readContext,
useCallback: mountCallback,
useTransition: mountTransition
};

/**
* @description: Update 阶段 Hooks 实现
*/
const HooksDispatcherOnUpdate = {
use,
useRef: updateRef,
useMemo: updateMemo,
useState: updateState,
useEffect: updateEffect,
useContext: readContext,
useCallback: updateCallback,
useTransition: updateTransition
};

1.5 /packages/react-reconciler/fiberHooks.js

function pushEffect(hookFlags, create, destroy, deps) {
const effect = {
deps,
create,
destroy,
next: null,
tag: hookFlags
};

const fiber = currentlyRenderingFiber;
const updateQueue = fiber.updateQueue;
if (updateQueue == null) {
const updateQueue = createFCUpdateQueue();
fiber.updateQueue = updateQueue;
effect.next = effect;
updateQueue.lastEffect = effect;
} else {
const lastEffect = updateQueue.lastEffect;
if (lastEffect == null) {
effect.next = effect;
updateQueue.lastEffect = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
updateQueue.lastEffect = effect;
}
}
return effect;
}

function mountEffect(create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= PassiveEffect;
hook.memoizedState = pushEffect(
Passive | HookHasEffect,
create,
undefined,
nextDeps
);
}

function updateEffect(create, deps) {
let destroy;
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;

if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(Passive, create, destroy, nextDeps);
return;
}
}

currentlyRenderingFiber.flags |= PassiveEffect;
hook.memoizedState = pushEffect(
Passive | HookHasEffect,
create,
destroy,
nextDeps
);
}
}

1.6 /packages/react-reconciler/completeWork.js completeWork()

export const completeWork = workInProgress => {
const newProps = workInProgress.pendingProps;
const current = workInProgress.alternate;

switch (workInProgress.tag) {
case HostComponent:
if (current != null && workInProgress.stateNode) {
updateFiberProps(workInProgress.stateNode, newProps);

if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
const instance = createInstance(workInProgress.type, newProps);
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;

if (workInProgress.ref !== null) {
markRef(workInProgress);
}
}

bubbleProperties(workInProgress);
return null;
case HostText:
if (current != null && workInProgress.stateNode) {
const oldText = current.memoizedProps.content;
const newText = newProps.content;

if (oldText !== newText) {
markUpdate(workInProgress);
}
} else {
const instance = createTextInstance(newProps.content);
workInProgress.stateNode = instance;
}
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;
case SuspenseComponent:
popSuspenseHandler();
const offscreenFiber = workInProgress.child;
const isHidden = offscreenFiber.pendingProps.mode === 'hidden';
const currentOffscreenFiber = offscreenFiber.alternate;
if (currentOffscreenFiber != null) {
const wasHidden = currentOffscreenFiber.pendingProps.mode === 'hidden';
if (isHidden !== wasHidden) {
offscreenFiber.flags |= Visibility;
bubbleProperties(offscreenFiber);
}
} else if (isHidden) {
offscreenFiber.flags |= Visibility;
bubbleProperties(offscreenFiber);
}
bubbleProperties(workInProgress);
return;
case OffscreenComponent:
bubbleProperties(workInProgress);
return;
case MemoComponent:
bubbleProperties(workInProgress);
return;
default:
console.log('completeWork 未实现的类型');
break;
}
};

1.7 /packages/react-reconciler/completeWork.js bubbleProperties()

function bubbleProperties(workInProgress) {
let subtreeFlags = NoFlags;
let child = workInProgress.child;
let newChildLanes = NoLanes;

while (child != null) {
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;

newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes)
);

child.return = workInProgress;
child = child.sibling;
}

workInProgress.subtreeFlags |= subtreeFlags;
workInProgress.childLanes = newChildLanes;
}

1.8 /packages/react-reconciler/workLoop.js commitRoot()

function commitRoot(root) {
const finishedWork = root.finishedWork;

if (finishedWork == null) {
return;
}

const lane = root.finishedLane;

if (lane === NoLane) {
console.log('commitRoot root.finishedLane 不应该是 NoLane');
}

root.finishedWork = null;
root.finishedLane = NoLane;
markRootFinished(root, lane);

if (
(finishedWork.flags & PassiveMask) !== NoFlags ||
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags
) {
if (!rootDoesHasPassiveEffects) {
rootDoesHasPassiveEffects = true;
scheduleCallback(NormalPriority, () => {
flushPassiveEffects(root.pendingPassiveEffects);
return;
});
}
}

/**
* @description: 判断是否存在三个子阶段需要执行的操作
*/
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
const subtreeHasEffects =
(finishedWork.subtreeFlags & MutationMask) !== NoFlags;

if (rootHasEffect || subtreeHasEffects) {
commitMutationEffects(finishedWork, root);
root.current = finishedWork;
commitLayoutEffects(finishedWork, root);
} else {
root.current = finishedWork;
}

rootDoesHasPassiveEffects = false;
ensureRootIsScheduled(root);
}

1.9 /packages/react-reconciler/commitWork.js commitMutationEffects()

export const commitMutationEffects = commitEffects(
'mutation',
MutationMask | PassiveMask,
commitMutationEffectsOnFiber
);

1.10 /packages/react-reconciler/commitWork.js commitMutationEffectsOnFiber()

export function commitMutationEffectsOnFiber(finishedWork, root) {
const { tag, flags } = finishedWork;

if ((flags & Placement) !== NoFlags) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}

if ((flags & ChildDeletion) !== NoFlags) {
const deletions = finishedWork.deletions;
if (deletions != null) {
deletions.forEach(childToDelete => {
commitDeletion(childToDelete, root);
});
}
finishedWork.flags &= ~ChildDeletion;
}

if ((flags & Update) !== NoFlags) {
commitUpdate(finishedWork);
finishedWork.flags &= ~Update;
}

if ((flags & PassiveEffect) !== NoFlags) {
commitPassiveEffect(finishedWork, root, 'update');
finishedWork.flags &= ~PassiveEffect;
}

if ((flags & Ref) !== NoFlags && tag === HostComponent) {
safelyDetachRef(finishedWork);
}

if ((flags & Visibility) !== NoFlags && tag === OffscreenComponent) {
const isHidden = finishedWork.pendingProps.mode === 'hidden';
hideOrUnhideAllChildren(finishedWork, isHidden);
finishedWork.flags &= ~Visibility;
}
}

1.11 /packages/react-reconciler/commitWork.js commitPassiveEffect()

export function commitPassiveEffect(fiber, root, type) {
if (
fiber.tag !== FunctionComponent ||
(type === 'update' && (fiber.flags & PassiveEffect) === NoFlags)
) {
return;
}

const updateQueue = fiber.updateQueue;
if (updateQueue !== null) {
if (updateQueue.lastEffect == null) {
console.log(
'当 FunctionComponent 存在 PassiveEffect Flag 时, 不应该不存在 lastEffect'
);
}
root.pendingPassiveEffects[type].push(updateQueue.lastEffect);
}
}

1.12 /packages/react-reconciler/commitWork.js commitDeletion()

export function commitDeletion(childToDelete, root) {
const rootChildrenToDelete = [];

commitNestedComponent(childToDelete, unmountFiber => {
switch (unmountFiber.tag) {
case HostComponent:
recordHostChildrenToDelete(rootChildrenToDelete, unmountFiber);
safelyDetachRef(unmountFiber);
return;
case HostText:
recordHostChildrenToDelete(rootChildrenToDelete, unmountFiber);
return;
case FunctionComponent:
commitPassiveEffect(unmountFiber, root, 'unmount');
return;
default:
console.log('commitDeletion 未实现的类型');
break;
}
});

if (rootChildrenToDelete.length) {
const hostParent = getHostParent(childToDelete);

if (hostParent != null) {
rootChildrenToDelete.forEach(node => {
removeChild(node.stateNode, hostParent);
});
}
}

childToDelete.return = null;
childToDelete.child = null;
}

1.13 /packages/react-reconciler/workLoop.js flushPassiveEffects()

function flushPassiveEffects(pendingPassiveEffects) {
let didFlushPassiveEffect = false;

pendingPassiveEffects.unmount.forEach(effect => {
didFlushPassiveEffect = true;
commitHookEffectListUnmount(Passive, effect);
});
pendingPassiveEffects.unmount = [];
pendingPassiveEffects.update.forEach(effect => {
didFlushPassiveEffect = true;
commitHookEffectListDestroy(Passive | HookHasEffect, effect);
});
pendingPassiveEffects.update.forEach(effect => {
didFlushPassiveEffect = true;
commitHookEffectListCreate(Passive | HookHasEffect, effect);
});
pendingPassiveEffects.update = [];
flushSyncCallbacks();
return didFlushPassiveEffect;
}

二、测试


<!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</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: { createElement, useState, useEffect, Fragment }
} = React;

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

useEffect(() => {
console.log('App Mount');
setValue(value + 1);
}, []);

useEffect(() => {
console.log('num change create', value);
return () => {
console.log('num change destroy', value);
};
}, [value]);

return createElement('div', {}, value);
}

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