HapInS Developers Blog

HapInSが提供するエンジニアリングの情報サイト

【初心者向け】強すぎる!react hooksのご紹介

はじめに

reactのhooksを1年使ってみてとても便利だと感じた(もうhooks無しでreact書きたくない!)のと同時に、 ネットに初心者向けの記事がそんなに多くなかったので執筆しようと思いました。 なるべく初心者の方にも分かりやすく説明できたらと思いますので、ご一読していただけたら幸いです。

hooksとは?

そもそもhooksって何?という話ですが、 hooksとはReactのバージョン16.8で追加された機能で、stateなどのReactの機能をクラスを書かずに使えるようになったものです。 今までだったらcreateClass()だとかReact.Componentを使っていたものが、とてもシンプルに書けるようになりました。

hooksの何がいい?

ではそんなhooksの強みをいくつかご紹介します。

個人的に大きいのは、そもそもどのコンポーネントを使うか都度考えなくてよいという点です。 以前はstateだったりライフサイクルを使うには、使用するコンポーネントそれに応じて変更する必要があったのですが、 今では基本的に、コンポーネントはすべてFunctionComponentを使って定義してよいということになりました。 FunctionComponentは記述自体もかなりスマートで読みやすく、初心者の方も入りやすいと思います。

補足ですが今までstateやライフサイクルを扱っていたのはClassComponentというものです。 ネットにある古い記事ではまだちらほら見かけることもあるので、こちらも興味があれば調べてみてください。

次にライフサイクルメソッドの複雑さから解放されることも大きいと思います。 今までは関連するロジックもライフサイクルごとに定義する必要があり、 私自身もたくさん思うように動かないという苦い経験をした覚えがあります。 処理も非常に追いやすくなったので、実装だけでなくデバッグもかなり楽になりましたね。

また既存のコードを完全に書き換えることなく、一部のコンポーネントで利用できるので、 既存のコードとhooksを含むコードを併用しながら段階的に採用できるのも強みと言えますね。

まとめるとクラスを書かずによりシンプルになって、直感的に分かりやすくなったというのと、 ライフサイクルにも縛られることが少ないので、初心者にも扱いやすいという感じですね。

代表的なhooksの紹介

それでは代表的なhooksをサンプルコードと一緒にいくつかご紹介します。

useState

useStateは、関数コンポーネントにstateを持たせられるものです。 ちなみにstateとはコンポーネントが内部で保持する「状態」のことです。 個人的にuseStateは最強で、一番利用頻度の高いものだと思います。 始めて触った時衝撃を受けたのを今でも覚えています。 初心者の方にとっても一番扱いやすいものだと思うので、ぜひマスターしてみてください。

基本的な使い方は const[stateの値, state更新関数] = useState(state初期値)です。 ここでuseStateのサンプルコードを見てみましょう。よくあるボタンを押すと表示されるクリック回数が増えるものです。

import React from 'react'

const UseState = () => {
  const [count, setCount] = React.useState(0)

  return (
    <p>
      <button onClick={() => setCount(count + 1)}>+</button>
   {count}回クリックされました
    </p>
  )
}

export default UseState

上記のコードにあたるstateはcountとなります。 stateの初期値はuseState(0)から0となるので、最初の{count}では0が表示されます。 その前にあるボタンがクリックされるとsetCountが呼び出されてstateであるcountの値が更新される仕組みです。 かなりシンプルで分かりやすいですね。素晴らしいです。

useEffect

useStateと同じくらいの頻度でよく使うものです。 このuseEffectは、レンダリング後に行う処理を指定できるものになります(すごい)。 さらっと書きましたがめちゃくちゃ強いです。こちらも簡単です。

ちなみによく記事では、useEffect=コンポーネントで副作用を制御できると書かれていますが、 この場合の副作用とは、DOMの変更やファイルへの書き込みなど、関数の外に影響を与える処理を指しています。

使い方はこんな感じ。

useEffect(() => {
  /* 第1引数には実行させたい副作用関数を記述*/
  console.log('副作用関数が実行されました') // 第2引数には副作用関数の実行タイミングを制御する依存データを記述
},[依存する変数の配列])

これだけ見るとちょっと難しく思えますが簡単に書くと...

useEffect(() => {
  関数A
},[変数Bの配列])

といった感じです。 要は変数Bが更新されたタイミングで関数Aが実行されます。 ちなみに第2引数を複数指定することも出来ますし、空にすることも出来ます。(空の場合は初回マウント時のみ実行されます) 先ほどのuseStateのコードに簡単なuseEffectを追加してみましょう。

import React from 'react'

const UseEffect = () => {
  const [count, setCount] = React.useState(0)

  React.useEffect(() => {
    console.log('countが更新されました')
  }, [count])

  return (
    <p>
      <button onClick={() => setCount(count + 1)}>+</button>
      {count}回クリックされました
    </p>
  )
}

export default UseEffect

上記のuseEffectではcountが更新されるのを監視しています。 ボタンがクリックされてcountが更新されたタイミングで、logが出力される仕組みです。

ちょっとだけ難しくなりますが、実際に関数を入れてみると...

import React from 'react'

const UseEffect = () => {
  const [count, setCount] = React.useState(0)

  React.useEffect(() => {
    const timerId = setTimeout(() => {
      setCount(count => count + 1)
    }, 1000)
    // クリーンアップ関数を返す
    return () => clearTimeout(timerId)
  }, [count])

  return (
    <p>
      time: {count}
    </p>
  )
}

export default UseEffect

こんな感じになります。 まず最初のレンダリングが完了したタイミングでuseEffectに指定した関数が呼びだされます。 そして1秒後にcountの値が更新されるとコンポーネントの再レンダリングが発生し、再びuseEffectの関数が呼ばれます。 この繰り返しにより、1秒ごとに数値が1ずつ増えていくコンポーネントとなるわけです。

上記にあるクリーンアップ関数を宣言した場合は、次回のコールバックが呼ばれる前にクリーンアップ関数が呼ばれる仕組みになっています。 ちなみにクリーンアップ関数はアンマウント時にも実行されます。

書き方によっては無限ループになってしまうこともあるのと、多用し過ぎると逆にロジックが追いづらくなったり、副作用の連鎖で発火の順番が分からなくなったりするので注意が必要です。 しかしかなり簡潔に色々な機能が実現出来るのでとても便利なhooksですね。

useMemo

useMemoは関数の結果を保持するためのhooksで、何回やっても結果が同じ場合の値などをメモ化し、そこから値を再取得できるものです。 メモ化とは何かというと、何度も同じ結果を返す処理に対して初回のみ処理結果を記録し、次回以降呼ばれた際には初回に得られた処理結果を取得するという仕組みです。 実際に私たちが繰り返す事柄を忘れないようにメモするのと同じですね。 これにより、都度計算する必要がないため処理速度の向上が期待できるというわけです。 書き方もuseEffectに結構似ているので、実際にサンプルコードを見てみましょう。

import React from 'react'

const UseMemo = ({ n }: { n: number }) => {
  const sum = React.useMemo(() => {
    let result = 0
    let i = 1
    while (i <= n) {
      result += i
      i++
    }
    return result
  }, [n])

  return (
    <p>
      {sum}
    </p>
  )
}

export default UseMemo

上記はUseMemoというコンポーネントを呼び出す際に渡したnという数の分だけ、 1ずつ加算した合計の結果を表示するものです。 このように、ループなど繰り返す処理をべた書きしてしまうとレンダリングされる度に計算が実行されてしまうので、 非常に処理が重くなります。 上記の場合nの値が更新されない限り再計算する必要はないですね。 そんな時はuseMemoを使って、計算した結果を記録しておいてその結果を呼び出す実装にするとスマートです。

またuseMemoはコンポーネント自体をメモ化することも可能です。 そうすることによってレンダリングコストを下げることも出来るので、質の高いコードを書こうと思った時には大活躍ですね。

useCallback

最後にuseCallbackをご紹介します。 先ほどご紹介したuseMemoでは関数の計算結果を記録しましたが、 useCallbackでは関数そのものを記録します。 書き方も似ていて、第2引数(依存する変数の配列)が更新されたタイミングで関数が動きます。

少し難しい話になりますが、 Reactの特性として、レンダリングする度にコンポーネント内の関数は別の関数に変わるというものがあります。 つまりuseEffectの第2引数に関数を用いると、レンダリング毎にuseEffectに渡した関数が呼ばれてしまうことになります。

以下のサンプルコードを見てみましょう。 inputに入力した文字列がlogに出力されるというものです。

import React from 'react'

const UseCallback = () => {
  const [count, setCount] = React.useState(0)
  const [log, setLog] = React.useState("")

  const outputLog = (value: string) => {
    console.log(value, 'と入力されました')
  }

  React.useEffect(() => {
    outputLog(log)
  }, [outputLog])

  return (
    <p>
      <input
        type="text"
        value={log}
        onChange={e => setLog(e.target.value)}
      />
      <button onClick={() => setCount(count + 1)}>setCount</button>
    </p>
  )
}

export default UseCallback

見てみると、countを更新するボタン意味あるの?という感じですが、 実際動かしてみると、inputに入力した文字列が変更された時だけでなく、 そのボタンを押した時もログが出力されてしまいます。 レンダリングする度にコンポーネント内の関数は別の関数に変わるので、 inputが更新されて再レンダリングされた時のoutputLogはレンダリング前のものと違うと認識されるわけです。 つまり内部的にはoutputLogが更新されたものとして、useEffectが動いてしまうんですね。 そんな時にuseCallbackを使います。

const UseCallback = () => {
  const [count, setCount] = React.useState(0)
  const [log, setLog] = React.useState("")

  const outputLog = React.useCallback(
    (value: string) => {
      console.log(value, 'と入力されました')
    },
    [log]
  )

  React.useEffect(() => {
    outputLog(log)
  }, [outputLog])

  return (
    <p>
      <input
        type="text"
        value={log}
        onChange={e => setLog(e.target.value)}
      />
      <button onClick={() => setCount(count + 1)}>setCount</button>
    </p>
  )
}

export default UseCallback

こうすることによって、logが更新されない限りoutputLogが呼び出されることはなくなります。 関数をメモ化とだけ聞くとあまり必要性を感じないかもしれませんが、実際にこうして動かしてみると 非常に面白くて、useCallbackの強みを感じますね。 useEffectは便利なものですが、今回のように関数を第2引数に渡す際は注意が必要です。

まとめ

今回はreactのhooksの中でも、特に使われている印象のある4つをご紹介しました。 この他にもhooksはいくつかあるので是非そちらも興味がありましたら調べてみて下さい。

参考

React hooks公式ドキュメント: https://ja.legacy.reactjs.org/docs/hooks-intro.html

React hooksを基礎から理解する: https://qiita.com/seira/items/f063e262b1d57d7e78b4

「Function Component」 と 「Class Component」の違い: https://qiita.com/ita-k/items/c8e05f084fe88a086100