Skip to main content

Hooks实现原理

React Hooks 的实现原理是基于链表结构闭包机制,通过严格的调用顺序来管理状态。以下是其核心原理的详细解析:

1. 基本数据结构:链表

每个函数组件内部维护一个 Fiber 节点,其中包含一个 memoizedState 属性,它是一个链表,每个节点对应一个 Hook:

// 简化的 Fiber 节点结构
const fiber = {
type: FunctionComponent, // 组件类型
stateNode: null, // 组件实例
memoizedState: null, // Hook 链表头节点
// ...
};

// 简化的 Hook 节点结构
const hook = {
memoizedState: initialState, // 状态值
baseState: null, // 基础状态(用于并发更新)
queue: null, // 更新队列
next: null, // 指向下一个 Hook 的指针
};

2. useState 的实现原理

(1) 首次渲染

  1. 创建一个 Hook 节点并添加到链表中。
  2. 执行初始状态计算(如果是函数,则调用该函数)。
  3. 返回状态值和更新函数。
// 简化的 useState 实现
function useState(initialState) {
// 获取当前 Fiber 节点
const fiber = getCurrentFiber();

// 从链表中获取或创建当前 Hook
let hook;
if (!fiber.memoizedState) {
// 首次渲染:创建新 Hook
hook = {
memoizedState: initialState,
queue: { pending: null },
next: null,
};
fiber.memoizedState = hook;
} else {
// 后续渲染:获取当前 Hook
hook = fiber.currentHook;
fiber.currentHook = hook.next;
}

// 处理更新队列
const queue = hook.queue;
if (queue.pending) {
// 计算新状态(合并所有更新)
let newState = hook.memoizedState;
let update = queue.pending.next; // 循环链表的第一个更新

do {
newState = update.action(newState);
update = update.next;
} while (update !== queue.pending.next);

hook.memoizedState = newState;
queue.pending = null;
}

// 返回状态和更新函数
const setState = (action) => {
// 创建新的更新并添加到队列
const update = {
action,
next: null,
};

// 将更新添加到循环链表
if (queue.pending === null) {
update.next = update; // 自己指向自己
} else {
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update;

// 调度重新渲染
scheduleUpdate();
};

return [hook.memoizedState, setState];
}

(2) 后续渲染

  1. 按顺序从链表中获取对应的 Hook 节点。
  2. 处理更新队列,计算新状态。
  3. 返回最新状态值和更新函数。

3. useEffect 的实现原理

(1) 数据结构

每个 useEffect 对应一个 Hook 节点,包含回调函数、依赖项和清理函数:

const effectHook = {
tag: 'effect', // 标记为 effect
create: effectCallback, // 副作用回调函数
destroy: null, // 清理函数(由 create 返回)
deps: dependencies, // 依赖项数组
next: null, // 指向下一个 effect
};

(2) 执行时机

  1. 首次渲染:将 effect 添加到队列,不立即执行。
  2. 提交阶段后:异步执行所有 effect(按顺序)。
  3. 依赖项变化:先执行上一次的清理函数,再执行新的 effect。
// 简化的 useEffect 实现
function useEffect(create, deps) {
const fiber = getCurrentFiber();
const hook = getCurrentHook();

// 比较依赖项
const prevDeps = hook.deps;
let hasChanged = true;

if (prevDeps) {
hasChanged = deps.some((dep, i) => dep !== prevDeps[i]);
}

if (hasChanged) {
// 保存新的 effect
hook.create = create;
hook.deps = deps;

// 将 effect 添加到 fiber 的 effects 链表
if (!fiber.effects) {
fiber.effects = [hook];
} else {
fiber.effects.push(hook);
}
}
}

// 提交阶段后执行所有 effects
function commitHookEffects() {
const fiber = getCurrentFiber();

// 执行所有 effect 的清理函数(如果有)
fiber.effects.forEach(effect => {
if (effect.destroy) {
effect.destroy();
}
});

// 执行所有 effect 的回调函数
fiber.effects.forEach(effect => {
effect.destroy = effect.create();
});
}

4. 严格的调用顺序

Hooks 依赖调用顺序来正确定位状态:

function Component() {
useState(1); // 第一个 Hook
useEffect(() => {}); // 第二个 Hook
useState(2); // 第三个 Hook

// React 通过调用顺序(1→2→3)来管理状态
}

为什么不能在条件语句中使用 Hook?

如果 Hook 调用顺序发生变化(如在条件语句中),会导致状态混乱:

function Component() {
useState(1); // 第一个 Hook

if (condition) {
useEffect(() => {}); // 可能是第二个或跳过
}

useState(2); // 取决于条件,可能是第二个或第三个
// 🔥 错误:调用顺序不一致,导致状态混乱
}

5. 闭包陷阱与解决方案

闭包陷阱示例

function Example() {
const [count, setCount] = useState(0);

useEffect(() => {
setInterval(() => {
console.log(count); // 始终打印 0(闭包捕获初始值)
}, 1000);
}, []);

return <button onClick={() => setCount(count + 1)}>Click</button>;
}

解决方案

  1. 使用函数式更新

    setCount(prev => prev + 1); // 不依赖闭包中的 count
  2. 使用 useRef 保存最新值

    const countRef = useRef(count);
    countRef.current = count; // 每次渲染更新

    useEffect(() => {
    setInterval(() => {
    console.log(countRef.current); // 始终获取最新值
    }, 1000);
    }, []);

6. 自定义 Hook 的复用机制

自定义 Hook 本质是普通函数,但通过相同的调用顺序规则,可以复用状态逻辑:

function useCounter(initialValue) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(c => c + 1);
return { count, increment };
}

function ComponentA() {
const { count, increment } = useCounter(0); // 第一个 useCounter
// 对应第一个 useState
}

function ComponentB() {
const { count, increment } = useCounter(10); // 第二个 useCounter
// 对应另一个 useState(与 ComponentA 独立)
}

总结

React Hooks 的实现核心是:

  1. 链表结构:每个函数组件对应一个 Fiber 节点,其中的 memoizedState 是一个链表,存储所有 Hook 状态。
  2. 调用顺序:Hooks 依赖严格的调用顺序来定位状态,因此不能在条件语句中使用。
  3. 异步执行useEffect 在提交阶段后异步执行,避免阻塞渲染。