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
| Hook | memoizedState 存储内容 |
|---|---|
useState | state 值 |
useReducer | state 值 |
useEffect | Effect 对象 { 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 为 ContextOnlyDispatcher3.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() // create7. 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 的区别?
| 特性 | useEffect | useLayoutEffect |
|---|---|---|
| 执行时机 | 异步 (下一帧) | 同步 (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 导致的不必要渲染?
- 拆分 Context (读写分离)
- useMemo 包装 Provider value
- memo 包装消费组件
- 使用状态管理库 (Zustand, Jotai)
Q10: Hooks 链表是如何工作的?
首次渲染 (mount):
1. 创建 Hook 对象
2. 挂到 fiber.memoizedState 链表尾部
3. 返回 [state, dispatch]
更新渲染 (update):
1. 从 fiber.memoizedState 按顺序取 Hook
2. 计算新状态
3. 返回 [newState, dispatch]
关键: mount 时创建, update 时复用, 依赖调用顺序!