Refs 是 React 中不多會使用到的特性。若是你已經讀過了官方的 React Ref Guide,你會從中瞭解到 Refs 被描述爲重要的 React 數據流的 「逃生艙門」,需謹慎使用。Refs 被視爲訪問組件的基礎 DOM 元素的正確方法。html
伴隨着 React Hooks 的到來,React 團隊引入了 useRef Hook,它擴展了這個功能:前端
「
useRef()
比 ref 屬性更有用。它經過相似在 class 中使用實例字段的方式,
很是方便地 保存任何可變值。」 ——
React 文檔
新的 React Hooks API 發佈的時候,個人確忽略了這一點,事實證實 useRef 真的很是有用。react
我是一名 Firetable 的軟件開發工程師。Firetable 是一個開源的 React 電子表格應用,結合了 Firestore 和 Firebase 的主要功能。其中有一個主要功能是側面抽屜,它是一種相似於窗體的 UI,用於編輯在主表上滑動的那一行。android
當用戶單擊選中表格中的某一個單元格時,能夠經過打開側抽屜的方式編輯該單元格所對應的行數據。 換句話說,咱們在側邊抽屜中渲染的內容取決於當前選擇的行 —— 咱們須要將這行的數據狀態記錄下來。ios
將這行數據的狀態的放在側抽屜組件內部是最符合邏輯的,由於當用戶選擇其餘單元格時,它應該僅影響側邊的抽屜組件。 然而:git
React 的推薦作法是 提高狀態 到倆組件最近的父級節點 (以這個爲例,父級節點爲 TablePage
)。可是咱們決定不將狀態遷移到這個組件,理由是:github
TablePage
不保存狀態,主要是放置 table 和 side drawer 組件的容器, 二者都不接收任何的 props。咱們傾向於保持這種作法。注意:即便咱們將數據狀態放在了 TablePage
,不管如何咱們都將面臨下面這個相同的問題。後端
問題就是每當用戶選擇一個單元格或打開側面抽屜時,全局 context 的更新會使得整個應用發生從新渲染。table 組件能夠一次顯示數十個單元格,而且每一個單元格都有本身的編輯器組件。這會致使大約 650ms 的渲染時間,這個時間太長以致於在打開側邊抽屜的時候會感覺到明顯的延遲。api
罪魁禍首是 context —— 這就是爲何要在 React 中使用而不是在全局 JavaScript 對象中使用:數組
」只要提供給 Provider 的值發生變化,全部消費到了 Provider 的後代組件都會發生重渲染。「 — React Context
到目前爲止,雖然咱們已經足夠了解 React 的狀態和生命週期,但如今看來咱們依舊陷入了困境。
在決定使用 useRef
以前,咱們嘗試了幾種不一樣的解決方案。(Dan Abramov 的文章) :
SideDrawerContext
) —— table 組件仍然會消費到新的 context,在打開側邊抽屜的時候依舊會 致使 table 組件的沒必要要的從新渲染。React.memo
或 useMemo
中 —— table 組件依舊是須要經過 useContext
拿到側邊抽屜組件的狀態,兩種 API 均沒法阻止其從新渲染。react-data-grid
組件進行 memo —— 這將使咱們的代碼更加的冗長。咱們還發現它阻止了 「必要」 的從新渲染,要求咱們花費更多的時間徹底修復或者重構咱們的代碼來實現側邊抽屜。當再次閱讀 Hook APIs 和 useMemo
文檔的時候,我終於遇到了 useRef
相關內容。
「
useRef()
比 ref 屬性更有用。它經過像在 class 中使用實例字段的方式,
很是方便地 保存任何可變值。」 ——
React 文檔
更重要的是:
「當 ref 對象內容發生變化時,useRef
並不會通知變動。變動.current
屬性不會引起組件從新渲染。」 —— React 文檔
此時:咱們不須要存儲側抽屜的狀態。咱們只須要引用設置該狀態的函數便可。
如下代碼是在 Firetable 使用的代碼縮寫版,其中包括了 ref 和 TypeScript 的類型:
import { SideDrawerRef } from 'SideDrawer' export function FiretableContextProvider({ children }) { const sideDrawerRef = useRef<SideDrawerRef>(); return ( <FiretableContext.Provider value={{ sideDrawerRef }}> {children} </FiretableContext.Provider> ) }
注意:因爲函數組件在從新渲染時會運行整個函數體,因此每當 「單元」 或 「打開」 狀態更新(並致使從新渲染)時,「sideDrawerRef」 老是能在 「.current」 中獲取到最新值。
事實證實,此解決方案是最佳的:
你能夠在 Firetable 源碼中看它是如何被使用的 GitHub.
不過,這並不意味着您能夠在應用中隨意使用。當您須要在特定時間訪問或更新另外一個組件的狀態,可是您的其餘組件不依賴於該狀態或基於該狀態進行呈現時,這是最好的辦法。 React 的提高狀態和單向數據流的核心概念足以覆蓋大多數應用程序架構。
🌈 今天的文章分享就到這裏啦,若是喜歡這篇文章的話請點贊、Star、關注我吧 🎯
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、 iOS、 前端、 後端、 區塊鏈、 產品、 設計、 人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、 官方微博、 知乎專欄。