`forwardRef`という「越境」:カプセル化を壊さずにDOMを操るための作法
やあ。Reactのコンポーネント設計に日々頭を悩ませている諸君、お疲れ様。
今日はReactの「境界線」の話をしよう。普段、コンポーネントを綺麗に分割している君たちなら一度は遭遇したはずだ。「この子コンポーネントの内部にある``に、親から直接フォーカスを当てたい」という、あのジレンマだ。
Reactにおいてコンポーネントは「ブラックボックス」であるべきだ。外部からは中身が見えず、propsという窓口を通してのみやり取りする。だが、`ref`だけは別格だ。これはDOMという「生」の存在を扱うための禁断のパスポートだ。今回は、このパスポートを安全に、かつ美しく発行する`forwardRef`の極意を伝授しよう。
—
なぜ `ref` はそのままでは渡せないのか?
まず、Reactの設計思想を思い出してほしい。親コンポーネントから子コンポーネントに`ref`というpropsを渡そうとしても、Reactはデフォルトでそれをブロックする。「`ref`は予約語であり、propsとして渡すものではない」とされているからだ。
ブラウザの裏側で何が起きているか。Reactは仮想DOMを構築し、最終的に実際のDOM要素に変換する。`ref`は、その「生成されたDOMノード」への参照を保持するための特別なフックだ。コンポーネントを関数として定義したとき、Reactはその関数自体を呼び出してUIを構築するが、コンポーネントの外側から内部のDOMノードに直接アクセスさせるのは、カプセル化の原則を破壊することに繋がる。
そこで登場するのが`forwardRef`だ。「このコンポーネントは、外部からの`ref`を受け入れて、内部の特定のDOMに接続することを許可しますよ」と明示的に宣言するための門番だと思えばいい。
—
実践:`forwardRef` の綺麗な書き方
現場でよくある「フォーカス制御を伴うカスタムInput」を例に実装してみよう。
import React, { forwardRef, useImperativeHandle, useRef } from ‘react’;
/
- カスタムInputコンポーネント
- forwardRefを使って、親から渡されたrefを内部のinput要素へ橋渡しする
/
const CustomInput = forwardRef((props, ref) => {
// 内部で独自の処理が必要な場合、useImperativeHandleを組み合わせるのがプロの技だ。
// 単にrefを渡すだけなら、引数のrefをそのままinputのref属性に渡すだけでいい。
return (
);
});
// 表示名を設定しておくと、React DevToolsでデバッグする際に非常に助かる
CustomInput.displayName = ‘CustomInput’;
export default CustomInput;
そして、これを呼び出す親側はこうなる。
const ParentComponent = () => {
const inputRef = useRef(null);
const handleFocus = () => {
// ref.currentがnullでないことを確認してから操作する
inputRef.current?.focus();
};
return (
);
};
—
現場で「膝を打つ」ための注意点
`forwardRef`を使う際に、シニアとして君たちに守ってほしい鉄則がいくつかある。
1. 安易な`ref`の公開は避ける
`ref`を渡すということは、外部からコンポーネントの内部構造(DOMの構造)を露出させるということだ。本当にそれが必要か?もし単に「値を同期させたい」だけなら、`useImperativeHandle`を使って、「外部から触れる操作(メソッド)」を制限付きで公開する方が、将来的なリファクタリングで事故が起きにくい。
2. `displayName` を忘れるな
`forwardRef`でラップされたコンポーネントは、DevTools上では単に`ForwardRef`と表示され、デバッグが困難になる。コードの末尾で必ず`Component.displayName = ‘ComponentName’`と宣言する癖をつけろ。これはチーム開発における「思いやり」だ。
3. DOMへの依存を最小限に
`ref`を渡してDOMを直接操作するということは、Reactの宣言的なデータフローを一部バイパスしているのと同じだ。`focus()`や`scrollIntoView()`のような「DOMのAPIそのものを叩くしかない」ケース以外では、極力ステートとpropsで解決できないか一度立ち止まって考えてほしい。
—
まとめ
`forwardRef`は便利だ。しかし、強力なツールであるほど、使い所を間違えるとコードは一気にスパゲッティ化する。
「DOMに触れる」ことは、Reactにおいて最後の手段だ。だが、どうしても必要なとき、`forwardRef`は君の強力な味方になる。この境界線をどう引くか。その設計の美学こそが、ジュニアとシニアを分かつ境界線でもある。
明日からのコードで、適切に門を開き、適切に守る実装を心がけてみてくれ。何か詰まったら、いつでも聞きに来い。現場からは以上だ。

コメント