react
这是一个非常棒的问题!你现在困惑的核心在于 “时间轴(Timeline)”。
你不知道 React 的生命周期、浏览器渲染、Local Storage 读取 和 API 请求 这四件事到底是谁先谁后。
不用担心,我们用一个最直观的 “去饭店点菜” 的例子,结合 React 的底层运行机制把这个过程画出来。
1. 错误的写法:为什么会报错?
(写法:初始值 null -> useEffect 读取)
想象你去一家饭店(页面加载),你是 API 请求,你口袋里的 VIP 卡 是 currentDeptId。
⏱️ 时间轴还原(毫秒级慢动作):
-
T=0ms (代码解析):
-
JS 代码刚跑起来。
-
React 看到
useState(null)。 -
结论:此时内存里,VIP 卡(ID)是 “空”。
-
-
T=10ms (首次渲染 Render):
-
React 开始画页面。它问:“现在 ID 是多少?”
-
内存回答:“是空。”
-
React 说:“行,那我就先按‘没卡’的情况画页面。”
-
-
T=20ms (组件挂载 & 发起请求 - 💣 炸雷点):
-
如果你用的是
react-query或者SWR这种库,它们通常在组件一挂载(Mount)时就立即发起请求。 -
API 请求冲出去了! 带着
x-dept-id: null。 -
后端报错:“你没卡,滚出去!”(401/400 Error)。
-
-
T=30ms (浏览器绘制 Paint):
- 浏览器把刚才 React 画的“没卡”的界面展示给用户看。
-
T=40ms (useEffect 终于执行了 - 🐢 迟到了):
-
React 规定:
useEffect必须等浏览器画完界面(T=30ms)之后才运行。 -
这时候代码才慢悠悠地去 LocalStorage 翻口袋:“哎呀,我有 VIP 卡(ID=123)啊!”
-
执行
setDeptId("123")。
-
-
T=50ms (二次渲染):
-
React 重新画页面,这次有了 ID。
-
但是... 刚才那个报错的 API 请求已经无法撤回了! 用户已经看到了报错红框。
-
2. 正确的写法:为什么能修好?
(写法:初始值直接读取 LocalStorage)
我们把读卡的动作提前。
⏱️ 时间轴还原:
-
T=0ms (代码解析 - ✅ 关键改变):
-
JS 代码刚跑起来。
-
React 看到:
JavaScript
useState(localStorage.getItem("deptId") || null) -
JS 是同步执行的。在这一行代码结束前,程序会暂停,去翻 LocalStorage(虽然很快,但确实发生了)。
-
拿到 ID "123"。
-
结论:在 React 开始干活之前,内存里的初始值已经是 "123" 了!
-
-
T=10ms (首次渲染 Render):
-
React 开始画页面。它问:“现在 ID 是多少?”
-
内存回答:“是 123。”
-
React 说:“好,按‘有卡’的情况画页面。”
-
-
T=20ms (组件挂载 & 发起请求):
-
react-query发起请求。 -
API 请求冲出去了! 带着
x-dept-id: 123。 -
后端响应:“尊贵的 VIP,这是您的数据。”(200 OK)。
-
-
T=30ms (浏览器绘制):
- 用户直接看到有数据的页面。
-
T=40ms (useEffect):
- 这时候就算有 useEffect,也没事干了,因为 ID 早就有了。
3. 图解:DOM 和 React 到底在干嘛?
React 对于 DOM 的操作,遵循一个铁律:所见即所得(UI = f(State))。
-
红色区域 (Sync):初始化 State、计算 Virtual DOM。这是同步的,必须一口气干完。
-
绿色区域 (DOM):React 把计算结果推给浏览器,浏览器把像素画在屏幕上。
-
蓝色区域 (Async):
useEffect。这是异步的。React 为了不让界面卡顿,特意把useEffect安排在 “浏览器画完之后” 才执行。
为什么 useEffect 会导致竞态?
因为 API 请求(通常由 useQuery 等库触发)往往紧跟在 渲染阶段 或 挂载阶段。
而 useEffect 里的 setState 必定发生在 渲染 -> 绘制 -> Effect 这一长串流程的最后。
等你 Effect 设置好 ID,黄花菜都凉了。
总结给 React 开发者
-
初始化 (useState/Zustand create) 是 最先 执行的,比组件渲染还早。
-
useEffect 是 最后 执行的,比浏览器画图还晚。
-
Local Storage 是同步 API (
getItem是同步方法),所以由于它很快,完全可以在初始化阶段直接读。
一句话口诀:
“不要等进了考场(useEffect)再翻书(读Storage),要在进考场前(初始化)就把知识(ID)装进脑子(State)里。”