跳到主要内容

模拟实现

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

一、实现


1.1 /packages/react-reconciler/fiberHooks.js

function mountWorkInProgressHook() {
const hook = {
next: null,
baseQueue: null,
baseState: null,
updateQueue: null,
memoizedState: null
};

if (workInProgressHook == null) {
if (currentlyRenderingFiber == null) {
throw new Error('请在函数组件内调用 Hook');
} else {
workInProgressHook = hook;
currentlyRenderingFiber.memoizedState = workInProgressHook;
}
} else {
workInProgressHook.next = hook;
workInProgressHook = hook;
}
return workInProgressHook;
}

function updateWorkInProgressHook() {
let nextCurrentHook = null;

if (currentHook == null) {
const current = currentlyRenderingFiber?.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}

if (nextCurrentHook == null) {
/**
* @description: nextCurrentHook 为 null
* 之前: u1 u2 u3
* 现在: u1 u2 u3 u4
* 原因:
* 1. if(){ u4 } => u4 放在了 If 语句中
*/
throw new Error('本次执行时的 Hook 比上次执行时多');
}

currentHook = nextCurrentHook;

const newHook = {
next: null,
baseState: currentHook.baseQueue,
baseQueue: currentHook.baseState,
updateQueue: currentHook.updateQueue,
memoizedState: currentHook.memoizedState
};

if (workInProgressHook == null) {
if (currentlyRenderingFiber == null) {
throw new Error('请在函数组件内调用 Hook');
} else {
workInProgressHook = newHook;
currentlyRenderingFiber.memoizedState = workInProgressHook;
}
} else {
workInProgressHook.next = newHook;
workInProgressHook = newHook;
}
return workInProgressHook;
}

function createFCUpdateQueue() {
const updateQueue = createUpdateQueue();
updateQueue.lastEffect = null;
return updateQueue;
}

/**
* @description: dispatchSetState
* @param {*} fiber dispatchSetState.bind 时已经传入
* @param {*} updateQueue dispatchSetState.bind 时已经传入
* @param {*} action : setState(value) || setValue(()=> value) 中的 value 或者 ()=> value
*/
function dispatchSetState(fiber, updateQueue, action) {
const lane = requestUpdateLane();
const update = createUpdate(action, lane);

// eagerState 策略
const current = fiber.alternate;
if (
fiber.lanes === NoLanes &&
(current == null || current.lanes === NoLanes)
) {
const currentState = updateQueue.lastRenderedState;
const eagerState = basicStateReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;

if (Object.is(currentState, eagerState)) {
enqueueUpdate(updateQueue, update, fiber, NoLane);
console.log('命中 eagerState 策略', fiber);
return;
}
}

enqueueUpdate(updateQueue, update, fiber, lane);
scheduleUpdateOnFiber(fiber, lane);
}

function mountState(initialState) {
const hook = mountWorkInProgressHook();

let memoizedState;

if (initialState instanceof Function) {
memoizedState = initialState();
} else {
memoizedState = initialState;
}

const queue = createFCUpdateQueue();
hook.updateQueue = queue;
hook.baseState = memoizedState;
hook.memoizedState = memoizedState;

const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
queue.dispatch = dispatch;
queue.lastRenderedState = memoizedState;

return [memoizedState, dispatch];
}

function updateState() {
const hook = updateWorkInProgressHook();

const queue = hook.updateQueue;
const baseState = hook.baseState;
const pending = queue.shared.pending;

const current = currentHook;
let baseQueue = current.baseQueue;

if (pending != null) {
if (baseQueue != null) {
const baseFirst = baseQueue.next;
const pendingFirst = pending.next;
baseQueue.next = pendingFirst;
pending.next = baseFirst;
}

baseQueue = pending;
current.baseQueue = pending;
queue.shared.pending = null;
}

if (baseQueue !== null) {
const prevState = hook.memoizedState;
const {
memoizedState,
baseQueue: newBaseQueue,
baseState: newBaseState
} = processUpdateQueue(baseState, baseQueue, renderLane, update => {
const skippedLane = update.lane;
const fiber = currentlyRenderingFiber;
fiber.lanes = mergeLanes(fiber.lanes, skippedLane);
});

if (!Object.is(prevState, memoizedState)) {
markWorkInProgressReceiveUpdate();
}

hook.memoizedState = memoizedState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueue;
queue.lastRenderedState = memoizedState;
}

return [hook.memoizedState, queue.dispatch];
}

/**
* @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
};

二、测试


<!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 src="../dist/react.iife.js"></script>
</head>
<body>
<div id="root"></div>

<script>
const {
ReactDOM: { createRoot },
React: { createElement, useState, Fragment }
} = React;

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

const handleClick = () => {
setValue(value => value + 1);
setValue(value => value + 1);
setValue(value => value + 1);
};

return createElement(
'div',
{
onClick: handleClick
},
value
);
}

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