React Hooks

先日、Next.jsを勉強していきたいと書きましたがそれ以前にReactがちゃんと使いこなせないとそもそもNext.jsの恩恵も受けられないので復習していきます。

Reactそのものをしっかり扱ったことは今までなく、昔React NativeでiOSアプリを作ろうとしていた時に勉強してた以来です。

Reactの中でも状態管理等々に深く関わるHooksを中心に復習したいと思います。

Hooksとは

HooksはReact 16.8で追加された機能です。

Reactのコンポーネントにはクラスコンポーネントと関数コンポーネントの2種類があります。

前者はこんな感じ。

import React from "react";

class Sample extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
  }

  increment = () => {
    this.setState({
      count: this.state.count + 1
    })
  }

  render() {
    return (
      <div>
        <span>Count: {this.state.count}</span>
        <button onClick={this.increment}>Click</button>
      </div>
    )
  }
}

constructorの中でコンポーネントstateに初期値を設定しています。変更する場合はsetStateを使います。

一方関数コンポーネントはこんな感じ。

export const Sample = (props) => {
  return (
    <div>
      <span>Sample</span>
    </div>
  )
}

しかしClassと異なりconstructor内でstateの設定ができません。

なので関数コンポーネントにできることは限られていました。

そこで関数コンポーネントでもstate等を使えるようにと登場したのがHooksです。

先程のクラスコンポーネントの例を関数コンポーネントで書くとこんな感じ。

import React, { useState } from "react";

export const Sample = (props) => {
  const [count, setCount] = useState(0)

  const increment = () => {
    setCount(count + 1)
  }

  return (
    <div>
      <span>Count: {count}</span>
      <button onClick={increment}>Click</button>
    </div>
  )
}

useStateというHookを使ってcountとそれを更新するためのsetCountを定義しています。初期値はuseStateの引数に入れた0です。

関数コンポーネントなので最終的にreturnすればOK、ということで直感的でシンプルな感じがします。

こうしてHooksが導入されたことにより、現在では関数コンポーネントが主流となっています。

主なHooks

ここからは主に使われるHooksを見ていきます。

useState

先程挙げたものです。一番基本的なHookでコンポーネントに状態(state)を持たせるために使います。

useEffect

コンポーネントのライフサイクルに伴う副作用を管理するためのHookです。

コンポーネントがマウントされた直後に呼び出されるcomponentDidMountコンポーネントが更新された直後に呼び出されるcomponentDidUpdate等に該当します。

これを使うとレンダリング後に副作用として関数を実行することができます。

例を挙げるとこんな感じ。

import React, { useState, useEffect } from "react";

export const Sample = (props) => {
  const [count, setCount] = useState(0)

  const increment = () => {
    setCount(count + 1)
  }

  useEffect(() => {
    document.title = `Clicked ${count} times.`
  }, [count])

  return (
    <div>
      <span>Count: {count}</span>
      <button onClick={increment}>Click</button>
    </div>
  )
}

先程のuseStateの例に追加しました。こうするとボタンをクリックする度にタイトルが変わります。

useEffectの構文は以下の通りです。

useEffect(() => {
  /* 第1引数: 副作用として実行したい関数 */
}, [ /* 第2引数: 副作用のトリガーとなる変数の配列 */ ])

第1引数にはレンダリング後に実行したい関数を、第2引数にはその副作用のトリガーとなる変数の配列を取ります。

第2引数に渡した配列の中のどれかの値が更新される度に第1引数の関数が実行されます。

注意点としてuseEffectは使い方を誤ると無限ループに陥ります。

import React, { useState, useEffect } from "react";

export const Sample = (props) => {
  const [count, setCount] = useState(0)

  const increment = () => {
    setCount(count + 1)
  }

  useEffect(() => {
    increment()
  }, [count])

  return (
    <div>
      <span>Count: {count}</span>
      <button onClick={increment}>Click</button>
    </div>
  )
}

かなり極端な例ですが、↑のようにすると

  • 初期レンダリング後にuseEffect内でincrementが呼ばれる
  • countが更新されたので再レンダリング時に再度useEffectが実行
  • useEffect内でincrementが呼ばれる
  • countが更新されたので(以下略)

となります。初期レンダリング時以外に実行したくない場合は第2引数に空配列を渡せばOKです。

useContext

コンポーネントから子コンポーネントに値を渡したい場合は通常propsを使います。

しかし、場合によっては何階層にも渡ってpropsをバケツリレーする必要が出てきます。かなり手間ですし、記述量も増えます。

useContextを使えば直接の親子関係に無いコンポーネントでも共通して使えるグローバルな値にアクセスすることができます。

import React, { useContext } from "react";

const titleContext = React.createContext()

export const Parent = () => {
  return (
    <titleContext.Provider value={'hoge'}>
      <Child />
    </titleContext.Provider>
  )
}

export const Child = () => {
  return (
    <GrandChild />
  )
}

export const GrandChild = () => {
  return (
    <GreatGrandChild />
  )
}

export const GreatGrandChild = () => {
  const title = useContext(titleContext)
  return (
    <span>title: { title }</span>
  )
}

titleContextの現在の値をどこからでも取得できます。

注意点としてはContextの値が変わった場合、useContextを使用してそのContextの値を使用しているコンポーネントは全て再レンダリングされてしまいます。

まとめ

基本的なReact Hooksについて簡単にまとめてみました。

より詳細に知りたい方はこちらの公式ドキュメントを見てください。

次回は他のHooksについても書いていきたいと思います。