React useRef 詳解
useRef
是 React 提供的一個 Hook,用于在函數組件中創建可變的引用對象。它在 React 開發中有多種重要用途,下面我將全面詳細地介紹它的特性和用法。
基本概念
1. 創建 ref
const refContainer = useRef(initialValue);
initialValue
: ref 對象的初始值(.current
屬性的初始值)- 返回一個可變的 ref 對象,其
.current
屬性被初始化為傳入的參數
2. 核心特性
- 跨渲染周期保存值:ref 對象在組件的整個生命周期內保持不變
- 修改不會觸發重新渲染:改變
.current
屬性不會導致組件重新渲染 - 直接訪問 DOM 元素:最常見的用途之一
主要用途
1. 訪問 DOM 元素
最常見的用法是訪問 JSX 渲染的 DOM 元素:
function TextInputWithFocusButton() {const inputEl = useRef(null);const onButtonClick = () => {inputEl.current.focus();};return (<><input ref={inputEl} type="text" /><button onClick={onButtonClick}>Focus the input</button></>);
}
2. 存儲可變值
可以存儲任何可變值,類似于類組件中的實例屬性:
function Timer() {const intervalRef = useRef();useEffect(() => {intervalRef.current = setInterval(() => {console.log('Timer tick');}, 1000);return () => clearInterval(intervalRef.current);}, []);// ...
}
3. 保存上一次的值
實現獲取上一次 props 或 state 的功能:
function Counter() {const [count, setCount] = useState(0);const prevCountRef = useRef();useEffect(() => {prevCountRef.current = count;});const prevCount = prevCountRef.current;return (<div><p>Current: {count}, Previous: {prevCount}</p><button onClick={() => setCount(count + 1)}>Increment</button></div>);
}
4. 避免重復執行 useEffect
解決 React 18+ 嚴格模式下 useEffect 執行兩次的問題:
useEffect(() => {const executedRef = useRef(false);if (!executedRef.current) {executedRef.current = true;// 你的初始化代碼}
}, []);
高級用法
1. 轉發 refs (forwardRef)
將 ref 傳遞給子組件:
const FancyInput = React.forwardRef((props, ref) => {return <input ref={ref} className="fancy-input" {...props} />;
});function App() {const inputRef = useRef();useEffect(() => {inputRef.current.focus();}, []);return <FancyInput ref={inputRef} />;
}
2. 回調 refs
動態設置多個 ref:
function MeasureExample() {const [height, setHeight] = useState(0);const measuredRef = useCallback(node => {if (node !== null) {setHeight(node.getBoundingClientRect().height);}}, []);return (<div ref={measuredRef}><h1>Hello, world</h1><p>The above header is {Math.round(height)}px tall</p></div>);
}
3. 與第三方 DOM 庫集成
function Canvas() {const canvasRef = useRef(null);useEffect(() => {const ctx = canvasRef.current.getContext('2d');// 使用第三方庫繪制new ThirdPartyLibrary(ctx);}, []);return <canvas ref={canvasRef} />;
}
useRef 與 useState 的區別
特性 | useRef | useState |
---|---|---|
觸發重新渲染 | 否 | 是 |
值更新時機 | 同步 | 異步 |
適合存儲 | DOM 引用、可變變量、計時器 | 需要觸發 UI 更新的狀態 |
訪問方式 | .current 屬性 | 直接訪問狀態變量 |
初始化 | 參數作為 .current 初始值 | 參數作為初始狀態 |
注意事項
-
不要在渲染期間寫入/讀取
ref.current
:// 錯誤示例 function MyComponent() {const myRef = useRef();myRef.current = 123; // 不應該在渲染期間修改return <div>{myRef.current}</div>; // 也不應該依賴渲染期間的值 }
-
ref 不會自動通知內容變化:
- 如果需要在 ref 變化時執行代碼,使用回調 ref 或手動監聽
-
多個 refs 合并:
function useCombinedRefs(...refs) {return useCallback(el => {refs.forEach(ref => {if (!ref) return;if (typeof ref === 'function') ref(el);else ref.current = el;});}, refs); }
-
服務端渲染(SSR)注意事項:
- ref 在服務端渲染時不會被序列化
- 在服務端和客戶端渲染結果要保持一致
性能優化
useRef
本身是輕量級的,但以下情況需要注意:
-
避免在渲染函數中創建新 ref:
// 不好 - 每次渲染都創建新 ref function Component() {return <Child ref={useRef()} />; }// 好 - ref 只創建一次 function Component() {const ref = useRef();return <Child ref={ref} />; }
-
大量 ref 的內存問題:
- 當需要為列表中的每個元素創建 ref 時,考慮使用 ref 回調函數 或 第三方庫
總結
useRef
是 React Hooks 中一個非常實用的工具,它:
- 提供了一種訪問 DOM 節點的方式
- 可以存儲不會觸發重新渲染的可變值
- 在組件的整個生命周期內保持引用不變
- 是集成第三方庫和實現高級模式的利器