forwardRef
forwardRef
を使用すると、コンポーネントは ref を使用して親コンポーネントにDOMノードを公開できます。
const SomeComponent = forwardRef(render)
リファレンス
forwardRef(render)
コンポーネントがrefを受信し、それを子コンポーネントに転送できるようにするには、forwardRef()
を呼び出します
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});
パラメータ
render
: コンポーネントのレンダリング関数。Reactはこの関数を、コンポーネントが親から受信したpropsとref
を使用して呼び出します。返されるJSXは、コンポーネントの出力になります。
戻り値
forwardRef
は、JSXでレンダリングできるReactコンポーネントを返します。単純な関数として定義されたReactコンポーネントとは異なり、forwardRef
によって返されるコンポーネントは、ref
propを受信することもできます。
注意事項
- StrictModeでは、Reactはレンダー関数を2回呼び出し、意図しない副作用を見つけるのに役立てます。これは開発モードのみの動作であり、本番環境には影響しません。レンダー関数が純粋関数である場合(そうあるべきです)、これはコンポーネントのロジックに影響を与えません。いずれかの呼び出しの結果は無視されます。
render
関数
forwardRef
は引数としてレンダー関数を受け取ります。Reactはこの関数をprops
と ref
を使って呼び出します。
const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});
パラメータ ...
-
props
: 親コンポーネントから渡される props です。 -
ref
: 親コンポーネントから渡されるref
属性です。ref
はオブジェクトまたは関数です。親コンポーネントが ref を渡していない場合、null
になります。受け取ったref
を別のコンポーネントに渡すか、useImperativeHandle
に渡す必要があります。
戻り値 ...
forwardRef
は、JSX でレンダリングできる React コンポーネントを返します。単純な関数として定義された React コンポーネントとは異なり、forwardRef
によって返されるコンポーネントは ref
prop を受け取ることができます。
使用方法 ...
親コンポーネントへの 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 の後に2番目の引数として 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()
を呼び出すことができます。
コンポーネント内の DOM ノードへの ref を公開すると、後でコンポーネントの内部を変更することが難しくなることに注意してください。通常、ボタンやテキスト入力などの再利用可能な低レベルコンポーネントから 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の転送 ...
DOMノードに`ref`を転送する代わりに、`MyInput`のような独自のコンポーネントに転送できます。
const FormField = forwardRef(function FormField(props, ref) {
// ...
return (
<>
<MyInput ref={ref} />
...
</>
);
});
`MyInput`コンポーネントがその`<input>`にrefを転送する場合、`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`に転送し、`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ノード全体を公開する代わりに、より制限されたメソッドセットを持つ、*命令型ハンドル*と呼ばれるカスタムオブジェクトを公開できます。これを行うには、DOMノードを保持するために別のrefを定義する必要があります。
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を取得する場合、DOMノードではなく、`{ focus, scrollIntoView }`オブジェクトのみを受け取ります。これにより、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>
);
});