LoginSignup
0
0

More than 1 year has passed since last update.

これまで

第一回: useState, useRef
第二回: useContext
第三回: useEffect, useLayoutEffect, useMemo, useCallback

今回は、useReducerです。
ステート管理のキモとなるフックですね。。

前提

  • React 16.8以降。なのでHook APIの使用が前提です
  • Reactの基本的な説明はしません
  • TypeScriptで書いてます

申し訳程度の説明

  • 公式にはuseStateの代替品とあります。実際、useStateuseReducerで実装されています。
  • useReducer
    • 構文
      • [state, dispatch] = useReducer(reducer, initialState)
    • 引数
      • reducer: 現在のステート値を受け取って、新しいステート値を返す関数
      • initialState: ステートの初期値
    • 戻り値
      • state: ステート値
      • dispatch: reducerを実行するための関数

使ってみる

簡単なチェックボックスを作り、useStateと比較してみます。

useState

import React, { useState } from "react";

export default function Checkbox() {
  const [checked, setChecked] = useState(false);
  const toggle = () => setChecked(checked => !checked)

  return (
    <>
      <input type="checkbox" checked={checked} onChange={toggle} />
      {checked ? "checked" : "not checked"}
    </>
  );
}

useReducer

import React, { useReducer } from "react";

export default function Checkbox() {
  const [checked, toggle] = useReducer((checked) => !checked, false);

  return (
    <>
      <input type="checkbox" checked={checked} onChange={toggle} />
      {checked ? "checked" : "not checked"}
    </>
  );
}

なんかステート更新のロジックが抽象化されただけですね。useStateで十分な気が。。

ちょっと複雑なステートを考えてみる

簡単なユーザ情報を表示させるコンポーネントを考えます。

import React, { useReducer } from "react";

/** 管理したいステート */
type State = {
  id: string;
  firstName: string;
  lastName: string;
  city: string;
  state: string;
  email: string;
  admin: boolean;
};
type Actions = Partial<State>;

interface Reducer {
  (user: State, newDetails: Actions): State;
}

/** ステートの初期値 */
const firstUser: State = {
  id: "0391-3233-3201",
  firstName: "Taro",
  lastName: "Yamada",
  city: "Tokyo",
  state: "Shinjuku",
  email: "t-yamada@react.com",
  admin: false
};

/** ステートを受け取りステートを返す関数 */
const reducer: Reducer = (user, newDetails) => ({ ...user, ...newDetails });

function User() {
  const [user, setUser] = useReducer(reducer, firstUser);
  const handler = () => setUser({ admin: true });

    // もしもuseStateで記述していたら、、、
  // const handler = () => setUser({ ...user, admin: true });

  return (
    <div>
      <h1>
        {user.firstName} {user.lastName} - {user.admin ? "Admin" : "User"}
      </h1>
      <p>Email: {user.email}</p>
      <p>
        Location: {user.city}, {user.state}
      </p>
      <button onClick={handler}>あなたも今日から管理人!</button>
    </div>
  );
}

export default function App() {
  return <User />;
}

ステート更新にいちいちスプレッド構文を使わなくていいのでスッキリしますね。
が、まだuseStateで事足りそうですよね。。

もっと複雑なステートを考えてみる

前述の例に加え、複数のオブジェクトをステートを管理する場合を考えます。
useContextuseReducerを組み合わせてstore的にステートを一元管理します。

./src/AppProvider.tsx

import React, { createContext, useContext, useReducer } from "react";

type IUser = {
  id: string;
  firstName: string;
  lastName: string;
  city: string;
  state: string;
  email: string;
  admin: boolean;
};

/** 管理したいステート */
type State = {
  user: IUser;
  login: false;
  // other
};
type Actions = Partial<State>;

interface AppContextType {
  state: State;
  dispatch: React.Dispatch<Actions>;
}

/** ステートの更新関数 */
const reducer = (state: State, actions: Actions) => {
  const newState = { ...state };
  if(actions.user) newState.user = actions.user
  if(actions.login) newState.login = actions.login

  return newState;
};

/** ステートの初期値 */
const initState = {} as State;

/** コンテキストをエクスポート */
const AppContext = createContext({} as AppContextType);
export const useAppContext = () => useContext(AppContext);

/** コンテキスト */
const AppContextProvider: React.FC = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initState);
  return (
    <AppContext.Provider value={{ state, dispatch }}>
      {children}
    </AppContext.Provider>
  );
};
export default AppContextProvider;

./src/index.tsx

import React from "react";
import { render } from "react-dom";
import AppProvider from "./AppProvider"
import App from "./App";

render(
  <AppProvider>
    <App />
  </AppProvider>,
  document.getElementById("root")
);

./src/App.tsx

import React, { useEffect } from "react";
import { useAppContext } from "./AppProvider";

function User() {
  const { state, dispatch } = useAppContext();
  const { user } = state;
  const handler = () =>
    dispatch({
      user: { ...user, admin: true }
    });

  useEffect(() => dispatch({ login: true }));

  return (
    <div>
      <h1>
        {user.firstName} {user.lastName} - {user.admin ? "Admin" : "User"}
      </h1>
      <p>Email: {user.email}</p>
      <p>
        Location: {user.city}, {user.state}
      </p>
      <button onClick={handler}>あなたも今日から管理人!</button>
    </div>
  );
}

export default function App() {
  return <User />;
}

ステートのスケールを可能にしつつ、一元管理する方法としてはイイ線じゃないでしょうか。。

参考文献

Reactハンズオンラーニング 第2版 ――Webアプリケーション開発のベストプラクティス

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0