React Ref 指南:原理、實現與實踐

前言

React Ref(引用)是React中一個強大而重要的概念,它為我們提供了直接訪問DOM元素或組件實例的能力。雖然React推崇聲明式編程和數據驅動的理念,但在某些場景下,我們仍需要直接操作DOM或訪問組件實例。本文將深入探討React Ref的工作原理、使用方法和最佳實踐。

什么是React Ref?

React Ref是一個可以讓我們訪問DOM節點或在render方法中創建的React元素的方式。它本質上是一個對象,包含一個current屬性,用于存儲對真實DOM節點或組件實例的引用。

為什么需要Ref?

在React的聲明式編程模型中,數據流是單向的:props向下傳遞,事件向上冒泡。但在以下場景中,我們需要直接訪問DOM或組件:

  • 管理焦點、文本選擇或媒體播放
  • 觸發強制動畫
  • 集成第三方DOM庫
  • 測量DOM元素的尺寸
  • 訪問子組件的方法

Ref的演進歷史

1. String Refs(已廢棄)

// 不推薦使用
class MyComponent extends React.Component {componentDidMount() {this.refs.myInput.focus();}render() {return <input ref="myInput" />;}
}

String Refs存在性能問題和潛在的內存泄漏風險,已在React 16.3中被廢棄。

2. Callback Refs

class MyComponent extends React.Component {setInputRef = (element) => {this.inputElement = element;}componentDidMount() {if (this.inputElement) {this.inputElement.focus();}}render() {return <input ref={this.setInputRef} />;}
}

3. createRef(React 16.3+)

class MyComponent extends React.Component {constructor(props) {super(props);this.inputRef = React.createRef();}componentDidMount() {this.inputRef.current.focus();}render() {return <input ref={this.inputRef} />;}
}

4. useRef Hook(React 16.8+)

function MyComponent() {const inputRef = useRef(null);useEffect(() => {inputRef.current.focus();}, []);return <input ref={inputRef} />;
}

深入理解useRef

useRef的基本用法

useRef返回一個可變的ref對象,其.current屬性被初始化為傳入的參數。

const refContainer = useRef(initialValue);

useRef的特點

  1. 持久化存儲:useRef在組件的整個生命周期中保持同一個引用
  2. 不觸發重新渲染:修改.current屬性不會觸發組件重新渲染
  3. 同步更新.current的值會同步更新,不像state那樣異步

useRef vs useState

function RefVsState() {const [stateValue, setStateValue] = useState(0);const refValue = useRef(0);const updateState = () => {setStateValue(prev => prev + 1);console.log('State value:', stateValue); // 異步更新,可能顯示舊值};const updateRef = () => {refValue.current += 1;console.log('Ref value:', refValue.current); // 同步更新,顯示新值};return (<div><p>State: {stateValue}</p><p>Ref: {refValue.current}</p><button onClick={updateState}>Update State</button><button onClick={updateRef}>Update Ref</button></div>);
}

Ref的實際應用場景

1. 訪問DOM元素

function FocusInput() {const inputRef = useRef(null);const handleFocus = () => {inputRef.current.focus();};const handleClear = () => {inputRef.current.value = '';};return (<div><input ref={inputRef} type="text" /><button onClick={handleFocus}>Focus Input</button><button onClick={handleClear}>Clear Input</button></div>);
}

2. 存儲可變值

function Timer() {const [time, setTime] = useState(0);const intervalRef = useRef(null);const start = () => {if (intervalRef.current) return;intervalRef.current = setInterval(() => {setTime(prev => prev + 1);}, 1000);};const stop = () => {if (intervalRef.current) {clearInterval(intervalRef.current);intervalRef.current = null;}};useEffect(() => {return () => {if (intervalRef.current) {clearInterval(intervalRef.current);}};}, []);return (<div><p>Time: {time}</p><button onClick={start}>Start</button><button onClick={stop}>Stop</button></div>);
}

3. 保存上一次的值

function usePrevious(value) {const ref = useRef();useEffect(() => {ref.current = value;});return ref.current;
}function MyComponent({ count }) {const prevCount = usePrevious(count);return (<div><p>Current: {count}</p><p>Previous: {prevCount}</p></div>);
}

高級Ref技巧

1. forwardRef

forwardRef允許組件將ref轉發到其子組件:

const FancyInput = React.forwardRef((props, ref) => (<input ref={ref} className="fancy-input" {...props} />
));function Parent() {const inputRef = useRef(null);const handleFocus = () => {inputRef.current.focus();};return (<div><FancyInput ref={inputRef} /><button onClick={handleFocus}>Focus Input</button></div>);
}

2. useImperativeHandle

useImperativeHandle可以自定義暴露給父組件的實例值:

const CustomInput = React.forwardRef((props, ref) => {const inputRef = useRef(null);useImperativeHandle(ref, () => ({focus: () => {inputRef.current.focus();},scrollIntoView: () => {inputRef.current.scrollIntoView();},getValue: () => {return inputRef.current.value;}}));return <input ref={inputRef} {...props} />;
});function Parent() {const customInputRef = useRef(null);const handleAction = () => {customInputRef.current.focus();console.log(customInputRef.current.getValue());};return (<div><CustomInput ref={customInputRef} /><button onClick={handleAction}>Focus and Get Value</button></div>);
}

3. Ref回調函數

function MeasureElement() {const [dimensions, setDimensions] = useState({ width: 0, height: 0 });const measureRef = useCallback((node) => {if (node !== null) {setDimensions({width: node.getBoundingClientRect().width,height: node.getBoundingClientRect().height});}}, []);return (<div><div ref={measureRef} style={{ padding: '20px', border: '1px solid #ccc' }}>Measure me!</div><p>Width: {dimensions.width}px</p><p>Height: {dimensions.height}px</p></div>);
}

最佳實踐與注意事項

1. 避免過度使用Ref

// ? 不推薦:過度使用ref
function BadExample() {const inputRef = useRef(null);const [value, setValue] = useState('');const handleChange = () => {setValue(inputRef.current.value); // 不必要的ref使用};return <input ref={inputRef} onChange={handleChange} />;
}// ? 推薦:使用受控組件
function GoodExample() {const [value, setValue] = useState('');const handleChange = (e) => {setValue(e.target.value);};return <input value={value} onChange={handleChange} />;
}

2. 檢查ref的有效性

function SafeRefUsage() {const elementRef = useRef(null);const handleClick = () => {// 總是檢查ref是否有效if (elementRef.current) {elementRef.current.focus();}};return (<div><input ref={elementRef} /><button onClick={handleClick}>Focus</button></div>);
}

3. 清理副作用

function ComponentWithCleanup() {const intervalRef = useRef(null);useEffect(() => {intervalRef.current = setInterval(() => {console.log('Interval running');}, 1000);// 清理函數return () => {if (intervalRef.current) {clearInterval(intervalRef.current);}};}, []);return <div>Component with cleanup</div>;
}

4. 避免在渲染期間訪問ref

// ? 不推薦:在渲染期間訪問ref
function BadRefUsage() {const inputRef = useRef(null);// 渲染期間訪問ref可能為nullconst inputValue = inputRef.current?.value || '';return <input ref={inputRef} placeholder={inputValue} />;
}// ? 推薦:在effect或事件處理器中訪問ref
function GoodRefUsage() {const inputRef = useRef(null);const [placeholder, setPlaceholder] = useState('');useEffect(() => {if (inputRef.current) {setPlaceholder(inputRef.current.value || 'Enter text');}});return <input ref={inputRef} placeholder={placeholder} />;
}

性能考慮

1. 使用useCallback優化ref回調

function OptimizedRefCallback() {const [dimensions, setDimensions] = useState({ width: 0, height: 0 });// 使用useCallback避免不必要的重新渲染const measureRef = useCallback((node) => {if (node !== null) {const rect = node.getBoundingClientRect();setDimensions({ width: rect.width, height: rect.height });}}, []);return <div ref={measureRef}>Measured content</div>;
}

2. 避免內聯ref回調

// ? 不推薦:內聯ref回調
function InlineRefCallback() {const [element, setElement] = useState(null);return (<div ref={(node) => setElement(node)}>Content</div>);
}// ? 推薦:使用useCallback
function OptimizedRefCallback() {const [element, setElement] = useState(null);const refCallback = useCallback((node) => {setElement(node);}, []);return <div ref={refCallback}>Content</div>;
}

實際項目示例

自定義Hook:useClickOutside

function useClickOutside(callback) {const ref = useRef(null);useEffect(() => {const handleClick = (event) => {if (ref.current && !ref.current.contains(event.target)) {callback();}};document.addEventListener('mousedown', handleClick);return () => {document.removeEventListener('mousedown', handleClick);};}, [callback]);return ref;
}// 使用示例
function Dropdown() {const [isOpen, setIsOpen] = useState(false);const dropdownRef = useClickOutside(() => setIsOpen(false));return (<div ref={dropdownRef}><button onClick={() => setIsOpen(!isOpen)}>Toggle Dropdown</button>{isOpen && (<div className="dropdown-menu"><p>Dropdown content</p></div>)}</div>);
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88190.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88190.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88190.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

4.權重衰減(weight decay)

4.1 手動實現權重衰減 import torch from torch import nn from torch.utils.data import TensorDataset,DataLoader import matplotlib.pyplot as plt def synthetic_data(w,b,num_inputs):Xtorch.normal(0,1,size(num_inputs,w.shape[0]))yXwbytorch.normal(0,0.1,sizey.shap…

OpenCV開發-初始概念

第一章 OpenCV核心架構解析1.1 計算機視覺的基石OpenCV&#xff08;Open Source Computer Vision Library&#xff09;作為跨平臺計算機視覺庫&#xff0c;自1999年由Intel發起&#xff0c;已成為圖像處理領域的標準工具。其核心價值體現在&#xff1a;跨平臺性&#xff1a;支持…

LeetCode 930.和相同的二元子數組

給你一個二元數組 nums &#xff0c;和一個整數 goal &#xff0c;請你統計并返回有多少個和為 goal 的 非空 子數組。 子數組 是數組的一段連續部分。 示例 1&#xff1a; 輸入&#xff1a;nums [1,0,1,0,1], goal 2 輸出&#xff1a;4 解釋&#xff1a; 有 4 個滿足題目要求…

【論文解讀】Referring Camouflaged Object Detection

論文信息 論文題目&#xff1a;Referring Camouflaged Object Detection 論文鏈接&#xff1a;https://arxiv.org/pdf/2306.07532 代碼鏈接&#xff1a;https://github.com/zhangxuying1004/RefCOD 錄用期刊&#xff1a;TPAMI 2025 論文單位&#xff1a;南開大學 ps&#xff1a…

Spring中過濾器和攔截器的區別及具體實現

在 Spring 框架中&#xff0c;過濾器&#xff08;Filter&#xff09; 和 攔截器&#xff08;Interceptor&#xff09; 都是用于處理 HTTP 請求的中間件&#xff0c;但它們在作用范圍、實現方式和生命周期上有顯著區別。以下是詳細對比和實現方式&#xff1a;核心區別特性過濾器…

CANFD 數據記錄儀在新能源汽車售后維修中的應用

一、前言隨著新能源汽車市場如火如荼和新能源汽車電子系統的日益復雜&#xff0c;傳統維修手段在面對復雜和偶發故障時往往捉襟見肘&#xff0c;CANFD 數據記錄儀則憑借其獨特優勢&#xff0c;為售后維修帶來新的解決方案。二、 詳細介紹在新能源汽車領域&#xff0c;CANFD 數據…

某當CRM XlsFileUpload存在任意文件上傳(CNVD-2025-10982)

免責聲明 本文檔所述漏洞詳情及復現方法僅限用于合法授權的安全研究和學術教育用途。任何個人或組織不得利用本文內容從事未經許可的滲透測試、網絡攻擊或其他違法行為。使用者應確保其行為符合相關法律法規,并取得目標系統的明確授權。 前言: 我們建立了一個更多,更全的…

自然語言處理與實踐

文章目錄Lesson1&#xff1a;Introduction to NLP、NLP 基礎與文本預處理1.教材2.自然語言處理概述(1)NLP 的定義、發展歷程與應用場景(2)NLP 的主要任務&#xff1a;分詞、詞性標注、命名實體識別、句法分析等2.文本預處理3.文本表示方法&#xff1a;詞向量表示/詞表征Lesson2…

CSS揭秘:9.自適應的橢圓

前置知識&#xff1a;border-radius 用法前言 本篇目標是實現一個橢圓&#xff0c;半橢圓&#xff0c;四分之一橢圓。 一、圓形和橢圓 當我們想實現一個圓形時&#xff0c;通常只要指定 border-radius 為 width/height 的一半就可以了。 當我們指定的border-radius的值超過了 w…

善用關系網絡:開源AI大模型、AI智能名片與S2B2C商城小程序賦能下的成功新路徑

摘要&#xff1a;本文聚焦于關系在個人成功中的關鍵作用&#xff0c;指出關系即財富&#xff0c;善用關系、拓展人脈是成功的重要途徑。在此基礎上&#xff0c;引入開源AI大模型、AI智能名片以及S2B2C商城小程序等新興技術工具&#xff0c;探討它們如何助力個體在復雜的關系網絡…

2025年滲透測試面試題總結-2025年HW(護網面試) 34(題目+回答)

安全領域各種資源&#xff0c;學習文檔&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各種好玩的項目及好用的工具&#xff0c;歡迎關注。 目錄 2025年HW(護網面試) 34 一、網站信息收集 核心步驟與工具 二、CDN繞過與真實IP獲取 6大實戰方法 三、常…

螢石全新上線企業AI對話智能體,開啟IoT人機交互新體驗

一、什么是螢石AI對話智能體&#xff1f;如何讓設備聽得到、聽得懂&#xff1f;這次螢石發布的AI對話Agent&#xff0c;讓設備能進行自然、流暢、真人感的AI對話智能體&#xff0c;幫助開發者打造符合業務場景的AI對話智能體能力&#xff0c;實現全雙工、實時打斷、可擴展、對話…

智紳科技:以科技為翼,構建養老安全守護網

隨著我國老齡化進程加速&#xff0c;2025年60歲以上人口突破3.2億&#xff0c;養老安全問題成為社會關注的焦點。智紳科技作為智慧養老領域的領軍企業&#xff0c;以“科技賦能健康&#xff0c;智慧守護晚年”為核心理念&#xff0c;通過人工智能、物聯網、大數據等技術融合&am…

矩陣系統源碼部署實操指南:搭建全解析,支持OEM

矩陣系統源碼部署指南矩陣系統是一種高效的數據處理框架&#xff0c;適用于大規模分布式計算。以下為詳細部署步驟&#xff0c;包含OEM支持方案。環境準備確保服務器滿足以下要求&#xff1a;操作系統&#xff1a;Linux&#xff08;推薦Ubuntu 18.04/CentOS 7&#xff09;硬件配…

基于python的個人財務記賬系統

博主介紹&#xff1a;java高級開發&#xff0c;從事互聯網行業多年&#xff0c;熟悉各種主流語言&#xff0c;精通java、python、php、爬蟲、web開發&#xff0c;已經做了多年的畢業設計程序開發&#xff0c;開發過上千套畢業設計程序&#xff0c;沒有什么華麗的語言&#xff0…

從 CODING 停服到極狐 GitLab “接棒”,軟件研發工具市場風云再起

CODING DevOps 產品即將停服的消息&#xff0c;如同一顆重磅炸彈&#xff0c;在軟件研發工具市場炸開了鍋。從今年 9 月開始&#xff0c;CODING 將陸續下線其 DevOps 產品&#xff0c;直至 2028 年 9 月 30 日完全停服。這一變動讓眾多依賴 CODING 平臺的企業和個人開發者陷入了…

#滲透測試#批量漏洞挖掘#HSC Mailinspector 任意文件讀取漏洞(CVE-2024-34470)

免責聲明 本教程僅為合法的教學目的而準備&#xff0c;嚴禁用于任何形式的違法犯罪活動及其他商業行為&#xff0c;在使用本教程前&#xff0c;您應確保該行為符合當地的法律法規&#xff0c;繼續閱讀即表示您需自行承擔所有操作的后果&#xff0c;如有異議&#xff0c;請立即停…

深入解析C++驅動開發實戰:優化高效穩定的驅動應用

深入解析C驅動開發實戰&#xff1a;優化高效穩定的驅動應用 在現代計算機系統中&#xff0c;驅動程序&#xff08;Driver&#xff09;扮演著至關重要的角色&#xff0c;作為操作系統與硬件設備之間的橋梁&#xff0c;驅動程序負責管理和控制硬件資源&#xff0c;確保系統的穩定…

SNIProxy 輕量級匿名CDN代理架構與實現

&#x1f310; SNIProxy 輕量級匿名CDN代理架構與實現 &#x1f3d7;? 1. 整體架構設計 &#x1f539; 1.1 系統架構概覽 #mermaid-svg-S4n74I2nPLGityDB {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-S4n74I2nP…

Qt的信號與槽(一)

Qt的信號與槽&#xff08;一&#xff09;1.信號和槽的基本認識2.connect3.關閉窗口的按鈕4.函數的根源5.形參和實參的類型&#x1f31f;hello&#xff0c;各位讀者大大們你們好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f680;系列專欄&#xff1a;【Qt的學習】 &…