源码解析
React Hooks 源码解析
今天我将会深入
首先,让我们进入需要确保

dispatcher
当我们执行完渲染工作时,我们将
let currentDispatcher;
const dispatcherWithoutHooks = {
/* ... */
};
const dispatcherWithHooks = {
/* ... */
};
function resolveDispatcher() {
if (currentDispatcher) return currentDispatcher;
throw Error("Hooks can't be called");
}
The hooks queue
在使用场景之后,
-
它的初始状态在首次渲染时被创建。
-
她的状态可以即时更新。
-
React 会在之后的渲染中记住hook 的状态。 -
React 会根据调用顺序为您提供正确的状态 -
React 会知道这个hook 属于哪个Fiber 。
因此,我们需要重新思考我们查看组件状态的方式。到目前为止,我们认为它就像是一个普通的对象:
{
"foo": "foo",
"bar": "bar",
"baz": "baz"
}
但是在处理
{
memoizedState: 'foo',
next: {
memoizedState: 'bar',
next: {
memoizedState: 'bar',
next: null
}
}
可以在实现中查看单个
-
baseState - 将给予reducer 的状态对象。 -
baseUpdate- 最近的创建了最新baseState 的调度操作。 -
queue - 调度操作的队列,等待进入reducer 。
回到
let currentlyRenderingFiber;
let workInProgressQueue;
let currentHook;
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:123
function prepareHooks(recentFiber) {
currentlyRenderingFiber = workInProgressFiber;
currentHook = recentFiber.memoizedState;
}
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:148
function finishHooks() {
currentlyRenderingFiber.memoizedState = workInProgressHook;
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
}
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:115
function resolveCurrentlyRenderingFiber() {
if (currentlyRenderingFiber) return currentlyRenderingFiber;
throw Error("Hooks can't be called");
}
// Source: https://github.com/facebook/react/tree/5f06576f51ece88d846d01abd2ddd575827c6127/react-reconciler/src/ReactFiberHooks.js:267
function createWorkInProgressHook() {
workInProgressHook = currentHook ? cloneHook(currentHook) : createNewHook();
currentHook = currentHook.next;
workInProgressHook;
}
function useXXX() {
const fiber = resolveCurrentlyRenderingFiber();
const hook = createWorkInProgressHook();
// ...
}
function updateFunctionComponent(
recentFiber,
workInProgressFiber,
Component,
props
) {
prepareHooks(recentFiber, workInProgressFiber);
Component(props);
finishHooks();
}
一旦更新完成,一个叫做
const ChildComponent = () => {
useState("foo");
useState("bar");
useState("baz");
return null;
};
const ParentComponent = () => {
const childFiberRef = useRef();
useEffect(() => {
let hookNode = childFiberRef.current.memoizedState;
assert(hookNode.memoizedState, "foo");
hookNode = hooksNode.next;
assert(hookNode.memoizedState, "bar");
hookNode = hooksNode.next;
assert(hookNode.memoizedState, "baz");
});
return <ChildComponent ref={childFiberRef} />;
};
常见Hooks 的实现
State hooks
function basicStateReducer(state, action) {
return typeof action === "function" ? action(state) : action;
}
正如预期的那样,我们可以直接为
const ParentComponent = () => {
const [name, setName] = useState();
return <ChildComponent toUpperCase={setName} />;
};
const ChildComponent = (props) => {
useEffect(() => {
props.toUpperCase((state) => state.toUpperCase());
}, [true]);
return null;
};
Effect hooks
- 它们是在渲染时创建的,但它们在绘制后运行。
- 它们将在下一次绘制之前被销毁。
- 它们按照已经被定义的顺序执行。
因此,应该有另一个额外的队列保持这些
- 在变化之前调用实例的
getSnapshotBeforeUpdate() 方法。 - 执行所有节点的插入,更新,删除和
ref 卸载操作。 - 执行所有生命周期和
ref 回调。生命周期作为单独的过程发生,因此整个树中的所有放置,更新和删除都已经被调用。此过程还会触发任何特定渲染的初始effects 。 - 由
useEffect() hook 安排的effects - 基于实现也被称为“passive effects”。
当涉及到
tag - 一个二进制数,它将决定effect 的行为。create- 绘制后应该运行的回调。destroy- 从create() 返回的回调应该在初始渲染之前运行。inputs - 一组值,用于确定是否应销毁和重新创建effect 。next - 函数组件中定义的下一个effect 的引用。
除了
const NoEffect = /* */ 0b00000000;
const UnmountSnapshot = /* */ 0b00000010;
const UnmountMutation = /* */ 0b00000100;
const MountMutation = /* */ 0b00001000;
const UnmountLayout = /* */ 0b00010000;
const MountLayout = /* */ 0b00100000;
const MountPassive = /* */ 0b01000000;
const UnmountPassive = /* */ 0b10000000;
这些二进制值的最常见用例是使用管道(|)将这些位按原样添加到单个值。然后我们可以使用&符号(&)检查标签是否实现某种行为。如果结果为非零,则表示
- Default effect — UnmountPassive | MountPassive.
- Mutation effect — UnmountSnapshot | MountMutation.
- Layout effect — UnmountMutation | MountLayout.
以下是
if ((effect.tag & unmountTag) !== NoHookEffect) {
// Unmount
}
if ((effect.tag & mountTag) !== NoHookEffect) {
// Mount
}
因此,基于我们刚刚学到的关于
function injectEffect(fiber) {
const lastEffect = fiber.updateQueue.lastEffect;
const destroyEffect = () => {
console.log("on destroy");
};
const createEffect = () => {
console.log("on create");
return destroy;
};
const injectedEffect = {
tag: 0b11000000,
next: lastEffect.next,
create: createEffect,
destroy: destroyEffect,
inputs: [createEffect],
};
lastEffect.next = injectedEffect;
}
const ParentComponent = <ChildComponent ref={injectEffect} />;