【サクッとReact】Refを使ってDOMノードを操作しよう

Posted: September 12, 2020

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操作を安全に行いましょう!🙌