最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入理解React Ref 機制useRef 與 forwardRef 的協(xié)作原理

 更新時間:2026年03月23日 09:53:29   作者:軟彈  
這篇文章主要介紹了React Ref機制useRef 與 forwardRef的協(xié)作原理,本文通過實例代碼給大家講解的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧

1. 歷史背景:從命令式到聲明式的演變

1.1 Class 組件時代:命令式開發(fā)

在 React 早期,我們使用 Class 組件,開發(fā)模式本質上是命令式的。

什么是命令式?

命令式編程關注"怎么做"——你需要一步步告訴計算機執(zhí)行什么操作

?? 代碼示例(Class 組件 - 命令式):

// 點擊按鈕,輸入框背景變黃、聚焦、并選中所有文字
class OldForm extends React.Component {
  constructor(props) {
    super(props);
    // ═══════════════════════════════════════════════════════════════════
    // 【命令式特點 ①】:必須手動聲明變量來“抓住”DOM 元素
    // 就像手里必須拿著一根繩子拴著這個元素,否則找不到它
    // ═══════════════════════════════════════════════════════════════════
    this.inputEl = null; 
  }
  // ═══════════════════════════════════════════════════════════════════
  // 【命令式特點 ②】:開發(fā)者必須像“指揮官”一樣,一步步下達指令
  // 1. 找到元素 -> 2. 改顏色 -> 3. 聚焦 -> 4. 選中文本
  // 順序不能亂,漏一步都不行
  // ═══════════════════════════════════════════════════════════════════
  handleHighlight = () => {
    // ?? 第一步:手動修改樣式屬性
    this.inputEl.style.backgroundColor = 'yellow';
    // ?? 第二步:手動調用聚焦方法
    this.inputEl.focus();
    // ?? 第三步:手動調用選中方法
    this.inputEl.select();
    // ?? 痛點:如果 inputEl 為 null,這里直接報錯崩潰
    // 開發(fā)者需要自己防御性編程 (if (!this.inputEl) return;)
  };
  render() {
    return (
      <div>
        <input
          // ═══════════════════════════════════════════════════════════════════
          // 【命令式特點 ③】:使用回調函數(shù)“捕獲”DOM
          // 每次渲染都要執(zhí)行這個函數(shù),把 DOM 元素存到 this.inputEl 里
          // ═══════════════════════════════════════════════════════════════════
          ref={el => this.inputEl = el} 
          defaultValue="請高亮我"
        />
        {/* ?? 按鈕綁定的不是狀態(tài)變化,而是一個具體的“動作” */}
        <button onClick={this.handleHighlight}>
          高亮并選中 (命令式)
        </button>
      </div>
    );
  }
}
// ═══════════════════════════════════════════════════════════════════
// 【核心邏輯】:UI = 一系列指令的集合 (Do A, then Do B, then Do C)
// ═══════════════════════════════════════════════════════════════════

命令式的特點:

特點

說明

手動管理 DOM

開發(fā)者需要自己找到元素、修改屬性、綁定事件

狀態(tài)同步復雜

數(shù)據變了要更新 DOM,DOM 變了要更新數(shù)據

生命周期繁瑣

需要在 componentDidMount、componentDidUpdate 中處理副作用

難以追蹤

隨著代碼量增加,誰在什么時候修改了 DOM 很難追蹤

1.2 函數(shù)組件時代:聲明式開發(fā)

隨著 React Hooks 的推出,函數(shù)組件成為主流,開發(fā)模式轉向聲明式。

什么是聲明式?

聲明式編程關注"是什么"——你描述界面應該長什么樣,React 負責幫你實現(xiàn)。

?? 代碼示例(函數(shù)組件 - 聲明式):輸入框自動聚焦

//點擊按鈕,輸入框背景變黃、聚焦、并選中所有文字 
import { useRef, useEffect, useState } from 'react';
function ModernForm() {
  const inputRef = useRef(null);
  // ═══════════════════════════════════════════════════════════════════
  // 【聲明式特點 ①】:只關心“狀態(tài)是什么”,不關心“怎么變”
  // 我們定義一個狀態(tài) isActive,代表“是否處于高亮模式”
  // ═══════════════════════════════════════════════════════════════════
  const [isActive, setIsActive] = useState(false);
  // ═══════════════════════════════════════════════════════════════════
  // 【聲明式特點 ②】:副作用是“響應”狀態(tài)變化的
  // 邏輯:當 isActive 變?yōu)?true 時 -> 自動執(zhí)行聚焦和選中
  // 開發(fā)者不需要告訴 React“什么時候”做,只需定義“什么條件下”做
  // ═══════════════════════════════════════════════════════════════════
  useEffect(() => {
    if (isActive) {
      // ? 只有當狀態(tài)改變時,這些命令式操作才會被執(zhí)行
      inputRef.current?.focus();
      inputRef.current?.select();
      // 可選:執(zhí)行完后重置狀態(tài),或者保持高亮
      // 這里為了演示持續(xù)高亮,暫不重置
    }
  }, [isActive]); // ?? 關鍵:依賴項驅動,狀態(tài)變 -> 效應動
  const handleClick = () => {
    // ═══════════════════════════════════════════════════════════════════
    // 【聲明式特點 ③】:只更新數(shù)據,不操作 DOM
    // 點擊只是改變了“狀態(tài)”,具體的變色、聚焦由 React 和 useEffect 協(xié)調完成
    // ═══════════════════════════════════════════════════════════════════
    setIsActive(true);
  };
  return (
    <div>
      <input
        ref={inputRef}
        defaultValue="請高亮我"
        // ═══════════════════════════════════════════════════════════════════
        // 【聲明式特點 ④】:樣式是狀態(tài)的“映射”
        // 背景色 = isActive ? '黃色' : '默認'
        // 只要狀態(tài)對了,樣式自然就對,不需要手動 style.xxx
        // ═══════════════════════════════════════════════════════════════════
        style={{ 
          backgroundColor: isActive ? 'yellow' : 'transparent' 
        }}
      />
      <button onClick={handleClick}>
        高亮并選中 (聲明式)
      </button>
    </div>
  );
}
// ═══════════════════════════════════════════════════════════════════
// 【核心邏輯】:UI = f(State) (界面是狀態(tài)的函數(shù))
// State 變了 -> UI 自動重新計算并渲染
// ═══════════════════════════════════════════════════════════════════

聲明式的優(yōu)勢:

優(yōu)勢

說明

代碼簡潔

無需手動操作 DOM,React 自動處理

可預測性強

UI 完全由 State 決定,易于調試

負擔低

只需關注數(shù)據變化,不用想 DOM 更新細節(jié)

性能優(yōu)化

React 自動批量更新,減少不必要的重繪

1.3 為什么要從命令式轉變?yōu)槁暶魇剑?/h3>

對比維度

命令式 (Class)

聲明式 (Hooks)

代碼量

多(需寫生命周期)

少(只需寫邏輯)

狀態(tài)管理

分散(this.state + DOM)

集中(useState)

復用性

低(HOC/Render Props 復雜)

高(Custom Hooks)

學習曲線

陡峭(this 綁定、生命周期)

平緩(函數(shù)思維)

轉變的必要性:

  • 可維護性:大型項目中,命令式代碼難以維護
  • 協(xié)作效率:聲明式代碼更易讀,團隊協(xié)作更順暢
  • 生態(tài)發(fā)展:Hooks 生態(tài)更豐富,社區(qū)支持更好

2. 聲明式的邊界:什么是瀏覽器行為

2.1 聲明式的局限性

雖然聲明式很優(yōu)雅,但它無法處理所有場景。

?? 思考題:

// ? 這樣寫沒用!
function Input() {
  const [isFocused, setIsFocused] = useState(false);
  // 設置 isFocused = true 并不會讓輸入框真正聚焦
  // 因為聚焦是瀏覽器的行為,不是 React 的狀態(tài)
  return <input autoFocus={isFocused} />;
}

2.2 什么是瀏覽器行為?

瀏覽器行為指的是那些瞬時的、副作用的、無法通過狀態(tài)驅動的操作:

瀏覽器行為

說明

為什么聲明式無法處理

focus() / blur()

聚焦/失焦

這是瞬時動作,不是狀態(tài)

scrollIntoView()

滾動到指定位置

這是視口行為

select()

選中文本

這是編輯器行為

play() / pause()

播放/暫停媒體

這是媒體控制行為

Canvas 繪圖

逐幀繪制

這是即時渲染行為

第三方庫集成

jQuery/D3 等

它們直接操作 DOM

核心區(qū)別:

聲明式:UI = f(State)  →  描述"界面應該長什么樣"
命令式:DOM.action()   →  執(zhí)行"現(xiàn)在立刻做什么"

3. useRef:聲明式世界里的命令式逃生艙

3.1 useRef 的基本用法

為了解決聲明式無法處理瀏覽器行為的問題,React 提供了 useRef。

?? 代碼示例(基礎用法):

import { useRef, useEffect } from 'react';
function AutoFocusInput() {
  const inputRef = useRef(null);  // ① 創(chuàng)建 ref
  useEffect(() => {
    // ② 在副作用中操作 DOM
    inputRef.current.focus();
  }, []);
  return (
    // ③ 將 ref 綁定到 DOM 元素
    <input ref={inputRef} placeholder="我會自動聚焦" />
  );
}

3.2 useRef 的核心特性

特性

說明

ref.current 指向真實 DOM

可以調用所有原生 DOM API

修改 ref 不觸發(fā)重渲染

與 useState 的最大區(qū)別

生命周期持久

組件重新渲染時,ref 保持引用不變

可存儲任意值

不僅是 DOM,也可以是定時器、前一次的值等

?? 代碼示例(對比 useState 和 useRef):

function Compare() {
  const [count, setCount] = useState(0);
  const countRef = useRef(0);
  const updateState = () => {
    setCount(count + 1);  // ? 觸發(fā)重渲染
  };
  const updateRef = () => {
    countRef.current = countRef.current + 1;  // ? 不觸發(fā)重渲染
    console.log(countRef.current);  // 但值確實變了
  };
  return (
    <div>
      <p>State: {count}</p>  {/* 會顯示最新值 */}
      <p>Ref: {countRef.current}</p>  {/* 不會自動更新顯示 */}
    </div>
  );
}

3.3 useRef 解決了什么痛點?

痛點

解決方案

無法操作瞬時瀏覽器行為

通過 ref.current 調用 DOM API

需要在渲染間保持可變值

ref 的值在渲染間持久保存

需要訪問前一次的值

用 ref 存儲上一輪的值

需要存儲定時器/訂閱

避免內存泄漏

?? 代碼示例(存儲定時器):

function TimerComponent() {
  const timerRef = useRef(null);
  useEffect(() => {
    timerRef.current = setInterval(() => {
      console.log('tick');
    }, 1000);
    // 清理定時器
    return () => clearInterval(timerRef.current);
  }, []);
  return <div>計時中...</div>;
}

3.4 useRef 的新痛點

雖然 useRef 很好用,但它有一個致命限制:

?? 代碼示例(問題演示):

// 子組件:自定義函數(shù)組件
function MyInput(props) {
  // ?? 這里收不到 ref!
  return <input {...props} />;
}
// 父組件
function Parent() {
  const inputRef = useRef(null);
  useEffect(() => {
    console.log(inputRef.current);  // ? 輸出:null
  }, []);
  return <MyInput ref={inputRef} />;  // ref 被"吃掉"了
}

為什么會被"吃掉"?

  • React 設計時,ref 和 key 一樣,是特殊屬性
  • 它們不會被放入 props 對象中傳遞給函數(shù)組件
  • 這是為了保持函數(shù)組件的純粹性(函數(shù)組件沒有實例)

4. 封裝的代價:ref 在組件傳遞中的困境

4.1 真實開發(fā)場景

在實際項目中,我們很少直接在父組件中寫原生 <input>,而是會:

  • 封裝成通用組件 <MyInput />
  • 使用 UI 庫組件(如 AntD 的 <Input />

?? 代碼示例(封裝場景):

// --- 子組件:封裝的輸入框 ---
function MyInput(props) {
  return (
    <div className="input-wrapper">
      <label>用戶名:</label>
      <input {...props} />
    </div>
  );
}
// --- 父組件:試圖控制子組件 ---
function Form() {
  const inputRef = useRef(null);
  const handleSubmit = () => {
    // ? 失?。篿nputRef.current 是 null
    inputRef.current?.focus();
  };
  return (
    <form onSubmit={handleSubmit}>
      <MyInput ref={inputRef} name="username" />
      <button type="submit">提交</button>
    </form>
  );
}

4.2 問題根源分析

┌─────────────────────────────────────────────────────────┐
│                      父組件                              │
│   const inputRef = useRef(null)                         │
│   inputRef.current → ??? (null)                         │
│                        ↓                                │
│   <MyInput ref={inputRef} />                            │
│                        ↓                                │
│              ┌───────────────────┐                      │
│              │    子組件 MyInput │                      │
│              │                   │                      │
│              │  function(props)  │                      │
│              │  ?? ref 不進入 props │                   │
│              │                   │                      │
│              │  <input {...props}│                      │
│              │   ref 丟失!       │                     │
│              └───────────────────┘                      │
└─────────────────────────────────────────────────────────┘

4.3 錯誤嘗試:手動傳遞 ref

有些人會嘗試這樣解決:

// ? 錯誤做法:把 ref 當作普通 prop 傳遞
function MyInput({ inputRef, ...props }) {
  return <input {...props} ref={inputRef} />;
}
function Parent() {
  const ref = useRef(null);
  return <MyInput inputRef={ref} />;  // 不推薦!
}

為什么不推薦?

  • 命名不統(tǒng)一,容易造成混淆
  • 不符合 React 的設計規(guī)范
  • 無法與 React 的 ref 系統(tǒng)正確集成

5. forwardRef:打通組件邊界的橋梁

5.1 forwardRef 的引入

為了解決 ref 無法傳遞給函數(shù)組件的問題,React 提供了 forwardRef。

核心作用:

5.2 基本用法

?? 代碼示例(修復封裝問題):

import { forwardRef } from 'react';
// --- 子組件:使用 forwardRef ---
const MyInput = forwardRef((props, ref) => {  // ① 接收第二個參數(shù) ref
  return (
    <div className="input-wrapper">
      <label>用戶名:</label>
      {/* ② 將 ref 綁定到內部 DOM */}
      <input {...props} ref={ref} />
    </div>
  );
});
// --- 父組件:無需修改 ---
function Form() {
  const inputRef = useRef(null);
  const handleSubmit = () => {
    // ? 成功:inputRef.current 指向內部的 <input>
    inputRef.current?.focus();
  };
  return (
    <form onSubmit={handleSubmit}>
      <MyInput ref={inputRef} name="username" />
      <button type="submit">提交</button>
    </form>
  );
}

5.3 協(xié)作原理圖解

┌─────────────────────────────────────────────────────────┐
│                      父組件                              │
│   const inputRef = useRef(null)                         │
│   inputRef.current → <input> DOM 節(jié)點 ?                  │
│                        ↓                                │
│   <MyInput ref={inputRef} />                            │
│                        ↓                                │
│              ┌───────────────────┐                      │
│              │  forwardRef 包裝   │                      │
│              │   (轉發(fā) ref)       │                      │
│              └───────────────────┘                      │
│                        ↓                                │
│              ┌───────────────────┐                      │
│              │    子組件 MyInput   │                      │
│              │                   │                      │
│              │  (props, ref)     │ ← ① ref 作為第二參數(shù)  │
│              │                   │                      │
│              │  <input ref={ref} │ ← ② 綁定到內部 DOM    │
│              └───────────────────┘                      │
└─────────────────────────────────────────────────────────┘

5.4 完整協(xié)作流程

表格

步驟

代碼

說明

1. 父組件創(chuàng)建 ref

const inputRef = useRef(null)

創(chuàng)建引用容器

2. 父組件傳遞 ref

<MyInput ref={inputRef} />

將 ref 傳給子組件

3. 子組件接收 ref

forwardRef((props, ref) => {...})

ref 作為第二參數(shù)

4. 子組件綁定 DOM

<input ref={ref} />

將 ref 綁定到內部元素

5. 父組件操作 DOM

inputRef.current.focus()

成功調用 DOM API

6. useImperativeHandle:自定義暴露接口的進階方案

6.1 為什么需要 useImperativeHandle?

使用 forwardRef 后,父組件可以拿到子組件內部的 DOM 節(jié)點。但這帶來了一個新問題:

?? 代碼示例(暴露過多的問題):

// 子組件
const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});
// 父組件
function Parent() {
  const inputRef = useRef(null);
  const handleClick = () => {
    // ?? 問題:父組件可以訪問 input 的所有屬性和方法
    inputRef.current.focus();           // 這是預期的
    inputRef.current.value = 'hacked';  // 這也可以!
    inputRef.current.style.color = 'red'; // 這也可以!
    inputRef.current.form.submit();     // 甚至可以訪問表單!
  };
  return <MyInput ref={inputRef} />;
}

這違背了什么原則?

  • 封裝原則:子組件的內部實現(xiàn)不應該完全暴露給父組件
  • 最小權限原則:父組件應該只擁有它需要的能力
  • 接口穩(wěn)定性:如果子組件內部 DOM 結構變化,父組件代碼可能崩潰

6.2 useImperativeHandle 的作用

useImperativeHandle 允許子組件自定義暴露給父組件的 ref 值。

核心功能:

不是直接暴露 DOM 節(jié)點,而是暴露一個自定義對象,只包含你想要暴露的方法。

6.3 基本用法

?? 代碼示例(使用 useImperativeHandle):

import { forwardRef, useImperativeHandle, useRef } from 'react';
// --- 子組件:自定義暴露接口 ---
const MyInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);
  // 自定義暴露給父組件的方法
  useImperativeHandle(ref, () => ({
    // 只暴露 focus 方法
    focus: () => {
      inputRef.current?.focus();
    },
    // 只暴露 select 方法
    select: () => {
      inputRef.current?.select();
    },
    // 只暴露 getValue 方法
    getValue: () => {
      return inputRef.current?.value || '';
    },
    // 只暴露 setValue 方法
    setValue: (value) => {
      if (inputRef.current) {
        inputRef.current.value = value;
      }
    }
  }));
  return <input {...props} ref={inputRef} />;
});
// --- 父組件:只能調用暴露的方法 ---
function Parent() {
  const inputRef = useRef(null);
  const handleClick = () => {
    // ? 可以調用暴露的方法
    inputRef.current?.focus();
    inputRef.current?.select();
    const value = inputRef.current?.getValue();
    // ? 以下操作會報錯(因為不是暴露的方法)
    // inputRef.current.value = 'hacked';  // undefined
    // inputRef.current.style.color = 'red'; // undefined
  };
  return <MyInput ref={inputRef} />;
}

6.4 協(xié)作原理圖解

┌─────────────────────────────────────────────────────────┐
│                      父組件                              │
│   const inputRef = useRef(null)                         │
│   inputRef.current → { focus, select, getValue } ?       │
│                        ↓                                │
│   <MyInput ref={inputRef} />                            │
│                        ↓                                │
│              ┌───────────────────┐                      │
│              │  forwardRef 包裝   │                      │
│              └───────────────────┘                      │
│                        ↓                                │
│              ┌───────────────────┐                      │
│              │    子組件 MyInput   │                      │
│              │                   │                      │
│              │  useImperative    │                      │
│              │  Handle(ref, ()=> │                      │
│              │   { focus, ... }) │ ← 自定義返回對象      │
│              │                   │                      │
│              │  const inputRef   │                      │
│              │  = useRef(null)   │ ← 內部真實 DOM 引用   │
│              │                   │                      │
│              │  <input ref={     │                      │
│              │   inputRef} />    │                      │
│              └───────────────────┘                      │
└─────────────────────────────────────────────────────────┘

6.5 對比:直接暴露 DOM vs 自定義暴露

對比維度

直接暴露 DOM

自定義暴露 (useImperativeHandle)

封裝性

? 差,內部結構完全暴露

? 好,只暴露必要接口

安全性

? 低,父組件可任意修改

? 高,父組件只能調用指定方法

靈活性

? 低,受限于 DOM API

? 高,可以組合多個操作

可維護性

? 低,DOM 結構變化會影響父組件

? 高,內部實現(xiàn)變化不影響父組件

TypeScript 支持

? 自動推斷 DOM 類型

? 可自定義接口類型

6.6 進階場景:組合多個內部 ref

有時子組件內部有多個 DOM 節(jié)點,我們可以用 useImperativeHandle 組合它們:

?? 代碼示例(組合多個 ref):

const ComplexInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);
  const labelRef = useRef(null);
  const errorRef = useRef(null);
  useImperativeHandle(ref, () => ({
    // 組合多個內部 ref 的操作
    focus: () => {
      inputRef.current?.focus();
      labelRef.current?.classList.add('focused');
    },
    blur: () => {
      inputRef.current?.blur();
      labelRef.current?.classList.remove('focused');
    },
    showError: (message) => {
      if (errorRef.current) {
        errorRef.current.textContent = message;
        errorRef.current.style.display = 'block';
      }
    },
    hideError: () => {
      if (errorRef.current) {
        errorRef.current.style.display = 'none';
      }
    },
    validate: () => {
      const value = inputRef.current?.value || '';
      if (value.length < 3) {
        this.showError('至少需要 3 個字符');
        return false;
      }
      this.hideError();
      return true;
    }
  }));
  return (
    <div className="complex-input">
      <label ref={labelRef}>{props.label}</label>
      <input {...props} ref={inputRef} />
      <div ref={errorRef} className="error" style={{ display: 'none' }} />
    </div>
  );
});

6.7 進階場景:依賴項優(yōu)化

useImperativeHandle 接受第三個參數(shù) deps,用于優(yōu)化性能:

?? 代碼示例(添加依賴項):

const MyInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);
  const [isValid, setIsValid] = useState(true);
  useImperativeHandle(
    ref,
    () => ({
      focus: () => {
        inputRef.current?.focus();
      },
      // 方法內部可以使用組件的狀態(tài)
      validate: () => {
        const value = inputRef.current?.value || '';
        const result = value.length >= 3;
        setIsValid(result);  // 可以觸發(fā)狀態(tài)更新
        return result;
      },
      // 可以訪問 props
      getProps: () => {
        return {
          name: props.name,
          placeholder: props.placeholder
        };
      }
    }),
    [props.name, props.placeholder]  // 依賴項,變化時重新創(chuàng)建方法
  );
  return <input {...props} ref={inputRef} />;
});

依賴項的作用:

場景

是否需要 deps

說明

方法內部使用 props

? 需要

props 變化時需要更新方法

方法內部使用 state

? 需要

state 變化時需要更新方法

方法只操作 DOM

? 可選

DOM 引用穩(wěn)定,可不加

方法調用其他函數(shù)

? 需要

被調用的函數(shù)變化時需要更新

7. 最佳實踐與注意事項

7.1 何時使用 useRef + forwardRef + useImperativeHandle?

場景

推薦方案

說明

自動聚焦輸入框

? useRef + forwardRef

簡單場景,直接暴露 DOM

表單組件庫

? + useImperativeHandle

需要封裝,限制外部訪問

集成第三方庫

? useRef

直接操作 DOM

滾動到指定位置

? useRef + forwardRef

需要跨組件控制

表單數(shù)據提交

? 優(yōu)先使用受控組件

能用 State 解決的不用 ref

樣式變化

? 優(yōu)先使用 className/State

聲明式更合適

條件渲染

? 優(yōu)先使用 State

ref 不適合控制渲染邏輯

核心原則:

┌────────────────────────────────────────────────────────────┐
│                    Ref 使用決策樹                           │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  需要操作 DOM 嗎?                                          │
│       ↓ 是                                                  │
│  是原生 DOM 元素嗎?                                        │
│       ↓ 是 → 直接用 useRef                                  │
│       ↓ 否                                                  │
│  是自定義函數(shù)組件嗎?                                        │
│       ↓ 是 → 用 forwardRef                                  │
│       ↓                                                    │
│  需要限制父組件訪問權限嗎?                                   │
│       ↓ 是 → 加上 useImperativeHandle                       │
│       ↓ 否 → 直接轉發(fā) ref                                   │
│                                                            │
└────────────────────────────────────────────────────────────┘

7.2 注意事項

?? 注意 1:forwardRef 會創(chuàng)建新組件

const MyComponent = forwardRef(...);
// 需要手動設置 displayName,方便調試
MyComponent.displayName = 'MyComponent';

?? 注意 2:ref 不能用在條件渲染中

?? 注意 3:避免過度使用

// ? 能用 State 解決的,不要用 ref
function BadExample() {
  const countRef = useRef(0);  // 應該用 useState
  const increment = () => {
    countRef.current++;  // 不會觸發(fā)重渲染
  };
}
// ? 正確做法
function GoodExample() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1);  // 會觸發(fā)重渲染
  };
}

?? 注意 4:useImperativeHandle 的依賴項

// ? 忘記加依賴項,方法內部使用過時的 props/state
useImperativeHandle(ref, () => ({
  getValue: () => props.defaultValue  // 可能不是最新值
}));
// ? 加上依賴項
useImperativeHandle(ref, () => ({
  getValue: () => props.defaultValue
}), [props.defaultValue]);

?? 注意 5:避免循環(huán)引用

// ? 不要在 useImperativeHandle 中調用父組件傳入的函數(shù)
// 這可能導致循環(huán)依賴
useImperativeHandle(ref, () => ({
  submit: () => {
    props.onSubmit();  // 如果 onSubmit 又調用了 ref 的方法,會循環(huán)
  }
}));

7.3 性能考量

優(yōu)化點

說明

建議

依賴項精簡

只添加真正需要的依賴

避免不必要的重新創(chuàng)建

方法緩存

復雜方法可以用 useCallback 包裹

減少內存分配

避免大對象

不要暴露整個 DOM 對象

只暴露必要方法

懶加載 ref

不常用的 ref 可以延遲創(chuàng)建

減少初始渲染負擔

8. 總結

8.1 知識回顧

┌────────────────────────────────────────────────────────────┐
│                    React Ref 機制演進                       │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  Class 組件 (命令式)                                        │
│       ↓ 轉變原因:可維護性、可預測性、性能                    │
│  函數(shù)組件 (聲明式)                                          │
│       ↓ 局限性:無法處理瀏覽器行為                           │
│  useRef (命令式逃生艙)                                      │
│       ↓ 困境:ref 無法傳遞給函數(shù)組件                         │
│  forwardRef (組件邊界橋梁)                                   │
│       ↓ 問題:暴露過多,破壞封裝                             │
│  useImperativeHandle (自定義接口)                            │
│                                                            │
└────────────────────────────────────────────────────────────┘

8.2 核心要點

概念

核心作用

使用場景

關鍵代碼

聲明式

描述 UI 應該長什么樣

90% 的常規(guī)渲染場景

useStateuseEffect

命令式

執(zhí)行瞬時操作

聚焦、滾動、第三方庫

ref.current.action()

useRef

獲取 DOM 引用

需要操作原生 DOM 時

const ref = useRef(null)

forwardRef

轉發(fā) ref 給子組件

封裝組件需要被外部控制時

forwardRef((props, ref) => {})

useImperativeHandle

自定義暴露方法

需要限制外部訪問權限時

useImperativeHandle(ref, () => ({...}))

8.3 三者協(xié)作關系

┌────────────────────────────────────────────────────────────┐
│                    三者協(xié)作關系圖                           │
├────────────────────────────────────────────────────────────┤
│                                                            │
│   父組件                    子組件                          │
│   ┌──────────┐            ┌──────────────┐                │
│   │ useRef   │ ────────→  │ forwardRef   │                │
│   │ (創(chuàng)建)   │   ref      │ (接收并轉發(fā))  │                │
│   └──────────┘            └──────┬───────┘                │
│                                  │                         │
│                                  ↓                         │
│                          ┌──────────────┐                │
│                          │useImperative │                │
│                          │Handle        │                │
│                          │(自定義暴露)   │                │
│                          └──────┬───────┘                │
│                                  │                         │
│                                  ↓                         │
│                          ┌──────────────┐                │
│                          │ 內部 useRef   │                │
│                          │ (真實 DOM)    │                │
│                          └──────────────┘                │
│                                                            │
└────────────────────────────────────────────────────────────┘

8.4 一句話總結

useRef 是聲明式世界里的命令式逃生艙,forwardRef 是打通組件邊界的橋梁,useImperativeHandle 是保護封裝性的守門人。三者協(xié)作讓我們既能享受聲明式的優(yōu)雅,又能保留對瀏覽器行為的終極控制權,同時不破壞組件的封裝原則。

到此這篇關于深入理解React Ref 機制useRef 與 forwardRef 的協(xié)作原理的文章就介紹到這了,更多相關React useRef 與 forwardRef 協(xié)作原理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • hooks中useEffect()使用案例詳解

    hooks中useEffect()使用案例詳解

    這篇文章主要介紹了hooks中useEffect()使用總結,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-09-09
  • 在create-react-app中使用sass的方法示例

    在create-react-app中使用sass的方法示例

    這篇文章主要介紹了在create-react-app中使用sass的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • React Router V4使用指南(精講)

    React Router V4使用指南(精講)

    這篇文章主要介紹了React Router V4使用指南(精講),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • react類標簽的生命周期詳解

    react類標簽的生命周期詳解

    在React類組件中,生命周期方法是非常重要的概念,它們允許我們在組件的不同階段執(zhí)行代碼,這包括在組件掛載、更新以及卸載時執(zhí)行的生命周期方法,本文通過介紹React類組件中的生命周期方法,旨在幫助開發(fā)者深入理解組件的生命周期管理
    2024-11-11
  • VSCode 配置React Native開發(fā)環(huán)境的方法

    VSCode 配置React Native開發(fā)環(huán)境的方法

    本篇文章主要介紹了VSCode 配置React Native開發(fā)環(huán)境的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • forwardRef?中React父組件控制子組件的實現(xiàn)代碼

    forwardRef?中React父組件控制子組件的實現(xiàn)代碼

    forwardRef 用于拿到父組件傳入的 ref 屬性,這樣在父組件便能通過 ref 控制子組件,這篇文章主要介紹了forwardRef?-?React父組件控制子組件的實現(xiàn)代碼,需要的朋友可以參考下
    2024-01-01
  • react簡單實現(xiàn)防抖和節(jié)流

    react簡單實現(xiàn)防抖和節(jié)流

    在日常開發(fā)中,我們經常會有防抖和節(jié)流的需要,可以減小服務器端壓力,提升用戶體驗,本文就詳細的介紹了react簡單實現(xiàn)防抖和節(jié)流,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-05-05
  • React實現(xiàn)倒計時功能組件

    React實現(xiàn)倒計時功能組件

    這篇文章主要為大家詳細介紹了如何通過React實現(xiàn)一個倒計時功能組件,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以了解下
    2023-09-09
  • 淺談箭頭函數(shù)寫法在ReactJs中的使用

    淺談箭頭函數(shù)寫法在ReactJs中的使用

    這篇文章主要介紹了淺談箭頭函數(shù)寫法在ReactJs中的使用,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • React中的ref屬性的使用示例詳解

    React中的ref屬性的使用示例詳解

    React 提供了 refrefref 屬性,讓我們可以引用組件的實例或者原生 DOM 元素,使用 refrefref,可以在父組件中調用子組件暴露出來的方法,或者調用原生 element 的 API,這篇文章主要介紹了React中的ref屬性的使用,需要的朋友可以參考下
    2023-04-04

最新評論

金寨县| 隆子县| 外汇| 凉城县| 宝清县| 怀远县| 灯塔市| 盐城市| 沈阳市| 汉阴县| 云梦县| 句容市| 延边| 永州市| 望奎县| 祁连县| 永寿县| 茶陵县| 昆山市| 普兰县| 芒康县| 灵川县| 芜湖县| 双柏县| 宁德市| 社会| 临颍县| 拉孜县| 海兴县| 洪江市| 丁青县| 巴林右旗| 文山县| 漠河县| 通许县| 汶川县| 微博| 禹城市| 合水县| 嘉荫县| 昆山市|