この記事は古くなっており、今後更新されません。新しい React 日本語ドキュメントであるja.react.dev をご利用ください。
以下の新しいドキュメントで最新の React の使い方がライブサンプル付きで学べます。
ref のフォワーディングはあるコンポーネントを通じてその子コンポーネントのひとつにref を自動的に渡すテクニックです。これは基本的にはアプリケーション内のほとんどのコンポーネントで必要ありません。しかし、コンポーネントの種類によっては、特に再利用可能なコンポーネントライブラリにおいては、便利なものとなるかもしれません。一般的なシナリオについて以下で述べます。
ネイティブのbutton
DOM 要素をレンダーするFancyButton
というコンポーネントを考えてみましょう:
functionFancyButton(props){return(<buttonclassName="FancyButton">{props.children}</button>);}
React コンポーネントは、レンダーの結果も含め、実装の詳細を隠蔽します。FancyButton
を使用する他のコンポーネントは内側のbutton
DOM 要素に対するref を取得する必要は通常ありません 。これは、互いのコンポーネントの DOM 構造に過剰に依存することを防ぐので、良いことです。
そういったカプセル化はFeedStory
やComment
のようなアプリケーションレベルのコンポーネントでは望ましいことではありますが、FancyButton
やMyTextInput
といった非常に多くのところで再利用可能な “末梢の” コンポーネントでは不便である可能性があります。このようなコンポーネントは、アプリケーションのいたるところで通常の DOM であるbutton
やinput
と同様に扱われる傾向にあり、フォーカス、要素の選択、アニメーションをこなすにはそれら DOM ノードにアクセスすることが避けられないかもしれません。
ref のフォワーディングはオプトインの機能であり、それにより、コンポーネントがref
を受け取って、それをさらに下層の子に渡せる(つまり、ref を “転送” できる)ようになります。
下の例では、FancyButton
は渡されたref
を取得して、それをレンダーするbutton
DOM にフォワーディングするために、React.forwardRef
を使っています。
const FancyButton= React.forwardRef((props, ref)=>(<buttonref={ref}className="FancyButton">{props.children}</button>));// You can now get a ref directly to the DOM button:const ref= React.createRef();<FancyButtonref={ref}>Click me!</FancyButton>;
このように、FancyButton
を使ったコンポーネントは下層のbutton
DOM ノードの ref を取得することができ、必要であればbutton
DOM を直接使うかのように、DOM にアクセスすることができます。
上の例で、何が起こっているかを順々に説明します。
React.createRef
を呼び、React ref をつくり、それをref
変数に代入します。ref
を<FancyButton ref={ref}>
に JSX の属性として指定することで渡します。ref
を、forwardRef
内の関数(props, ref) => ...
の 2 番目の引数として渡します。ref
を<button ref={ref}>
に JSX の属性として指定することで渡します。ref.current
は<button>
DOM ノードのことを指すようになります。補足
2 番目の引数
ref
はReact.forwardRef
の呼び出しを使ってコンポーネントを定義したときにだけ存在します。通常の関数またはクラスコンポーネントはref
引数を受け取らず、ref は props からも利用できません。ref のフォワーディング先は DOM コンポーネントだけにとどまりません。クラスコンポーネントインスタンスに対しても ref をフォワーディングできます。
コンポーネントライブラリの中で、forwardRef
を使い始めた場合、破壊的変更として扱い、新しいメジャーバージョンをリリースすべきです。ライブラリが外から見て今までと違う挙動(例えば、どの値が ref に代入されるかや、どの型がエクスポートされるのか)をする可能性があり、古い挙動に依存しているアプリケーションや他のライブラリを壊す可能性があるからです。
React.forwardRef
が存在する場合だけ、条件的にReact.forwardRef
を適用することも同じ理由で推奨されません:そのような実装は、React そのものを更新したとき、ライブラリがどのように振る舞うかを変えてしまい、ユーザのアプリケーションを破壊する可能性があるからです。
このテクニックは高階コンポーネント(HOC としても知られています)においても特に便利です。コンポーネントの props をコンソールにログ出力する HOC を例として考えてみましょう。
functionlogProps(WrappedComponent){classLogPropsextendsReact.Component{componentDidUpdate(prevProps){ console.log('old props:', prevProps); console.log('new props:',this.props);}render(){return<WrappedComponent{...this.props}/>;}}return LogProps;}
“logProps” HOC はすべてのprops
をラップするコンポーネントに渡すので、レンダーされる出力は同じになるでしょう。例えば、“fancy button” コンポーネントに渡されるすべての props をログとして記録するために、この HOC を使用することができます。
classFancyButtonextendsReact.Component{focus(){// ...}// ...}// Rather than exporting FancyButton, we export LogProps.// It will render a FancyButton though.exportdefaultlogProps(FancyButton);
ところが上記の例には欠陥があります。これでは ref が渡されないのです。ref
は props のひとつではないからです。key
と同様に ref は React では props とは違う扱いになります。HOC に対する ref を追加した場合、ラップされたコンポーネントではなく、一番外側のコンテナコンポーネントを参照します。
これはFancyButton
コンポーネントに紐付けられることを意図した ref が、実際にはLogProps
コンポーネントに紐付けられてしまうことを意味します。
import FancyButtonfrom'./FancyButton';const ref= React.createRef();// The FancyButton component we imported is the LogProps HOC.// Even though the rendered output will be the same,// Our ref will point to LogProps instead of the inner FancyButton component!// This means we can't call e.g. ref.current.focus()<FancyButtonlabel="Click Me"handleClick={handleClick}ref={ref}/>;
幸いにも、React.forwardRef
API を使って、内側のFancyButton
コンポーネントに対して ref を明示的に転送することができます。React.forwardRef
は render 関数を受け取り、その関数はprops
とref
を引数として取り、React ノードを返します。例えば、
functionlogProps(Component){classLogPropsextendsReact.Component{componentDidUpdate(prevProps){ console.log('old props:', prevProps); console.log('new props:',this.props);}render(){const{forwardedRef,...rest}=this.props;// Assign the custom prop "forwardedRef" as a refreturn<Componentref={forwardedRef}{...rest}/>;}}// Note the second param "ref" provided by React.forwardRef.// We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"// And it can then be attached to the Component.return React.forwardRef((props, ref)=>{return<LogProps{...props}forwardedRef={ref}/>;});}
React.forwardRef
は render 関数を受け取ります。React DevTools は ref をフォワーディングしているコンポーネントとして何を表示すべきかを決定するために、この関数を使います。
例えば、次のコンポーネントは ”ForwardRef” として DevTools に表示されます。
const WrappedComponent= React.forwardRef((props, ref)=>{return<LogProps{...props}forwardedRef={ref}/>;});
render 関数に名前をつけると、DevTools はその名前を含めるようになります(例: ”ForwardRef(myFunction)”):
const WrappedComponent= React.forwardRef(functionmyFunction(props, ref){return<LogProps{...props}forwardedRef={ref}/>;});
ラップしているコンポーネントを含めるために、render 関数のdisplayName
を設定することもできます:
functionlogProps(Component){classLogPropsextendsReact.Component{// ...}functionforwardRef(props, ref){return<LogProps{...props}forwardedRef={ref}/>;}// Give this component a more helpful display name in DevTools.// e.g. "ForwardRef(logProps(MyComponent))"const name= Component.displayName|| Component.name; forwardRef.displayName=`logProps(${name})`;return React.forwardRef(forwardRef);}