React 运行时

2 阅读3分钟

4.1 Hooks can only be called inside the body of a function component

原因(三选一):

  • 条件 / 循环 / 嵌套函数里调用 Hook
  • 同一项目装了两份 React(检查 npm ls react)
  • 组件不是 PascalCase(function home() 被当普通函数,Hook 调用不合法)

定位:

cd frontend && npm ls react

若出现两个版本,在 vite.config.ts 加:

resolve: { dedupe: ['react', 'react-dom'] }

4.2 Cannot read properties of undefined (reading 'xxx')

定位:

  • 打开 React DevTools,定位到崩溃组件,看 props / state
  • 在崩溃前加 console.log 看值的来源

修法:

  • 加可选链 obj?.xxx应急,真正要追为什么是 undefined
  • 是异步还没回来 → 加 loading 守卫
  • 是父组件没传 → 父组件改

4.3 Maximum update depth exceeded 无限渲染

根因:useEffect 依赖里放了每次渲染都变的引用(对象字面量、新数组、内联函数)。

定位:

useEffect(() => {
  console.log('effect run', deps);
  // ...
}, [deps]);

看哪个依赖每次都是"新"的(Object.is 比较)。

修法:

  • useMemo / useCallback 包住依赖
  • 把对象拆成基础类型(string / number)放进依赖
  • 用 ref 替代:不需要触发渲染的值放 useRef

4.4 Warning: Each child in a list should have a unique "key" prop

误区:不能用 index 当 key,尤其列表会排序/删除/插入时。

修法:用数据自身唯一 id(item.id)。

4.5 antd 5 相关

4.5.1 Warning: [antd: Form] Instance created by useForm is not connected to any Form element

<Form form={form}> 没传 form 实例,或 Form 还没挂载就调用 form.setFieldsValue

修法:在 useEffectonFinish 里调用 form 方法,不要在 render 阶段。

4.5.2 findDOMNode is deprecated in StrictMode

antd 旧版组件在 React 18 严格模式下警告,升级 antd 到 5.13+。

4.5.3 antd v5 CSS-in-JS 闪烁(FOUC)

SSR / Vite 下首屏样式丢失。

解决:

import { StyleProvider } from '@ant-design/cssinjs';
<StyleProvider hashPriority="high">
  <App />
</StyleProvider>

4.5.4 Modal / Drawer 关闭时报错 unmounted component

原因:destroyOnClose 未开,内部状态在关闭后继续更新。

修法:<Modal destroyOnClose>

4.6 Zustand 相关

4.6.1 组件不更新

// ❌ 返回整个对象,引用不变
const state = useStore(s => s);

// ✅ selector,只订阅需要的字段
const user = useStore(s => s.user);

4.6.2 持久化 hydrate 时 undefined

persist 在第一次 render 后才注水,初始值会闪一下。

修法:

const hasHydrated = useStore.persist.hasHydrated();
if (!hasHydrated) return <Loading />;

或用 onRehydrateStorage 回调。

4.6.3 store 在多个 tab 不同步

persist 默认只同步到 localStorage,不跨 tab 通知。要跨 tab 用 storage 事件或 BroadcastChannel。

4.7 react-router v6

4.7.1 useNavigate() may be used only in the context of a <Router> component

某个组件在 <BrowserRouter> 外被渲染(常见于在 main.tsx 之外的工具调用 navigate)。

修法:把跳转改为返回结果,在组件内调用 navigate;或暴露 router 实例。

4.7.2 嵌套路由白屏

父路由忘了写 <Outlet />。检查 ../frontend/src/components/Layout.tsx 是否渲染了 Outlet。

4.7.3 路由切换不重置 state

component 是同一个,React 复用 fiber,内部 state 保留。

修法:

<Routes>
  <Route path="/foo/:id" element={<Foo key={location.pathname} />} />
</Routes>

key 强制重建组件。

4.7.4 Link 跳转刷新整页

原因:用了 <a href> 而非 <Link to>

4.8 useEffect 双执行(StrictMode)

React 18 <StrictMode> 下,dev 模式 effect 会跑两次,这是故意的,用来暴露副作用清理问题。

正确做法:写好 cleanup,不要为了不双执行而关 StrictMode。

useEffect(() => {
  const id = setTimeout(...);
  return () => clearTimeout(id); // 必须 cleanup
}, []);

4.9 Hydration mismatch(SSR 场景)

本项目是纯 CSR,理论上不会遇到。但若引入 Next.js / Remix:

  • 服务端和客户端渲染结果不一致(Date.now()Math.random()window)
  • 修法:把不一致部分用 useEffect 推迟到 client 端