useRef 說明
useRef
是一個 Hook 可以讓你參考一個值並不會受到 React 的重新渲染影響
以上是根據 React 官方文檔 對 useRef
的說明,但這句話這究竟代表什麼意思?簡單來說我會把 useRef
當作是 useState
但不會觸發重新渲染 ,React 會幫你記錄這筆資料,就算元件重新渲染了還是存在。
範例一:記錄元件的資料狀態,不受渲染影響
這麼說還是有些抽象,讓我們來模擬一個簡單的需求並嘗試實現它:「計算一個元件究竟被重新渲染了幾次」。首先在畫面上定義一個輸入框,當使用者輸入名字時,會在畫面上顯示「我的名字是 x 」以及「我重新渲染了 x 次」。
// 先定義模板的模樣<div> <input value={name} onChange={(e) => setName(e.target.value)} /> <div>我的名字是 {name}</div> <div>我重新渲染了 {renderCount} 次</div></div>
並且定義資料的部分,這裡使用 useState
來定義 name
和 renderCount
這兩個變數,並且在每次元件刷新時就讓 renderCount
+ 1。
// 處理資料的部分const [name, setName] = useState('');const [renderCount, setRenderCount] = useState(0);
useEffect(() => { setRenderCount((prevCount) => prevCount + 1);});
看起來很簡單,應該不會有什麼問題?但實際上前面的程式邏輯上有非常大的漏洞,它無法運作且會瘋狂跳出以下的錯誤!
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
這是因為輸入導致觸發元件重新渲染,這時候觸發 renderCount + 1
又會導致元件重新渲染,進入無限的更新 State > 觸發渲染 > 更新 State > 觸發渲染 …… 😭,這時候就是使用 useRef
的典型時機。
只需要把 renderCount
改成使用 useRef
來定義,程式就可以如期運作了,這是因為 useRef
並不會造成元件重新渲染:
const [name, setName] = useState('');const renderCount = useRef(1);
useEffect(() => { renderCount.current += 1;});
本元件重新渲染次數:1
在上面的例子中示範了如果需要在元件重新渲染時保留某些狀態,就可以使用 useRef
這個 Hook 來記錄。
範例二:選取元素
由於 React 是建構虛擬 DOM 來處理元件的渲染,所以我們無法直接使用 querySelector
,這個方法只能取得真實的 DOM,而不是 React 的虛擬 DOM。在 React 中,我們可以使用 ref
來選取虛擬的 DOM 元素,使用 useRef
記錄下來。
const [name, setName] = useState('');const inputRef = useRef(null);
useEffect(() => { inputRef.current.focus();}, []);
在這個案例中,你可以想像就像是 Vitural DOM 版的 querySelector
,只是這個方法可以在 React 的元件中使用。
總結
既然說到 useRef
就不能不談 useState
與其他定義變數的方式,它們有不同的用途和特點,讓我們來看看它們的差異:
方法一:定義變數在元件外
let outside = 0;const App = () => { return <div>{outside}</div>;};
這麼做就相當於定義了一個全域變數,它會在加載整個應用程式的時候被建立,並且在整個應用程式中都可以使用,這種方式在某些情況下是很方便的,比如當我們有多個元件需要共享同一個變數時,但也會有一些潛在的問題像是:
- 命名衝突:如果多個元件都使用了同一個變數名稱,就會造成命名衝突。
- 難以追蹤:當變數被多個元件共享時,很難追蹤變數是如何被修改的。這可能會導致難以調試的問題。
- 難以測試:導致程式的複雜性和不穩定性上升且難以預估與測試。
綜合下來不會是一個很好的方法,如果希望狀態能夠在元件之間共享,可以傳遞 Props 或使用 useContext 來達成,而非使用全域變數。
方法二:定義變數在元件內
const App = () => { let inside = 0; return <div>{inside}</div>;};
這麼做與方法一類似,只是變數被定義在元件內部,這樣就不會有命名衝突的問題,但一個很大的區別是,這個變數會在每次元件刷新時重置。
方法三:使用 useState
相信學習 React 第一個碰的 Hook 就是 useState
,因此也不贅述,就是當 setState 時,元件會重新渲染。
const App = () => { const [state, setState] = useState(0); return <div>{state}</div>;};
方法四:使用 useRef
與 useState
類似,但不會觸發重新渲染。
const App = () => { const ref = useRef(0); return <div>{ref.current}</div>;};
資料來源
- Learn useRef in 11 Minutes - Web Dev Simplified
- useRef - React Docs
- Why need useRef and not mutable variable? - stack overflow