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 使用 props
和 ref
呼叫此函式。
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
組件 傳遞一個 ref 給 MyInput
。MyInput
組件將該 ref *轉發* 給 <input>
瀏覽器標籤。因此,Form
組件可以訪問該 <input>
DOM 節點並在其上呼叫 focus()
。
請記住,將 ref 暴露給組件內部的 DOM 節點會讓您以後更難更改組件的內部結構。您通常會從可重複使用的低階組件(如按鈕或文字輸入)中暴露 DOM 節點,但您不會對應用程式級組件(如大頭貼或評論)執行此操作。
範例 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 並將其傳遞給 FormField
。FormField
元件將該 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> ); }
疑難排解
我的元件被 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>
);
});
如果某些邏輯是有條件的,那麼 MyInput
的 ref
也可能是 null
。
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
{showInput && <input ref={ref} />}
</label>
);
});
如果 showInput
為 false
,則 ref 不會被轉發到任何節點,且 MyInput
的 ref 將保持空白。如果條件隱藏在另一個組件內(例如本例中的 Panel
),則此情況特別容易被忽略。
const MyInput = forwardRef(function MyInput({ label, showInput }, ref) {
return (
<label>
{label}
<Panel isExpanded={showInput}>
<input ref={ref} />
</Panel>
</label>
);
});