クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【React】フック(React Hooks)事始め:useState、useEffect、useContext

Reactの状態管理シリーズの続きです。以前はステート、コンテクスト、Reduxなどいろいろやって来ましたが、フック(React Hooks)はまだやっていませんでした。コンテクストはクラスコンポーネントでしか使えませんでしたがフックは関数コンポーネントでも使えます。ただ、完全な代替手段ではなくコンポーネント間のデータの受け渡しはコンテクストと併用する必要がありそうです。基本のフックと言われるuseState、useEffect、useContextを触ってみます。それではいきましょう!

【目次】

※参考:前回記事
【React】コンテクスト(Context)でネストされたコンポーネントに値を渡す - クモのようにコツコツと

※参考:Reactを習得するためにやったことまとめ
qiita.com

前回までのおさらい

React状態管理シリーズ

ステート

※参考:【React】Reactプロジェクトでステートを事始め(setState()、タイマー処理、イベント処理) - クモのようにコツコツと
※参考:【React】ステートで制御構造(条件分岐、ループ) - クモのようにコツコツと

コンテクスト

※参考:【React】コンテクスト(Context)でネストされたコンポーネントに値を渡す - クモのようにコツコツと

Redux

※参考:【React】Reduxを事始める(インストール〜ストア、レデューサー、プロバイダー設定) - クモのようにコツコツと


コンテクストはクラスコンポーネントでしか使えなかったがフック(React Hooks)は関数コンポーネントでも使えるようだ。さっそくトライ!

環境設定

まずはcreate react appをインストール。アプリ名は「react-hook-test」

$ npx create-react-app react-hook-test

フォルダに移動

$ cd react-hook-test

アプリを起動

$ npm start

よし、クルクルしとる。 f:id:idr_zz:20210216043921j:plain

いったん「Controle + C」で閉じる。


gitの初期設定(「.gitフォルダ」が作られる)

$ git init

GItHubリポジトリと紐付け

$ git remote add origin https://github.com/ryo-i/react-hook-test.git

フック(React Hooks)とは何か

で、このフックとは何なのか。

「フック」自体はプログラミングで一般的に使われる言葉

プログラムの中に独自の処理を割りこませるために用意されている仕組み。

※参考:https://wa3.i-3-i.info/word12296.html

そういえばWordPressでもフック機能があった。

※参考:フックって?WordPressのカスタマイズ方法を説明します! | 株式会社LIG


Reactの公式ドキュメントによれば

フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。

※参考:フックの導入 – React

フックとは、関数コンポーネントに state やライフサイクルといった React の機能を “接続する (hook into)” ための関数です。フックは React をクラスなしに使うための機能ですので、クラス内では機能しません。

※参考:フック早わかり – React

クラスなしに関数コンポーネントで使える、というのがポイントのようだ。(このページの「早わかり」って翻訳、いいなw)


フックを使うにあたり、ドキュメントのフックページをかなり読み込んでみたのだが、いまいち概念が理解できなかった。

「基本のフック」といわれる3つの機能をやってみる。

  • useState:ステートを設定
  • useEffect:副作用の設定
  • useContext:コンテクストと連携

※参考:フック API リファレンス – React

useStateでステートを設定

まずは一つ目の「useState」、ステートの設定ができる。

※参考:ステートフックの利用法 – React

ステートフルな値と、それを更新するための関数を返します。

※参考:フック API リファレンス – React


「Count.jsx」というファイルを作成。

ReactuseStateをインポート

import React, { useState } from 'react';

Countコンポーネントを作成

function Count() {
  
    const data = {
        name: '羊',
        count: 0
    }

    const [count, setCount] = useState(data.count);
    const name = data.name;

    return (
        <div className="reactHookTest">
            <h2>React Hooks事始め</h2>
            <p>{name}{count} 匹</p>
            <button onClick={() => setCount(count + 1)}>
            数える
            </button>
        </div>
    );
}
  • 変数dataでステートの初期値を設定、連想配列nameキーの値は「羊」、countキーの値は「0」
  • 変数countsetCountuseState()でステート設定(引数はdata.countキー)
  • 変数namedata.nameを取得
  • JSX設定、pタグでnamecountを呼び出す
    buttonタグをクリックすると(onClicksetCountcountを1増やす

[count, setCount]の箇所はJSの分割代入。変数名側も配列にして一気に設定できる。

※参考:分割代入 - JavaScript | MDN

最後にCountコンポーネントをエクスポート

export default Count;

AppコンポーネントにCountコンポーネントを配置する

「App.js」の冒頭でCountコンポーネントをインポート

import Count from './Count';

Appコンポーネント内のaタグの下にCountコンポーネントを配置

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <!-- 中略 -->
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
          <Count />
      </header>
    </div>
  );
}

ブラウザで挙動を確認。アプリを起動する

$ npm start

お、Counterコンポーネントが表示されている! f:id:idr_zz:20210216052056j:plain 「羊」はnameで「0匹」の0はcount部分

「数える」ボタンを押すと羊の数が増える! f:id:idr_zz:20210216052230j:plain ステートで状態が変わっているということだ。

ページをリロードするとステートの初期値「0匹」に戻る f:id:idr_zz:20210216052056j:plain

useEffectで副作用の設定

次はuseEffect、副作用の設定ができる。

そもそも副作用とは何か

プログラミングにおいて、式の評価による作用には、主たる作用とそれ以外の副作用(side effect)とがある[1][2]。 式は、評価値を得ること(※関数では「引数を受け取り値を返す」と表現する)が主たる作用とされ、それ以外のコンピュータの論理的状態(ローカル環境以外の状態変数の値)を変化させる作用を副作用という

※参考:副作用 (プログラム) - Wikipedia

うむ、よくわかんない、、

「副作用」とは、関数またはメソッドを実行した時に、オブジェクトの属性が変化することを指しています。

※参考:プログラミングで副作用と状態ってなに? | 民主主義に乾杯

なるほど!こちらの方がわかりやすい。

そしてReactでの副作用とは

データの取得、購読 (subscription) の設定、あるいは React コンポーネント内の DOM の手動での変更、といったものはすべて副作用の例です。

※参考:副作用フックの利用法 – React


useEffectさっそく使ってみる。先ほどのCounterコンポーネントを改造する。

冒頭でuseStateに加えuseEffectもインポート

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

Countコンポーネント

function Count() {
  
    const data = {
        name: '羊',
        count: 0
    }

    const [count, setCount] = useState(data.count);
    const name = data.name;

    useEffect(() => {
        document.title = `React Hooks事始め - ${name}が ${count} 匹`;
    });

    return (
        <div className="reactHookTest">
            <h2>React Hooks事始め</h2>
            <p>{name}{count} 匹</p>
            <button onClick={() => setCount(count + 1)}>
            数える
            </button>
        </div>
    );
}

export default Count;
  • useEffect()を追加、ページのtitleタグにもnamecountを呼び出す。

${name}${count}などはstyled-componentsの時にも使ったJSのプレースホルダの記法。

※参考:【React】styled-componentsのcreateGlobalStyleでbodyタグにCSS設定(Reactとメタ言語の比較-4) - クモのようにコツコツと

このように副作用の処理を追加することができる。


ブラウザの動作を確認

ページをリロードするとtitleに「羊が0匹」が追加された! f:id:idr_zz:20210216055303j:plain

カウントを増やすとtitleも動的に変わる! f:id:idr_zz:20210216055350j:plain

ページをリロードするとtitleもステート初期値の「0匹」に戻る f:id:idr_zz:20210216055303j:plain

useContextでコンテクストと連携

最後はuseContext。コンテクストと連携できる。

これまでステートをコンポーネントの中に設定していたが、コンテクストはコンポーネントの外側からステートの値を受け取ることができた。

※参考:【React】コンテクスト(Context)でネストされたコンポーネントに値を渡す - クモのようにコツコツと

しかしフックは関数の中で使うルールのようだ

ネストした関数の中でフックを呼び出さないでください。

※参考:フック早わかり – React

コンポーネントの状態を参照している場合、フックはコンポーネント間で共有するのに役立ちません。コンポーネントの状態は、コンポーネントに対してローカルです。

※参考:javascript — ReactのuseState()フックを使用してコンポーネント間で状態を共有することは可能ですか?

コンポーネントを超えるステートはコンテクストで設定してuseContextで受け取る

コンテクストオブジェクト(React.createContext からの戻り値)を受け取り、そのコンテクストの現在値を返します。

※参考:フック API リファレンス – React


ドキュメントの事例では同一ファイル内で処理が完結しているが、今回、別ファイル間でデータをやりとりする形にしてみた。

まず親コンポーネント「App.js」でコンテクストを設定する。コンテクスト設定は下記と同じ手順。

※参考:【React】コンテクスト(Context)でネストされたコンポーネントに値を渡す - クモのようにコツコツと

ReactのcreateContext機能もインポートする

import React, { createContext } from 'react';

変数Contextでコンテクスト作成。複数ファイルで使えるようエクスポートする。

export const Context = createContext();

変数dataにステート初期データを設定

const data = {
    name: '羊',
    count: 0
}

AppコンポーネントでProviderを使って子コンポーネントCountにコンテキストを渡す(初期値はdata)。

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <!-- 中略 -->
        <Context.Provider value={data} >
          <Count />
        </Context.Provider>
      </header>
    </div>
  );
}

Providerの詳細はこちらを参照

※参考:【React】コンテクスト(Context)でネストされたコンポーネントに値を渡す - クモのようにコツコツと


Countコンポーネントを修正する

import React, { useState, useEffect, useContext } from 'react';
import { Context } from './App.js';
  • useState, useEffectに加え、useContextもインポート
  • App.jsからContextコンテキストをインポート
function Count() {
  
    const context = useContext(Context);
    const name = context.name;

    const [count, setCount] = useState(context.count);

    useEffect(() => {
        document.title = `React Hooks事始め - ${name}が ${count} 匹`;
    });

    return (
        <div className="reactHookTest">
            <h2>React Hooks事始め</h2>
            <p>{name}{count} 匹</p>
            <button onClick={() => setCount(count + 1)}>
            数える
            </button>
        </div>
    );
}

export default Count;
  • これまであった変数dataは不要なので削除する
  • 変数contextuseContext()を実行(引数はContext
  • 変数namecontext.nameを取得
  • countsetCountuseState()の引数をcontext.countに変更

これまでコンポーネントの中で設定していたステートをuseContext()によって親コンポーネントAppからコンテキストを受け取る形に変更した。


ブラウザ挙動

おお、親コンポーネントのコンテキストから受け取った初期値「羊」、「0匹」を表示している! f:id:idr_zz:20210216052056j:plain

「数える」ボタンを押すと羊の数が増える! f:id:idr_zz:20210216052230j:plain

ページをリロードするとステートの初期値「0匹」に戻る f:id:idr_zz:20210216052056j:plain


コード(GitHub)

※参考:GitHub - ryo-i/react-hook-test at 0d008a30779c34e4f2414e19defc2e938d26398c

プレビュー(GitHub Pages)

※参考:React App

最後に

ということで、基本のフック3種類(useState、useEffect、useContext)を体験できましたー。

最後のuseContextは作っている過程でコンテキストがうまく認識されずTypeErrorなどに苦しみました。いろいろな記事を参考にさせていただき無事成功しましたー。

※参考:React hooksを基礎から理解する (useContext編) - Qiita
※参考:React Context / Hooks 入門
※参考:こんなに簡単なの?React useContextって | アールエフェクト
※参考:React Context で複数のコンポーネント間でデータを共有する|まくろぐ
※参考:useContextのしくみ - Qiita
※参考:5分でわかる useContext の使い方【TypeScriptまで】 - Qiita
※参考:【React hooks】噛み砕いて解説してみた~useContext編~ - Qiita
※参考:React hooksを基礎から理解する (useContext編) - Qiita

フックを使うと関数コンポーネントで状態管理できてシンプルな記述になりますね。

最近のReactはクラスコンポーネントより関数コンポーネントが主流になってきているようです。

※参考:React今昔物語 - ICS MEDIA

フックには他にもいろいろな機能があるようなので徐々に理解を進めていきたいと思います。

※参考:フック API リファレンス – React

さて次は、ReactとFirebaseの連携をやってみる予定です。それではまた!


※参考:Reactを習得するためにやったことまとめ
qiita.com