✏️

DOMの理解を深めることで、制御コンポーネント / 非制御コンポーネント を理解する

2023/10/09に公開

この記事のゴール

制御コンポーネント / 非制御コンポーネントとはReactを使用してフォームを操作・制御する方法に関する考え方、コンセプトのこと。
この記事を通してそれぞれの意味、違いを理解する。

[📝公式ドキュメント]

DOM(Document Object Model)

制御コンポーネント / 非制御コンポーネントについて理解する上で、DOMの理解が必須なのでまずはDOMについての理解を深める。

概要

  • ドキュメント(HTML文書やXML文書)をオブジェクトのツリー状に表現することで、ドキュメントをプログラムから操作・利用することを可能にする仕組み
  • DOMという仕組みを介してJavaScriptなどによって作られたプログラムがドキュメントの文書構造、スタイル、内容を変更することができる
    • つまり DOM は APIである
  • ブラウザはWebページを読み込むと、HTML文書を解析し、それをブラウザのメモリ内 に DOMツリー として構築する

DOMを確認してみる

DOMツリーの一番上にある要素は document オブジェクトとなっており、これがWebページへの入口の役割を果たしている。


引用:https://www.w3schools.com/js/js_htmldom.asp

従って、ブラウザのコンソールに document と入力するとページ全体を表現するDOMが返ってくる

これを console.dir を使ってみてみると、具体的にオブジェクトがどのようなプロパティによって構成されているかも確認できる。

次に、文書内のinput要素を確認してみる。

そうすると先ほどと同じようにinput要素がオブジェクトとしてどのような属性を持っているのかを確認することができる。

文字を入力などが起こるとブラウザはDOMを更新する

例えば、input フィールドの valueの値が最初は ""であったが、input フィールドに文字を入力するとブラウザがDOMを更新するため、文字入力後にinput要素が持つ属性を確認してみると、valueの値が入力した値に変更されていることがわかる。

このようにして、HTMLのフォーム要素においてそのフォームの値は通常DOMで管理されている。

Reactが値を管理するのが制御コンポーネント

しかし、Reactの世界ではこのフォームの値の管理をDOMに任せるのではなくReact内部で管理することができる。

つまり、Reactによって値を制御する、ということなのでこういったコンポーネントを「制御コンポーネント」という。

具体的には、コンポーネントの中のステートによってフォームの値が管理されている コンポーネントのこと。

従って、入力した値をフォームで送信する、というような場合にその値は useState を使って管理された値が送信されることとなる。

import React, { useState } from 'react'

export const ControlledComponentSample = () => {
  const [email, setEmail] = useState('')
  
  return (
    <>
      <label htmlFor="email">Email</label>
      <input name="email" id="email" type="text" value={email} onChange={(e)=> setEmail(e.target.value)}/>
      {/* 送信するのはuseStateで管理された値 */}
      <button onClick={()=> submit(email)}>送信</button>
    </>
  )
}

DOMが値を管理するのが非制御コンポーネント

先ほど見た例のようにReactでフォームの値を管理するのではなく、従来通りDOMでフォームの値を管理するようなコンポーネントがある時、それを「非制御コンポーネント」という。

つまり、Reactによってinput要素を作りレンダリングしたとして、その後inputに入力された値はReactの世界の話ではなくブラウザとDOMの世界の話というイメージ。

非制御コンポーネントの場合にReactでどのように入力値を扱うのか

それではReactで値を管理できない制御コンポーネントのフォームの値をReact側で扱いたい場合はどうすればいいのか?

一つの手段としてuseRefという Hook を使いDOM参照を得るという方法がある

useRef

useRef自体はuseState と同じく値を保持することができるHookで、例えば以下のように初期値に0を指定しrefを出力してみると以下のようになる。

const ref = useRef(0);
console.log(ref)
//=> { current: 0 }

useRefによって作られるオブジェクトには必ずcurrentプロパティがあり、ここに管理する値が入る。

( ただし、useRefuseStateとは違って値が更新されてもコンポーネントの再レンダリングは発生しない。)

useRefを活用してDOMノードを参照する

このuseRefを活用してDOMノードを参照することができる。

React要素に用意されているrefという属性に対してuseRefによって宣言した ref を渡すことで、ReactがこのReact要素のDOMノードを作る際に ref の current プロパティにそのノードへの参照を格納する。

これによりDOMが持つ様々なプロパティにアクセスすることができるようになる。

function App() {
  const myRef = useRef<HTMLInputElement>(null);
  
  console.log(myRef);
  
  return (
    <input type="text" id="input" ref={myRef}/>
  );
}

例えば上記のようにしたとき、myRef.current には input ノードの中身が格納されていることがわかる↓

従って、入力した値をフォームで送信する、というような場合に送信する値をref.current?.valueのようにして取得することで、Reactの外で管理された値であってもReact内部で扱い送信することができるようになる。

const ref = useRef<HTMLInputElement>(null);

<input type="text" id="input" ref={ref}/>
<button onClick={()=> console.log(ref.current?.value)}>確定</button>

まとめ

  • 非制御コンポーネントはこれまで通りフォーム要素の値をDOMが管理しているコンポーネントのこと
  • 制御コンポーネントはフォームの値の管理をDOMではなくReact内部で行っているコンポーネントのこと

Discussion