為了生成唯一id,React18專門引入了新Hook:useId
大家好,我卡頌。
看看如下組件有什麼問題:
// App.tsx const id = Math.random(); export default function App() { return <div id={id}>Hello</div> }
如果應用是 CSR
(客户端渲染), id
是穩定的, App
組件沒有問題。
但如果應用是 SSR
(服務端渲染),那麼 App.tsx
會經歷:
-
React
在服務端渲染,生成隨機id
(假設為0.1234
),這一步叫dehydrate
(脱水) -
<div id="0.12345">Hello</div>
作為HTML
傳遞給客户端,作為首屏內容 -
React
在客户端渲染,生成隨機id
(假設為0.6789
),這一步叫hydrate
(注水)
客户端、服務端生成的 id
不匹配!
事實上,服務端、客户端無法簡單生成穩定、唯一的 id
是個由來已久的問題,早在15年就有人提過 issue
:
Generating random/unique attributes server-side that don't break client-side mounting
直到最近, React18
推出了官方 Hook
—— useId
,才解決以上問題。他的用法很簡單:
function Checkbox() { // 生成唯一、穩定id const id = useId(); return ( <> <label htmlFor={id}>Do you like React?</label> <input type="checkbox" name="react" id={id} /> </> ); );
雖然用法簡單,但背後的原理卻很有意思 —— 每個 id
代表該組件在組件樹中的層級結構。
本文讓我們來了解 useId
的原理。
歡迎加入人類高質量前端框架羣,帶飛
React18來了,一切都變了
這個問題雖然一直存在,但之前一直可以使用 自增的全局計數變量
作為 id
,考慮如下例子:
// 全局通用的計數變量 let globalIdIndex = 0; export default function App() { const id = useState(() => globalIdIndex++); return <div id={id}>Hello</div> }
只要 React
在服務端、客户端的運行流程一致,那麼雙端產生的 id
就是對應的。
但是,隨着 React Fizz
( React
新的服務端流式渲染器)的到來,渲染順序不再一定。
比如,有個特性叫 Selective Hydration
,可以根據用户交互改變 hydrate
的順序。
當下圖左側部分在 hydrate
時,用户點擊了右下角部分:
此時 React
會優先對右下角部分 hydrate
:
關於 Selective Hydration
更詳細的解釋見: New Suspense SSR Architecture in React 18
如果應用中使用 自增的全局計數變量
作為 id
,那麼顯然先 hydrate
的組件 id
會更小,所以 id
是不穩定的。
那麼,有沒有什麼是服務端、客户端都穩定的標記呢?
答案是:組件的層次結構。
useId的原理
假設應用的組件樹如下圖:
不管 B
和 C
誰先 hydrate
,他們的層級結構是不變的,所以 層級 本身就能作為服務端、客户端之間不變的標識。
比如 B
可以使用 2-1
作為 id
, C
使用 2-2
作為 id
:
function B() { // id為"2-1" const id = useId(); return <div id={id}>B</div>; }
實際需要考慮兩個要素:
1. 同一個組件使用多個id
比如這樣:
function B() { const id0 = useId(); const id1 = useId(); return ( <ul> <li id={id0}></li> <li id={id1}></li> </ul> ); }
2. 要跳過沒有使用useId的組件
還是考慮這個組件樹結構:
如果組件 A
、 D
使用了 useId
, B
、 C
沒有使用,那麼只需要為 A
、 D
劃定層級,這樣就能 減少需要表示層級 。
在 useId
的實際實現中,層級被表示為 32進制 的數。
之所以選擇 32進制 ,是因為選擇儘可能大的進制會讓生成的字符串儘可能緊湊。比如:
const a = 18; // "10010" length 5 a.toString(2) // "i" length 1 a.toString(32)
具體的 useId
層級算法參考useId
總結
React
源碼內部有多種 棧
結構(比如用於保存 context
數據的 棧
)。
useId
棧
的邏輯是其中比較複雜的一種。
誰能想到用法如此簡單的 API
背後,實現起來居然這麼複雜?
React
團隊搗鼓 併發特性 ,真挺不容易的...
- BFE開源項目2021年回顧和致謝
- 三大核心能力,揭示全面釋放數據價值的獨門祕訣
- 技術升級!國內公有云廠商首個支持保留消息功能
- 巧用 CSS 實現動態線條 Loading 動畫
- CTF&爬蟲:掌握這些特徵,一秒識別密文加密方式
- 一鍵AI着色,黑白老照片畫面瞬間鮮活
- 可觀測領域準獨角獸「駐雲科技」完成2億元新一輪融資
- 【微信小程序雲開發】1分鐘學會實現上傳、下載、預覽、刪除圖片,並且以九宮格展示圖片
- 強強聯袂!騰訊雲TDSQL與國雙戰略簽約,錨定國產數據庫巨大市場
- 如何優雅地讀寫HttpServletRequest和HttpServletResponse的請求體
- 優艾智合完成B系列超3億元人民幣融資 加速移動機器人規模化落地
- 5個很少被提到但能提高NLP工作效率的Python庫
- 徹底理解Golang Slice
- 全新的 Vue3 狀態管理工具:Pinia
- 想給用户天涯若比鄰的體驗?業務全球化面臨的三重挑戰
- 設計模式【4】-- 建造者模式詳解
- 分佈式鎖及其實現
- TDSQL | 國產化浪潮下,數據庫 雲如何跑上核心業務?
- 為了生成唯一id,React18專門引入了新Hook:useId
- Java SPI機制從原理到實戰