🐷

React入門(根本的なことからわかりやすく解説)

2023/02/17に公開

今回はReactについて解説していきます。

まずは基本的なことから解説して行くので、Reactを全然触ったことがないという人には、かなり参考になるかと思います。

Reactとは?

まずReactとは、JavaScriptのUIライブラリになります。

このReactを使うことで、コンポーネントベースでUIを記述することができます。

また、Web以外にもモバイルアプリや、デスクトップアプリもReactを使って作ることができます。

そして、Reactは現在JavaScriptのライブラリの中で最も人気が高いので、学んでおいて損はない技術になります。

コンポーネントとは?

先ほど、コンポーネントベースでUIを記述することができると言いましたが、コンポーネントとは何でしょうか?

コンポーネントとは、画面の構成要素を定義したものになります。

そして、Reactのようなコンポーネント思考の言語は、このコンポーネントを組み合わせて画面を作っていくことになります。

具体例で言うと、ボタンというコンポーネントとインプットエリアを組み合わせて、フォームを作成するというようなイメージになります。

こうすることで、以下のようなメリットを得ることができます。

  • 再利用性の向上
  • 可読性の向上
  • 保守性の向上

例えば、ボタンというコンポーネントを定義した場合は、フォーム以外にも様々なところで再利用ができます。

また、コンポーネント1つ1つはそこまでコードが長くならないので、可読性が向上します。

そして、コンポーネント1つ1つは、基本的に互いに影響を及ぼし合いません。

例えば、ボタンコンポーネントを変更しても、インプットエリアへは何の影響も与えません。

つまり、保守性も向上します。

このように、コンポーネント指向の言語は様々なメリットがあるので、最近ではよく使われているのです。

JSXとは

Reactでは、コンポーネントをJSXという記法で記述していきます。

実際のコードで書くと、次のようになります。

const App =() => {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

見た目はほぼHTMLなので、馴染みやすいかと思います。

実際にこのコードはHTMLに変換され、画面に描画されます。

そして別のファイルなどで以下のように書くことで、コンポーネントを呼び出すことができます。

<App />

このように、コンポーネントは最初を大文字にし、JSXをreturnするという決まりがあります。

つまり、JSXをreturnする関数=コンポーネントという理解でOKです。

JSXとは(発展編)

次に、少し発展したJSXの説明をしていきます。

まず、JSXとはcreateElementという画面を描画するための関数の実行を意味します。

そして、その結果はオブジェクトに変換されるので、オブジェクトのように扱うことができます。

例えば、次のように変数にJSXを入れたりすることもできます。

const obj = <div>test</div>

そして、JSXの中では{}で囲むことで、JavaScriptの式を書くことができます。

式と文の違いが分からないという人がいるかもですが、式は変数に代入できるもの、文はif文のように{}で囲む必要があるものという理解で十分です。

const test = "hoge"
  return (
    <div className="App">
      {test === "hoge" ? "hello" : "good bye"}
    </div>
  );
}

ちなみに、この{}の中ではbooleanの値、nullとundefinedは表示されません。

また、配列は勝手に展開されて表示されます。

const arr = ["hoge","foo","bar"]
  return (
    <div className="App">
      {arr /* hogefoobar */}
    </div>
  );

Propsとは

次に、Propsについて説明していきます。

Propsは簡単に言うと、コンポーネントに渡す引数になります。

例えば、次のように使います。

const App =(props) => {
  return (
    <div className="App">
      {props.hoge}
    </div>
  );
}

<App hoge={"hoge"} />

このように、プロパティ名={値}としてpropsを渡すことができます。

そして、このpropsは親から子にのみ渡すことができます。

なので、子から親にpropsを渡すことはできません。

また、propsは変更ができないようになっているので、変更しようとするとエラーになります。

そして、propsは以下のようにchildreを使うことで、コンポーネントを渡すこともできます。

const Comp = (props) => {
  return(
    <>
    <div>Hello</div>
    {props.children /* <div>こんにちは</div> */}
    </>
  )
}

const App =(props) => {
  return (
    <div className="App">
      <Comp>
        <div>こんにちは</div>
      </Comp>
    </div>
  );
}

React Fragment

先ほど、<> </>というJSXが出てきましたが、何だか分からない人も多かったかと思います。

まず、Reactにおいてトップレベルの階層は、nodeが2つあってはならないという決まりがあります。

なので、次のように書くとエラーになります。

const Comp = (props) => {
  return(
    <div>Hello</div>
    {props.children /* <div>こんにちは</div> */}
  )
}

トップレベルをdivで囲めば解決なのですが、そうすると無駄な要素が増えてしまいます。

そんな時に<> </>で囲むことで、無駄な要素を増やさずにエラーも防ぐことができます。

ちなみに、この記号は元々<React.Fragment> </React.Fragment>と書いていましたが、省略して書けるようになりました。

Stateとは

Stateとは、コンポーネントの状態を意味します。

要は、コンポーネント毎に値を保管しておくことができます。

そもそもstateの必要性がイマイチ分からないと思うので、そこから説明していきます。

例えば、以下のようなコンポーネントを定義したとします。

const Counter = () => {
  let num = 0
  const onClickButton = () => {
    num +=1
    console.log(num)
  }
  return (
    <>
      {num}
      <button onClick={onClickButton}>+1</button>
    </>
  )
}

ボタンを押すごとに、1ずつプラスされた値が表示される想定です。

ところが、ボタンを押しても表示される値は0のままになります。

理由は、値を変更してもコンポーネントが更新されていないからです。

つまり、画面の値を変更するには、コンポーネントの再実行が必要となります。

けれど、もしこのCounterを再実行できても、最初にnumを初期化してしまっているのでまた0が表示されてしまいます。

このように、画面の変更には、変更した値もどこかに保持する必要があります。

そして、その役割を果たすのがStateとなります。

Stateの使い方

では次に、具体的なStateの使い方を解説していきます。

まず、先ほどのCounterコンポーネントをStateを使って書き換えると、次のようになります。

const Counter = () => {
  const [num,setNum] = useState(0)
  const onClickButton = () => {
    setNum(num+1)
    console.log(num)
  }
  return (
    <>
      {num}
      <button onClick={onClickButton}>+1</button>
    </>
  )
}

最初に、useStateを使うことで、stateを定義することができます。

このuseStateに初期値にしたい値を設定することで、初期値とStateの更新用関数が配列の形で返ってきます。

そして、更新用関数に更新後の値を入れることで、値を保持しつつコンポーネントの再実行をすることができます。

stateを使う際の注意点

次に、stateを使う際の注意点をいくつか紹介していきます。

まず、stateの更新は非同期で行われるので、即座に実行はされません。

例えば、次のように記述した場合は、飽くまで0+1を2回実行しているに過ぎないので、表示される値は1になります。

const Counter = () => {
  const [num,setNum] = useState(0)
  const onClickButton = () => {
    setNum(num+1)
    setNum(num+1)
    console.log(num)
  }
  return (
    <>
      {num}
      <button onClick={onClickButton}>+1</button>
    </>
  )
}

もし、上記のような処理をしたいときは、setStateにコールバック関数を渡すことで解決します。

const onClickButton = () => {
    setNum(num+1)
    setNum((prevState) => (prevState + 1))
    console.log(num)
  }

コールバック関数の引数として渡ってくるprevStateは、更新後の値が入ってくるので想定通りに2が表示されるようになります。

あと、注意する点についてはStateがオブジェクトの場合は、新しいオブジェクトを作って更新用関数に渡す必要があります。

例えば、以下のようにした場合、再描画はされません。

const Counter = () => {
  const [num,setNum] = useState({value: 0})
  const onClickButton = () => {
    num.value += 1
    setNum(num)
    console.log(num)
  }
  return (
    <>
      {num.value}
      <button onClick={onClickButton}>+1</button>
    </>
  )
}

理由は、オブジェクトの値が変更されていた場合でも、メモリ上は一緒の参照先になるので、ReactがStateの変化が起きたと検知できないためです。

なので、Stateにオブジェクトを使う場合は、新たなオブジェクトを作成してそれを更新用関数に渡す必要があります。

最後に、Stateの管理方法についての注意点です。

Reactの内部では、Stateを変数名ではなく、単に順番を元に管理しています。

つまり、Stateが複数あった場合も、n番目の値に何が入っているかという感じで管理しています。

なので次のように、描画毎でStateの数が変わってくるとエラーが発生します。

const [num,setNum] = useState({value: 0})
  if(num === 1){
    const [num2,setNum2] = useState({value: 0})
  }

またstateは、コンポーネント コンポーネントの場所 + 順番によって管理されるので、同じ場所で同じコンポーネントの表示を切り替えてる場所があった場合、Stateは変更後のコンポーネントに受け継がれます。

const App =(props) => {
  const [toggle,setToggle] = useState(false)
  return (
    <div className="App">
      <button onClick={() => setToggle(!toggle)}>切り替え</button>
      {toggle ? <Counter name={"counterA"} /> : <Counter name={"counterB"} />}
    </div>
  );
}

これを防ぎたい場合は、コンポーネントの属性にkeyを違う値で設定すれば防げます。

{toggle ? <Counter key={"counterA"} name={"counterA"} /> : <Counter key={"counterB"} name={"counterB"} />}

keyはReactがコンポーネントを識別できるようにする属性になります。

ただ、このように全く同じコンポーネントを全く同じ場所で切り替えることはほぼないので、頭の片隅おいておけばOKです。

ちなみに、トグルなどでコンポーネントがアンマウント(非表示)された場合、Stateはリセットされます。

もしリセットを防ぎたい時は、親コンポーネントでStateを定義しておく必要があります。

コンポーネントについて補足

最後にコンポーネントについて、いくつか補足情報をお伝えしていきます。

まずコンポーネントは、次のことが起こると再実行されます。

  • 親コンポーネントの更新
  • Stateの更新
  • Propsの更新

これを知っていると、Reactの挙動が理解しやすくなるかと思います。

また、JSXでリストを書くときは、keyをつける必要があります。

Stateと同じように、コンポーネントも飽くまで場所でのみ管理されています。

なので、リストにキーをつけていないと、同じ場所に同じコンポーネントがあることになり、Reactが区別できなくなってしまうからです。

ちなみに、エラーにはなりませんが、keyにインデックスを使用してはいけません。

理由は、レンダリング毎に、keyの値が変わってしまう可能性があるからです。

例えば、3つのリストの最初に新たな要素が足された場合は、全ての要素のkeyの値が変わってしまいます。

なので結局Reactが要素の識別ができなくなってしまいます。

const List = () => {
  const arr = ["hoge","foo","bar"]
  return (
    <>
      {arr.map(item => 
        <p key={item}>item</p>
      )}
    </>
  )
}

また、createPortlalでDOMを書き換えて、modalなどを表示することができます。

実際の使い方は次のようになります。

const ModalPodal = () => {
  const target =  document.getElementById("hoge")
  console.log(target,"ModalPodal")
  return createPortal("hello",target);
}

const App =(props) => {
  const [toggle,setToggle] = useState(false)


  return (
    <div id="test">
      <div id="hoge"></div>
      <button onClick={() => setToggle(!toggle)}>切り替え</button>
      <List />
      {toggle && <ModalPodal />}
    </div>
  );
}

createPortalの第一引数に入れたい要素や文字列、第二引数に対象のDOMを設定することで、特定のDOMに要素を付与することができます。

ちなみに、イベントのバブリングはDOMツリーではなく、JSXによって作られるReactツリーを元に発生します。

まとめ

今回は、Reactについて基礎的なことを解説しました。

ぜひ、こちらの記事を参考にReactの理解を深めてみてください。

宣伝

0からエンジニアになるためのノウハウをブログで発信しています。
https://hinoshin-blog.com/

また、YouTubeでの動画解説も始めました。
YouTubeのvideoIDが不正ですhttps://www.youtube.com/channel/UCqaBUPxazAcXaGSNbky1y4g

インスタの発信も細々とやっています。
https://www.instagram.com/hinoshin_enginner/

興味がある方は、ぜひリンクをクリックして確認してみてください!

Discussion