已棄用

在 React 19 中,`forwardRef` 已不再必要。請改為將 `ref` 作為 prop 傳遞。

`forwardRef` 將在未來的版本中棄用。 在這裡了解更多。

`forwardRef` 讓您的元件可以使用ref 將 DOM 節點暴露給父元件。

const SomeComponent = forwardRef(render)

參考

`forwardRef(render)`

呼叫 `forwardRef()` 讓您的元件接收一個 ref 並將其轉發到子元件

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});

請參閱下面的更多範例。

參數

  • `render`:元件的渲染函式。React 使用元件從其父元件接收的 props 和 `ref` 來呼叫此函式。您返回的 JSX 將是元件的輸出。

回傳值

`forwardRef` 會返回一個可以在 JSX 中渲染的 React 元件。 與定義為普通函式的 React 元件不同,由 `forwardRef` 返回的元件還可以接收 `ref` prop。

注意事項

  • 在嚴格模式下,React 會**呼叫您的渲染函式兩次**,以便幫助您找到意外的非純函式。這僅在開發模式下發生,不會影響生產環境。如果您的渲染函式是純函式(它 seharusnya如此),這不應影響您組件的邏輯。其中一次呼叫的結果將被忽略。

render 函式

forwardRef 接受一個渲染函式作為參數。React 使用 propsref 呼叫此函式。

const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});

參數

  • props:父組件傳遞的屬性。

  • ref:父組件傳遞的 ref 屬性。ref 可以是一個物件或一個函式。如果父組件沒有傳遞 ref,它將是 null。您應該將收到的 ref 傳遞給另一個組件,或者將它傳遞給 useImperativeHandle

回傳值

forwardRef 會回傳一個您可以在 JSX 中渲染的 React 組件。與定義為一般函式的 React 組件不同,由 forwardRef 回傳的組件能夠接受 ref 屬性。


用法

將 DOM 節點暴露給父組件

預設情況下,每個組件的 DOM 節點都是私有的。但是,有時將 DOM 節點暴露給父組件會很有用,例如允許將其設為焦點。若要啟用此功能,請將您的組件定義包裝到 forwardRef() 中。

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} />
</label>
);
});

您將在 props 之後收到一個 ref 作為第二個參數。將它傳遞給您想要暴露的 DOM 節點。

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} ref={ref} />
</label>
);
});

這讓父組件 Form 可以訪問由 MyInput 暴露的 <input> DOM 節點

function Form() {
const ref = useRef(null);

function handleClick() {
ref.current.focus();
}

return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}

這個 Form 組件 傳遞一個 refMyInputMyInput 組件將該 ref *轉發* 給 <input> 瀏覽器標籤。因此,Form 組件可以訪問該 <input> DOM 節點並在其上呼叫 focus()

請記住,將 ref 暴露給組件內部的 DOM 節點會讓您以後更難更改組件的內部結構。您通常會從可重複使用的低階組件(如按鈕或文字輸入)中暴露 DOM 節點,但您不會對應用程式級組件(如大頭貼或評論)執行此操作。

轉發 ref 的範例

範例 1(之) 2:
聚焦文字輸入框

點擊按鈕將會聚焦輸入框。Form 元件定義了一個 ref 並將其傳遞給 MyInput 元件。MyInput 元件將該 ref 轉發到瀏覽器的 <input>。這讓 Form 元件可以聚焦 <input>

import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
  }

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}


跨多個元件轉發 ref

您可以將 ref 轉發到您自己的元件(例如 MyInput),而不是轉發到 DOM 節點。

const FormField = forwardRef(function FormField(props, ref) {
// ...
return (
<>
<MyInput ref={ref} />
...
</>
);
});

如果該 MyInput 元件將 ref 轉發到它的 <input>,則 FormField 的 ref 將會提供您該 <input>

function Form() {
const ref = useRef(null);

function handleClick() {
ref.current.focus();
}

return (
<form>
<FormField label="Enter your name:" ref={ref} isRequired={true} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}

Form 元件定義一個 ref 並將其傳遞給 FormFieldFormField 元件將該 ref 轉發給 MyInput,然後再轉發到瀏覽器的 <input> DOM 節點。這就是 Form 存取該 DOM 節點的方式。

import { useRef } from 'react';
import FormField from './FormField.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
  }

  return (
    <form>
      <FormField label="Enter your name:" ref={ref} isRequired={true} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}


公開指令性控制代碼而不是 DOM 節點

您可以公開一個自定義物件(稱為*指令性控制代碼*),其中包含一組更受限的方法,而不是公開整個 DOM 節點。要做到這一點,您需要定義一個單獨的 ref 來保存 DOM 節點。

const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);

// ...

return <input {...props} ref={inputRef} />;
});

將您收到的 ref 傳遞給 useImperativeHandle,並指定您想要公開給 ref 的值。

import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);

useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);

return <input {...props} ref={inputRef} />;
});

如果某些元件取得了 MyInput 的 ref,它只會收到您的 { focus, scrollIntoView } 物件,而不是 DOM 節點。這讓您可以將您公開的 DOM 節點資訊限制在最小範圍內。

import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
    // This won't work because the DOM node isn't exposed:
    // ref.current.style.opacity = 0.5;
  }

  return (
    <form>
      <MyInput placeholder="Enter your name" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

閱讀更多關於使用指令性控制代碼的資訊。

陷阱

不要過度使用 ref。 您應該只將 ref 用於無法以 props 表示的*指令性*行為:例如,捲動到節點、聚焦節點、觸發動畫、選取文字等等。

如果您可以將某些內容表示為 prop,則不應該使用 ref。 例如,與其從 Modal 元件公開指令性控制代碼,例如 { open, close },不如將 isOpen 作為 prop,例如 <Modal isOpen={isOpen} />效果可以幫助您透過 props 公開指令性行為。


疑難排解

我的元件被 forwardRef 包裹,但它的 ref 總是 null

這通常表示您忘記實際使用您收到的 ref

例如,這個元件沒有對它的 ref 做任何事情。

const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input />
</label>
);
});

要修復它,請將 ref 傳遞給 DOM 節點或其他可以接受 ref 的元件。

const MyInput = forwardRef(function MyInput({ label }, ref) {
return (
<label>
{label}
<input ref={ref} />
</label>
);
});

如果某些邏輯是有條件的,那麼 MyInputref 也可能是 null

const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
{showInput && <input ref={ref} />}
</label>
);
});

如果 showInputfalse,則 ref 不會被轉發到任何節點,且 MyInput 的 ref 將保持空白。如果條件隱藏在另一個組件內(例如本例中的 Panel),則此情況特別容易被忽略。

const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
<Panel isExpanded={showInput}>
<input ref={ref} />
</Panel>
</label>
);
});