Refを使ってDOMノードを操作しよう

Posted: 2020/9/13
React/
Ref

React でのデータフローは、props を用いて親コンポーネントから子コンポーネントにデータを渡すのが基本です。子コンポーネントの DOM ノードを変更する時は新しい props による再レンダーで行います。

この方法以外で、子コンポーネントの DOM ノード操作などを行う時に React ではRef を用いることができます。

Ref の使い方

Ref の作成

Ref オブジェクトはReact.createRef()により作成され、DOM ノードのref 属性に入れることで React 要素に紐づけられます。

this.sampleRef = React.createRef();

Ref オブジェクトはコンポーネントのマウント時にインスタンスプロパティに割り当てられるので子コンポーネントなど、コンポーネントを跨いだ参照が可能です。

Ref のアクセス

クラスコンポーネントの render メソッドにref属性を持つ DOM ノードが渡されると、その要素への参照は、ref のcurrent属性で行えるようになります。また、コンポーネントのマウント時current属性に DOM 要素が割り当てられ、アンマウント時に null に戻ります。

const value = this.sampleRef.current;

以下に簡単な例を示します。

Input 要素に Ref で直接アクセスしていることがわかります。

class RefSample extends React.Component {
  constructor(props) {
    super(props);
    // Input DOMノードを操作するためにRefオブジェクトを作成
    this.sampleRef = React.createRef();
  }

  componentDidMount() {
    // refのcurrent属性を使い、ノードを取得
    // 直接input要素にフォーカスさせる
    this.sampleRef.current.focus();
  }
  render() {
    // Refオブジェクトをref属性に代入し、DOMノードを参照する
    return <input type="text" ref={this.sampleRef} />;
  }
}

関数コンポーネントで要素の参照を行うには

関数コンポーネントではuseRef を用いることで要素の参照を行うことができます。useRefは、引数で初期化された Ref オブジェクトを返し、書き換え可能な値をcurrent属性に保持します。

// Refオブジェクトを返す
const refContainer = useRef(initialValue);
// DOMノードを参照
refContainer.current;

ではクラスコンポーネントの例を関数コンポーネントに置き換えてみましょう。

function RefSample() {
  const sampleRef = useRef(null);

  useEffect(() => {
    sampleRef.current.focus();
  }, []);
  return <input type="text" ref={sampleRef} />;
}

ref 属性に Ref オブジェクトを入れ、DOM ノードに変更があるたびに Ref オブジェクトの current 属性の値を変更された DOM ノードに設定します。

また、useRef は current 属性を書き換えても再レンダーが発生しないので、DOM ノードを ref に割り当てたり、解除する時に何らかの処理を行いたいときはコールバック refを使用してください。

コールバック Ref

コールバック Refは、より細かな Ref の制御を可能とします。

createRef()によって作成された Ref オブジェクトを渡す代わりに、関数を渡します。この関数は、引数として React コンポーネントのインスタンスまたは参照先の DOM ノードを受け取ります。これを用いることでDOM ノードの参照を受け取り、コールバック関数内で処理を挟むことができます。

以下に例を示します。

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);

    this.textInput = null;
    // DOM要素を受け取ってtextInputに設定
    this.setTextInputRef = element => {
      this.textInput = element;
    };
    // 参照されたDOM要素が格納されたtextInputがあればフォーカスする
    this.focusTextInput = () => {
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // マウント時にInput要素を直接オートフォーカスします
    this.focusTextInput();
  }

  render() {
    // インスタンスフィールド(例えば this.textInput)にテキスト入力の DOM 要素への
    // 参照を保存するために `ref` コールバックを使用してください。
    return (
      <div>
        {/* このinput要素は参照される */}
        <input type="text" ref={this.setTextInputRef} />
        {/* buttonをクリックすると参照されたDOM要素にフォーカスする */}
        <button onClick={this.focusTextInput}>focus</button>
      </div>
    );
  }
}

ref コールバックを用いて DOM ノードへの参照をインスタンスプロパティに格納していることがわかります。

Ref の注意点

ここまで Ref を用いた DOM ノード操作について説明してきましたが、基本的に DOM 操作は React に任せたほうがよいので、あまり多用しないことをおすすめします。

また、Ref を使ってよい場面としては、以下のような例が挙げられます。

  • フォーカス、テキストの選択およびメディアの再生の管理
  • アニメーションの発火
  • サードパーティの DOM ライブラリとの統合

これらを考えて、うまく Ref を使っていきましょう 🙆‍♂️

まとめ

  • DOM ノード操作などを行う時に React ではRef を用いることができる
  • Ref オブジェクトはReact.createRef()により作成され、DOM ノードのref 属性に入れることで React 要素に紐づけられる
  • ref のcurrent属性で要素を参照できる
  • 関数コンポーネントでは createRef の代わりにuseRef を用いる
  • コールバック Ref で DOM ノードの参照を受け取り、コールバック関数内で処理を挟むことができる

以上です!Ref を上手く使って React の DOM 操作を安全に行いましょう!🙌