React 进阶
1 Hooks 深度
Hooks 产生原因与实现原理
实现原理:React 内部用链表(memorizedState)存储每个 Hook 的状态。每次组件渲染时按顺序遍历链表,通过调用顺序把 useState/useEffect 对应到同一个状态槽位。useState 存 state 值,useEffect 在 commit 阶段调度,useMemo/useCallback 存 [值/函数, deps]。
function useState(initialValue) {
const currentHook = hooks[currentHookIndex] || { state: initialValue };
hooks[currentHookIndex] = currentHook;
currentHookIndex++;
return [currentHook.state, setState];
}
Hooks 两条铁律
原因:React 用调用顺序来对应内部状态槽位。若在条件里调用,下次渲染顺序变化会导致状态错乱——例如第一次渲染有 useState1、useState2,第二次条件为 false 时只有 useState2,React 会把第二个 useState 错误地当成 Hook 1。
React 闭包陷阱:setInterval 场景与解法
最经典:setInterval 里 setCount(count + 1),count 永远是 0。
// ❌ 错误:effect 只跑一次,interval 闭包捕获 count=0
React.useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 永远用 0
}, 1000);
return () => clearInterval(id);
}, []);
// ✅ 解法 A:函数式更新(推荐)
React.useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);
// ✅ 解法 B:把 count 加进依赖(会频繁重建 interval)
}, [count]);
// ✅ 解法 C:用 ref 存最新值(需读值但不触发渲染时)
const countRef = React.useRef(count);
React.useEffect(() => { countRef.current = count }, [count]);
React.useEffect(() => {
const id = setInterval(() => console.log(countRef.current), 1000);
return () => clearInterval(id);
}, []);
口播模板:effect、事件监听、定时器、异步回调里若引用 state/props 会被闭包捕获成旧值。解法:补全依赖让 effect 重跑,或用函数式 setState 读最新值,必要时用 ref 保存最新值。
2 常用 Hooks 进阶
useState 底层与懒初始化
const [state, setState] = useState(() => computeExpensiveInitialState());
useEffect 完整生命周期与 cleanup
执行时机:Render(计算)→ Commit(改 DOM)→ Effect(useEffect 异步执行,不阻塞绘制)。deps:[] 只挂载/卸载执行;[a,b] 变化时执行;不传则每次 render 都执行(不推荐)。
useEffect(() => {
const timer = setInterval(() => {}, 1000);
return () => clearInterval(timer); // cleanup
}, []);
useRef 妙用
// 防抖:timerRef 存定时器
const timerRef = useRef(null);
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => { /* ... */ }, 500);
// 前一个值
const prevCountRef = useRef();
useEffect(() => { prevCountRef.current = count }, [count]);
const prevCount = prevCountRef.current;
useLayoutEffect 与 useEffect 区别
useLayoutEffect(() => {
const divWidth = divRef.current.getBoundingClientRect().width;
setWidth(divWidth); // 绘制前完成,避免闪烁
}, []);
useCallback 防抖失效原因
正确写法:fetchFn 和 debFn 都用 useCallback 保持引用稳定。防抖函数用 useMemo 记忆实例:const debFn = useMemo(() => debounce(sendVal, 500), [sendVal]);
useMemo 与 useCallback 关系
useContext 拆分与避免嵌套地狱
function composeProviders(providers) {
return ({ children }) =>
providers.reduceRight((acc, Provider) => <Provider>{acc}</Provider>, children);
}
const AppProviders = composeProviders([ThemeProvider, UserProvider, LocaleProvider]);
3 性能三件套
React.memo + useMemo + useCallback 搭配策略
const Table = React.memo(function Table({ columns, onRowClick }) { /* ... */ });
function Page() {
const columns = React.useMemo(() => [
{ key: "name", title: "Name" },
{ key: "age", title: "Age" },
], []);
const onRowClick = React.useCallback((row) => { console.log(row); }, []);
return <Table columns={columns} onRowClick={onRowClick} />;
}
依赖怎么写?引用稳定性
// ✅ 依赖写全
const fn = useCallback(() => { console.log(userId); }, [userId]);
// ✅ 函数式更新避免依赖
const inc = useCallback(() => setCount(c => c + 1), []);
// ❌ 漏依赖
const fn = useCallback(() => { console.log(count); }, []);
什么时候该用 / 不该用
4 Fiber 与 Diff
Fiber 可中断更新
两阶段:Render/Reconciliation(可中断,做 diff、打 flags)→ Commit(不可中断,改 DOM、执行 layout effects)。
三阶段:Render / Commit / Effect
Diff 只发生在 Render 阶段;Commit 只负责把 diff 结果应用到 DOM。
Diff 三假设与虚拟 DOM
虚拟 DOM:轻量 JS 对象树描述 UI 结构。Diff 比较新旧树找出差异,最小化 DOM 操作。列表必须用稳定 key。
5 状态管理选型
Redux 原理与中间件
redux-thunk 允许 action 返回函数处理异步;redux-saga 用 generator。Redux 因 reducer 必须纯函数,副作用通过中间件处理。
Zustand / Jotai / MobX / React Query 对比
选型原则:UI / Client / Server 状态
Context 适合低频全局配置/依赖注入;Redux 适合高频复杂业务、需可预测和调试。
6 React 18+
批处理与 setState 同步/异步
「异步」指合成事件/钩子调用顺序在更新之前,导致无法立即拿到更新后的值。可通过 setState 的 callback 或 useEffect 获取。
ErrorBoundary
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.log("Error:", error, errorInfo);
}
render() {
if (this.state.hasError) return <h1>Something went wrong.</h1>; // JSX
return this.props.children;
}
}
受控与非受控组件
7 实战问题
React Router 原理:hash 与 history
HashRouter 用 hashchange;BrowserRouter 用 History API。
内存泄漏:cleanup 模式与 AbortController
// AbortController 取消 fetch
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(e => { if (e.name !== 'AbortError') throw e; });
return () => controller.abort();
}, [url]);
React 懒加载
const About = React.lazy(() => import('./About'));
<Suspense fallback={<div>Loading...</div>}>
<About />
</Suspense>