Skip to content

React Hooks 原理

1. 核心数据结构 🔥

1.1 Hooks 链表存储

在 Fiber 节点中,Hooks 被存储为一个链表 (memoizedState)。 每次渲染组件时,React 会按顺序遍历这个链表,获取状态。

┌─────────────────────────────────────────────────────────────────────┐
│                     Fiber Node (Function Component)                 │
├─────────────────────────────────────────────────────────────────────┤
│  memoizedState ────► Hook1 ────► Hook2 ────► Hook3 ────► null       │
│                      │           │           │                      │
│                   useState    useEffect   useMemo                   │
└─────────────────────────────────────────────────────────────────────┘

1.2 Hook 对象结构

javascript
// Hook 对象结构 (简化版)
const hook = {
    memoizedState: any,     // 当前状态值
                            // - useState: state 值
                            // - useReducer: state 值
                            // - useEffect: effect 对象
                            // - useRef: { current: value }
                            // - useMemo: [value, deps]
                            // - useCallback: [callback, deps]
    
    baseState: any,         // 基础状态 (用于并发更新)
    baseQueue: Update,      // 基础更新队列
    
    queue: {                // 更新队列
        pending: null,      // 环形链表头
        dispatch: null,     // dispatch 函数
        lastRenderedReducer: reducer,
        lastRenderedState: state
    },
    
    next: Hook | null       // 指向下一个 Hook
};

1.3 不同 Hook 的 memoizedState

HookmemoizedState 存储内容
useStatestate 值
useReducerstate 值
useEffectEffect 对象 { create, destroy, deps... }
useRef{ current: value }
useMemo[memoizedValue, deps]
useCallback[callback, deps]
useContext读取的 context 值 (无 Hook 节点)

2. 为什么不能在循环/条件语句中使用 Hooks? 🔥

2.1 Hooks 调用规则

javascript
// ❌ 错误: 条件调用
function Component({ show }) {
    if (show) {
        const [count, setCount] = useState(0); // 可能跳过
    }
    const [name, setName] = useState('');
}

// ❌ 错误: 循环调用
function Component({ items }) {
    items.forEach(item => {
        const [state, setState] = useState(item); // 数量变化
    });
}

// ✅ 正确: 顶层调用
function Component({ show }) {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('');
    // 条件逻辑放在 Hook 内部或返回值
}

2.2 链表错乱示例

首次渲染 (show = true):
  hooks[0] → useState(0)   count
  hooks[1] → useEffect     effect
  hooks[2] → useState('')  name

二次渲染 (show = false, 跳过第一个 useState):
  hooks[0] → useEffect     ❌ 期望是 count, 得到 effect!
  hooks[1] → useState('')  ❌ 期望是 effect, 得到 name!

结果: 状态完全错乱!

2.3 React 如何检测违规?

javascript
// 开发模式下, React 维护 hookTypesDev 数组
let hookTypesDev = ['useState', 'useEffect', 'useState'];

// 每次渲染检查顺序是否一致
function checkHooksOrder(hookName) {
    if (hookTypesDev[currentHookIndex] !== hookName) {
        throw new Error(
            'React has detected a change in the order of Hooks'
        );
    }
}

3. Dispatcher 机制

3.1 什么是 Dispatcher?

Dispatcher 是 Hooks 的实际执行者。React 根据不同阶段使用不同 Dispatcher:

javascript
const ReactCurrentDispatcher = {
    current: null  // 指向当前 Dispatcher
};

// mount 阶段
const HooksDispatcherOnMount = {
    useState: mountState,
    useEffect: mountEffect,
    useRef: mountRef,
    useMemo: mountMemo,
    // ...
};

// update 阶段
const HooksDispatcherOnUpdate = {
    useState: updateState,
    useEffect: updateEffect,
    useRef: updateRef,
    useMemo: updateMemo,
    // ...
};

// render 阶段中途再次渲染 (因 setState 触发)
const HooksDispatcherOnRerender = {
    useState: rerenderState,
    // ...
};

3.2 Dispatcher 切换流程

renderWithHooks(fiber)

    ├─ current === null || current.memoizedState === null?
    │       │
    │       ├── Yes ─→ 使用 HooksDispatcherOnMount
    │       │
    │       └── No ──→ 使用 HooksDispatcherOnUpdate

    └─ 执行组件函数 Component(props)

          ├─ 调用 useState() → Dispatcher.useState()

          └─ 渲染完成 → 重置 Dispatcher 为 ContextOnlyDispatcher

3.3 为什么在组件外调用 Hook 会报错?

javascript
// 组件外调用
const [count, setCount] = useState(0); // 报错!

// 原因: 此时 Dispatcher 是 ContextOnlyDispatcher
const ContextOnlyDispatcher = {
    useState: throwInvalidHookError,
    useEffect: throwInvalidHookError,
    // 所有 Hook 都会抛出错误
};

function throwInvalidHookError() {
    throw new Error(
        'Invalid hook call. Hooks can only be called inside ' +
        'the body of a function component.'
    );
}

4. useState 实现原理 🔥

4.1 mountState (首次渲染)

javascript
function mountState(initialState) {
    // 1. 创建 Hook 对象, 挂载到链表
    const hook = mountWorkInProgressHook();
    
    // 2. 处理初始值 (支持函数)
    if (typeof initialState === 'function') {
        initialState = initialState();
    }
    
    // 3. 存储状态
    hook.memoizedState = hook.baseState = initialState;
    
    // 4. 创建更新队列
    const queue = {
        pending: null,
        dispatch: null,
        lastRenderedReducer: basicStateReducer,
        lastRenderedState: initialState
    };
    hook.queue = queue;
    
    // 5. 创建 dispatch 函数 (绑定 fiber 和 queue)
    const dispatch = queue.dispatch = dispatchSetState.bind(
        null,
        currentlyRenderingFiber,
        queue
    );
    
    return [hook.memoizedState, dispatch];
}

4.2 updateState (更新渲染)

javascript
// useState 本质是预设 reducer 的 useReducer
function updateState(initialState) {
    return updateReducer(basicStateReducer, initialState);
}

// 内置 reducer
function basicStateReducer(state, action) {
    return typeof action === 'function' ? action(state) : action;
}

4.3 dispatchSetState (触发更新)

javascript
function dispatchSetState(fiber, queue, action) {
    // 1. 创建 Update 对象
    const update = {
        lane,             // 优先级
        action,           // 新值或更新函数
        hasEagerState: false,
        eagerState: null,
        next: null
    };
    
    // 2. 性能优化: Eager State
    // 如果当前没有进行中的更新, 提前计算新状态
    if (fiber.lanes === NoLanes) {
        const currentState = queue.lastRenderedState;
        const eagerState = basicStateReducer(currentState, action);
        update.hasEagerState = true;
        update.eagerState = eagerState;
        
        // 如果状态没变, 跳过调度 (Bailout)
        if (Object.is(eagerState, currentState)) {
            return;  // 不触发重渲染!
        }
    }
    
    // 3. 将 Update 加入环形链表
    const pending = queue.pending;
    if (pending === null) {
        update.next = update; // 自环
    } else {
        update.next = pending.next;
        pending.next = update;
    }
    queue.pending = update;
    
    // 4. 调度更新
    scheduleUpdateOnFiber(fiber, lane);
}

4.4 Update 环形链表

queue.pending 始终指向最后一个 update
pending.next 指向第一个 update

添加 update1:
  pending → update1 ─┐
              ↑      │
              └──────┘

添加 update2:
  pending → update2 → update1 ─┐
              ↑                │
              └────────────────┘

添加 update3:
  pending → update3 → update1 → update2 ─┐
              ↑                          │
              └──────────────────────────┘

遍历顺序: update1 → update2 → update3 (FIFO)

5. useReducer 实现原理

5.1 mountReducer

javascript
function mountReducer(reducer, initialArg, init) {
    const hook = mountWorkInProgressHook();
    
    // 支持 init 函数
    const initialState = init !== undefined 
        ? init(initialArg) 
        : initialArg;
    
    hook.memoizedState = hook.baseState = initialState;
    
    const queue = {
        pending: null,
        dispatch: null,
        lastRenderedReducer: reducer,
        lastRenderedState: initialState
    };
    hook.queue = queue;
    
    const dispatch = queue.dispatch = dispatchReducerAction.bind(
        null,
        currentlyRenderingFiber,
        queue
    );
    
    return [hook.memoizedState, dispatch];
}

5.2 updateReducer

javascript
function updateReducer(reducer, initialArg, init) {
    const hook = updateWorkInProgressHook();
    const queue = hook.queue;
    
    queue.lastRenderedReducer = reducer;
    
    // 获取 baseState 和 baseQueue
    let baseQueue = hook.baseQueue;
    const pendingQueue = queue.pending;
    
    // 合并 pending 到 base
    if (pendingQueue !== null) {
        if (baseQueue !== null) {
            // 合并两个环形链表
            const baseFirst = baseQueue.next;
            const pendingFirst = pendingQueue.next;
            baseQueue.next = pendingFirst;
            pendingQueue.next = baseFirst;
        }
        hook.baseQueue = baseQueue = pendingQueue;
        queue.pending = null;
    }
    
    // 计算新状态
    if (baseQueue !== null) {
        const first = baseQueue.next;
        let newState = hook.baseState;
        let update = first;
        
        do {
            // 检查优先级
            if (isSubsetOfLanes(renderLanes, update.lane)) {
                newState = reducer(newState, update.action);
            } else {
                // 跳过低优先级更新
            }
            update = update.next;
        } while (update !== first);
        
        hook.memoizedState = newState;
    }
    
    return [hook.memoizedState, queue.dispatch];
}

5.3 useState vs useReducer

javascript
// useState 就是用 basicStateReducer 的 useReducer
function useState(initialState) {
    return useReducer(basicStateReducer, initialState);
}

function basicStateReducer(state, action) {
    return typeof action === 'function' 
        ? action(state)  // 函数式更新
        : action;        // 直接替换
}

6. useEffect 实现原理 🔥

6.1 Effect 对象结构

javascript
const effect = {
    tag: HookFlags,       // 标记类型 (Passive, Layout, ...)
    create: () => {},     // 创建函数 (effect 回调)
    destroy: () => {},    // 销毁函数 (cleanup)
    deps: [...],          // 依赖数组
    next: Effect          // 指向下一个 Effect (环形链表)
};

// HookFlags
const Passive = 0b1000;   // useEffect
const Layout = 0b0100;    // useLayoutEffect
const HasEffect = 0b0001; // 需要执行

6.2 mountEffect

javascript
function mountEffect(create, deps) {
    return mountEffectImpl(
        PassiveEffect,           // Fiber.flags
        HookPassive,             // Hook.tag
        create,
        deps
    );
}

function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
    const hook = mountWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    
    // 标记 Fiber 有 PassiveEffect
    currentlyRenderingFiber.flags |= fiberFlags;
    
    // 创建 Effect 对象, 存入 memoizedState
    hook.memoizedState = pushEffect(
        HookHasEffect | hookFlags,  // 首次一定要执行
        create,
        undefined,  // destroy 初始为 undefined
        nextDeps
    );
}

6.3 updateEffect

javascript
function updateEffect(create, deps) {
    return updateEffectImpl(
        PassiveEffect,
        HookPassive,
        create,
        deps
    );
}

function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
    const hook = updateWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    let destroy = undefined;
    
    if (currentHook !== null) {
        const prevEffect = currentHook.memoizedState;
        destroy = prevEffect.destroy;
        
        if (nextDeps !== null) {
            const prevDeps = prevEffect.deps;
            // 依赖没变, 不标记 HasEffect
            if (areHookInputsEqual(nextDeps, prevDeps)) {
                hook.memoizedState = pushEffect(
                    hookFlags,  // 没有 HasEffect!
                    create,
                    destroy,
                    nextDeps
                );
                return;
            }
        }
    }
    
    // 依赖变了, 标记需要执行
    currentlyRenderingFiber.flags |= fiberFlags;
    hook.memoizedState = pushEffect(
        HookHasEffect | hookFlags,
        create,
        destroy,
        nextDeps
    );
}

6.4 依赖比较

javascript
function areHookInputsEqual(nextDeps, prevDeps) {
    if (prevDeps === null) {
        return false;
    }
    
    // 浅比较每个依赖项
    for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
        if (Object.is(nextDeps[i], prevDeps[i])) {
            continue;
        }
        return false;
    }
    return true;
}

6.5 Effect 执行时机

Commit 阶段

    ├─── BeforeMutation ─────────────────────────────────────────────────

    ├─── Mutation ──────── 真实 DOM 操作

    ├─── Layout ────────── useLayoutEffect 同步执行
    │                      │
    │                      ├─ commitHookEffectListUnmount(Layout) // destroy
    │                      └─ commitHookEffectListMount(Layout)   // create

    └─── 渲染完成 ─────────

              └─ 调度 flushPassiveEffects (异步, 下一帧)

                      ├─ commitPassiveUnmountEffects() // destroy
                      └─ commitPassiveMountEffects()   // create

7. useLayoutEffect vs useEffect

7.1 执行时机对比

┌─────────────────────────────────────────────────────────────────────┐
│ Render Phase        │ Commit Phase             │ Browser            │
├─────────────────────┼──────────────────────────┼────────────────────┤
│                     │ BeforeMutation           │                    │
│                     │ Mutation (DOM 操作)       │                    │
│ 构建 Fiber 树        │ Layout                   │                    │
│                     │  └─ useLayoutEffect 🔥   │                    │
│                     │                          │ Layout             │
│                     │                          │ Paint              │
│                     │                          │                    │
│                     │ ────微任务────            │                    │
│                     │                          │                    │
│                     │ 下一帧 (requestIdleCallback)                  │
│                     │  └─ useEffect 🔥         │                    │
└─────────────────────┴──────────────────────────┴────────────────────┘

7.2 使用场景

javascript
// useLayoutEffect: 需要同步读取/修改 DOM
function Tooltip() {
    const ref = useRef();
    
    useLayoutEffect(() => {
        // 在 paint 前测量并调整位置, 避免闪烁
        const { width, height } = ref.current.getBoundingClientRect();
        ref.current.style.left = `${calculateLeft(width)}px`;
    }, []);
    
    return <div ref={ref}>Tooltip</div>;
}

// useEffect: 一般副作用 (数据获取, 订阅等)
function DataFetcher({ id }) {
    const [data, setData] = useState(null);
    
    useEffect(() => {
        fetch(`/api/${id}`).then(res => res.json()).then(setData);
    }, [id]);
    
    return <div>{data}</div>;
}

7.3 SSR 注意事项

javascript
// useLayoutEffect 在 SSR 时会警告
// 因为服务端没有 DOM, 无法同步执行

// 解决方案1: 使用 useEffect
// 解决方案2: 条件判断
const useIsomorphicLayoutEffect = 
    typeof window !== 'undefined' ? useLayoutEffect : useEffect;

8. useRef 实现原理

8.1 mountRef

javascript
function mountRef(initialValue) {
    const hook = mountWorkInProgressHook();
    const ref = { current: initialValue };
    hook.memoizedState = ref;
    return ref;
}

8.2 updateRef

javascript
function updateRef(initialValue) {
    const hook = updateWorkInProgressHook();
    // 直接返回同一个对象!
    return hook.memoizedState;
}

8.3 为什么 useRef 不会触发重渲染?

javascript
// useRef 返回的始终是同一个对象
const ref = useRef(0);
ref.current = 1;  // 修改 current 不会触发更新

// 因为:
// 1. 没有调用 dispatchSetState
// 2. memoizedState 引用没变
// 3. 对象内部属性变化 React 无法感知

8.4 常见用途

javascript
// 1. 保存 DOM 引用
const inputRef = useRef(null);
<input ref={inputRef} />

// 2. 保存可变值 (不触发渲染)
const timerRef = useRef(null);
timerRef.current = setInterval(...);

// 3. 保存前一次的值
function usePrevious(value) {
    const ref = useRef();
    useEffect(() => {
        ref.current = value;
    });
    return ref.current;
}

// 4. 跟踪组件是否已挂载
function useIsMounted() {
    const isMounted = useRef(false);
    useEffect(() => {
        isMounted.current = true;
        return () => { isMounted.current = false; };
    }, []);
    return isMounted;
}

9. useMemo 和 useCallback 实现原理

9.1 mountMemo

javascript
function mountMemo(nextCreate, deps) {
    const hook = mountWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    
    // 立即执行计算函数
    const nextValue = nextCreate();
    
    // 存储 [值, 依赖]
    hook.memoizedState = [nextValue, nextDeps];
    return nextValue;
}

9.2 updateMemo

javascript
function updateMemo(nextCreate, deps) {
    const hook = updateWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    const prevState = hook.memoizedState;
    
    if (prevState !== null) {
        if (nextDeps !== null) {
            const prevDeps = prevState[1];
            // 依赖没变, 返回缓存值
            if (areHookInputsEqual(nextDeps, prevDeps)) {
                return prevState[0];
            }
        }
    }
    
    // 依赖变了, 重新计算
    const nextValue = nextCreate();
    hook.memoizedState = [nextValue, nextDeps];
    return nextValue;
}

9.3 useCallback 实现

javascript
// useCallback 是 useMemo 的语法糖
function mountCallback(callback, deps) {
    const hook = mountWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    // 直接存储函数, 不执行
    hook.memoizedState = [callback, nextDeps];
    return callback;
}

function updateCallback(callback, deps) {
    const hook = updateWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    const prevState = hook.memoizedState;
    
    if (prevState !== null) {
        if (nextDeps !== null) {
            if (areHookInputsEqual(nextDeps, prevState[1])) {
                return prevState[0];  // 返回缓存的函数
            }
        }
    }
    
    hook.memoizedState = [callback, nextDeps];
    return callback;
}

9.4 等价关系

javascript
// 这两个等价
useCallback(fn, deps)
useMemo(() => fn, deps)

// 区别:
// useMemo: 缓存计算结果
// useCallback: 缓存函数引用

10. useContext 实现原理

10.1 特殊性

useContext 不创建 Hook 节点, 直接从 Context 读取值:

javascript
function readContext(context) {
    const value = isPrimaryRenderer
        ? context._currentValue
        : context._currentValue2;
    
    // 建立依赖关系
    if (lastContextDependency === null) {
        lastContextDependency = { context, next: null };
        currentlyRenderingFiber.dependencies = {
            lanes: NoLanes,
            firstContext: lastContextDependency
        };
    } else {
        lastContextDependency = lastContextDependency.next = {
            context,
            next: null
        };
    }
    
    return value;
}

10.2 Context 更新传播

javascript
// Provider 更新时
function propagateContextChange(workInProgress, context, renderLanes) {
    let fiber = workInProgress.child;
    
    while (fiber !== null) {
        const dependency = fiber.dependencies;
        if (dependency !== null) {
            let dep = dependency.firstContext;
            while (dep !== null) {
                // 找到使用该 Context 的组件
                if (dep.context === context) {
                    // 标记需要更新
                    fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
                    scheduleContextWorkOnParentPath(fiber, renderLanes);
                    break;
                }
                dep = dep.next;
            }
        }
        fiber = fiber.sibling || fiber.return;
    }
}

10.3 性能优化

javascript
// 问题: Context 变化会导致所有消费者重渲染
const ThemeContext = createContext({ theme: 'light', toggle: () => {} });

// 解决方案1: 拆分 Context
const ThemeValueContext = createContext('light');
const ThemeSetterContext = createContext(() => {});

// 解决方案2: useMemo 包装 value
function ThemeProvider({ children }) {
    const [theme, setTheme] = useState('light');
    const value = useMemo(() => ({ theme, toggle: () => setTheme(...) }), [theme]);
    return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}

// 解决方案3: 使用 memo 包装消费组件
const ThemedButton = memo(function ThemedButton() {
    const { theme } = useContext(ThemeContext);
    return <button className={theme}>Click</button>;
});

11. 自定义 Hooks 原理

11.1 本质

自定义 Hooks 只是提取逻辑的函数, 内部调用的 Hooks 直接挂载到调用组件的 Fiber 上:

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

function Counter() {
    const { count, increment } = useCounter(0);
    // Hooks 链表:
    // Hook1 (useState from useCounter)
    // Hook2 (useCallback from useCounter)
}

11.2 常用自定义 Hooks 示例

javascript
// useToggle
function useToggle(initial = false) {
    const [value, setValue] = useState(initial);
    const toggle = useCallback(() => setValue(v => !v), []);
    return [value, toggle];
}

// useDebounce
function useDebounce(value, delay) {
    const [debouncedValue, setDebouncedValue] = useState(value);
    
    useEffect(() => {
        const timer = setTimeout(() => setDebouncedValue(value), delay);
        return () => clearTimeout(timer);
    }, [value, delay]);
    
    return debouncedValue;
}

// useEventListener
function useEventListener(event, handler, element = window) {
    const savedHandler = useRef(handler);
    
    useLayoutEffect(() => {
        savedHandler.current = handler;
    }, [handler]);
    
    useEffect(() => {
        const listener = e => savedHandler.current(e);
        element.addEventListener(event, listener);
        return () => element.removeEventListener(event, listener);
    }, [event, element]);
}

// useFetch
function useFetch(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        const controller = new AbortController();
        
        setLoading(true);
        fetch(url, { signal: controller.signal })
            .then(res => res.json())
            .then(setData)
            .catch(setError)
            .finally(() => setLoading(false));
        
        return () => controller.abort();
    }, [url]);
    
    return { data, loading, error };
}

12. React 18 新 Hooks

12.1 useTransition

javascript
function SearchResults() {
    const [query, setQuery] = useState('');
    const [isPending, startTransition] = useTransition();
    
    function handleChange(e) {
        // 紧急更新: 输入框
        setQuery(e.target.value);
        
        // 非紧急更新: 搜索结果
        startTransition(() => {
            setSearchQuery(e.target.value);
        });
    }
    
    return (
        <>
            <input value={query} onChange={handleChange} />
            {isPending && <Spinner />}
            <Results query={searchQuery} />
        </>
    );
}

12.2 useDeferredValue

javascript
function SearchResults({ query }) {
    // 延迟更新 query, 类似 debounce
    const deferredQuery = useDeferredValue(query);
    
    // query 和 deferredQuery 不同时显示旧内容
    const isStale = query !== deferredQuery;
    
    return (
        <div style={{ opacity: isStale ? 0.5 : 1 }}>
            <Results query={deferredQuery} />
        </div>
    );
}

12.3 useId

javascript
function PasswordField() {
    // 生成唯一 ID, SSR 时保持一致
    const id = useId();
    
    return (
        <>
            <label htmlFor={id}>Password</label>
            <input id={id} type="password" />
        </>
    );
}

12.4 useSyncExternalStore

javascript
// 订阅外部状态 (Redux, Zustand 等)
function useOnlineStatus() {
    return useSyncExternalStore(
        // subscribe
        callback => {
            window.addEventListener('online', callback);
            window.addEventListener('offline', callback);
            return () => {
                window.removeEventListener('online', callback);
                window.removeEventListener('offline', callback);
            };
        },
        // getSnapshot (客户端)
        () => navigator.onLine,
        // getServerSnapshot (SSR)
        () => true
    );
}

13. 面试高频问题

Q1: Hooks 为什么不能在条件语句中使用?

Hooks 依赖调用顺序来匹配状态。React 使用链表按顺序存储 Hook,如果条件分支导致某次渲染跳过了 Hook,链表索引就会错位,导致状态读取错误。

Q2: useState 和 useReducer 的区别?

javascript
// useState 是 useReducer 的语法糖
useState(initialState) ≈ useReducer(basicStateReducer, initialState)

// 区别在于:
// 1. useState 适合简单状态
// 2. useReducer 适合复杂状态逻辑或多个关联状态
// 3. useReducer 可以将 dispatch 传递下去, 避免回调层层透传

Q3: useEffect 和 useLayoutEffect 的区别?

特性useEffectuseLayoutEffect
执行时机异步 (下一帧)同步 (Paint 前)
阻塞渲染
适用场景数据获取、订阅DOM 测量、动画
SSR 支持⚠️ 需特殊处理

Q4: useRef 为什么不触发重渲染?

useRef 返回的始终是同一个对象引用,修改 .current 不会触发状态更新机制 (不调用 dispatchSetState),React 无法感知对象内部属性变化。

Q5: useMemo 和 useCallback 的区别?

javascript
// useMemo: 缓存计算结果
const memoizedValue = useMemo(() => expensiveComputation(a, b), [a, b]);

// useCallback: 缓存函数引用
const memoizedFn = useCallback(() => doSomething(a, b), [a, b]);

// useCallback(fn, deps) === useMemo(() => fn, deps)

Q6: 为什么在组件外调用 Hooks 会报错?

React 在组件渲染时设置特定 Dispatcher,组件外 Dispatcher 是 ContextOnlyDispatcher,所有 Hook 调用都会抛出 "Invalid hook call" 错误。

Q7: 自定义 Hooks 的本质是什么?

自定义 Hooks 只是提取逻辑的函数,它内部调用的所有 Hooks 都会直接挂载到调用组件的 Fiber 节点上,与在组件中直接使用效果完全一致。

Q8: useTransition 和 useDeferredValue 的区别?

javascript
// useTransition: 包装状态更新, 降低其优先级
const [isPending, startTransition] = useTransition();
startTransition(() => setState(newValue));

// useDeferredValue: 延迟某个值的更新
const deferredValue = useDeferredValue(value);

// 区别:
// useTransition 控制更新触发
// useDeferredValue 控制值的消费

Q9: 如何避免 useContext 导致的不必要渲染?

  1. 拆分 Context (读写分离)
  2. useMemo 包装 Provider value
  3. memo 包装消费组件
  4. 使用状态管理库 (Zustand, Jotai)

Q10: Hooks 链表是如何工作的?

首次渲染 (mount):
  1. 创建 Hook 对象
  2. 挂到 fiber.memoizedState 链表尾部
  3. 返回 [state, dispatch]

更新渲染 (update):
  1. 从 fiber.memoizedState 按顺序取 Hook
  2. 计算新状态
  3. 返回 [newState, dispatch]

关键: mount 时创建, update 时复用, 依赖调用顺序!

前端面试知识库