masalibの日記

システム開発、運用と猫の写真ブログです

React hooksの入門(useReducer)

React hooksのuseReducerについて勉強した。

useReducerとは

複雑な状態遷移を処理するのに最適。逆にいうと数が少ない場合なuseStateかな。 この機能がでる前まではReduxを使用していました。

const [state, dispatch] = useReducer(reducer,'初期値')

reducerはstateを更新するための関数で、dispatchは、reducerを実行するための呼び出し関数です。 (変数を宣言するときに、stateの更新方法をあらかじめ設定しておくことが出来る。)

useStateとuseReducerの比較

比較してくれていたページがあったの引用させてもらいました

https://qiita.com/seira/items/2fbad56e84bda885c84c より引用

  useState useReducer
扱えるstateのtype 数値、文字列、論理値 オブジェクト、配列
関連するstateの取り扱い 複数を同時に取り扱うことが出来る
ローカルorグローバル ローカル グローバル useContext()と一緒に取り扱う

小さなコンポーネントならstateかな・・・大きくなる事を考慮するとuseReducerだと思う

useReducerのサンプル1

単純なカウンターになります

useReducerのサンプル1の解説その1

アクションのタイプを指定しています。exportしているのは他のコンポーネントで使う事を考慮しているからです。普通はこの部分だけ別ファイルにするみたい

export const ACTIONS = {
  INCREMENT: "increment",
  DECREMENT: "decrement",
  RESET: "reset"
};

useReducerのサンプル1の解説その2

const [state, dispatch] = useReducer(reducer, { count: 0 });

useReducerで状態管理できるようにしています

  function increment() {
    console.log("increment");
    dispatch({ type: ACTIONS.INCREMENT });
  }

プラスのボタンを押されて時にincrementの関数が実行されてその中にある dispatchという関数が起動されています

function reducer(state, action) {
  console.log("reducer function");
  console.log(`action:${action.type}`);
  switch (action.type) {
    case ACTIONS.INCREMENT:
      return { count: state.count + 1 };
    case ACTIONS.DECREMENT:
      return { count: state.count - 1 };
    case ACTIONS.RESET:
      return { count: 0 };
    default:
      return state;
  }
}

アクションのタイプによって切り替え処理をおこなっています

実際にプラスのボタンを押した時にだされるconsole.logは

f:id:masalib:20201115212115p:plain

になっています

単一のコンポーネントの場合はuseReducerは使わなくても問題ないかと思います

useReducerのサンプル2

Todoリストになります

useReducerのサンプル2の解説その1

サンプル1と同じようにactionのタイプを定義します。

export const ACTIONS = {
  ADD_TODO: "add-todo",
  TOGGLE_TODO: "toggle-todo",
  DELETE_TODO: "delete-todo"
};

今回は3つタイプを設定します

useReducerのサンプル2の解説その2

todoを追加するためにフォームを用意しています

<form onSubmit={handleSubmit}>
    todo:
  <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
    />
</form>

nameはuseStateで作った変数になります

const [name, setName] = useState("");

定番の入力された内容がnameに保存されるものです。
enterキーが入力されるとonSubmit={handleSubmit}が起動します

  function handleSubmit(e) {
    console.log("handleSubmit");
    e.preventDefault();
    tododispatch({
      type: ACTIONS.ADD_TODO,
      payload: { name: name }
    });
    setName("");
  }

起動されるといつもの再描画をおさえる
e.preventDefault();
が実行されます。

その後、tododispatchが起動します。 サンプル1と違うのはpaylodという部分が増えました。 タイプ以外の部分もdispatchにわたして処理ができます。

function todoreducer(todostate, action) {
  console.log("todoreducer");
  console.log(`action:${action.type}`);
  switch (action.type) {
    case ACTIONS.ADD_TODO:
      return [...todostate, newTodo(action.payload.name)];
   ・
   ・
   ・
   ・
function newTodo(name) {
  return { id: Date.now(), name: name, complete: false };
}

tododispatchの関数が起動されるといつもどおりタイプで処理をわけます。 Todoリストの追加になるので、

[...todostate, newTodo(action.payload.name)];

今までのTodoリストに新しいTodoリストを追加しています。

useReducerのサンプル2の解説その3

Todoリストはいつもどおり配列(todostate)をグルグル回して表示しています

<div>todo list:</div>
<div>
    {todostate.map((todo) => {
        return (
         <Todo key={todo.id} todo={todo} tododispatch={tododispatch} />
        );
    })}
</div>

単純に表示させるではなく子供のコンポーネントに値を渡しています。

<Todo key={todo.id} todo={todo} tododispatch={tododispatch} />

keyの部分はループで回る時のお決まりです。
あとは子供のコンポーネントにpropsで渡しています。 渡す部分にdispatchも渡しているのが味噌です。

子供コンポーネントでTodoの内容とtoggleとdeleteのボタンを表示しています

<div>
  <span style={{ color: todo.complete ? "#aaa" : "#000" }}>
    {todo.name}
   </span>
   <button onClick={toggle}>toggle</button>
   <button onClick={deletedata}>delete</button>
</div>

toggleのボタンを押すと関数が起動される

  function toggle() {
    console.log(todo.id);
    tododispatch({
      type: ACTIONS.TOGGLE_TODO,
      payload: { id: todo.id }
    });
  }

その中に親コンポーネントから渡されたdispatchを起動する事で 親のリストを更新しています

function todoreducer(todostate, action) {
  switch (action.type) {
    case ACTIONS.TOGGLE_TODO:
      return todostate.map((todo) => {
        if (todo.id === action.payload.id) {
          return { ...todo, complete: !todo.complete };
        }
        return todo;
      });
    default:
      return todostate;
  }
}

感想

React hooksが好きな所が理解できてよかった。Reduxが苦手なのでこの機能ができて本当に感謝。

人様のブログの記事はなんとなくの理解はできるけど、最終的な理解はcodesandboxのサイトで打ち込んだ時かな・・・

参考URL

https://blog.webdevsimplified.com/2020-06/use-reducer/
https://www.to-r.net/media/react-tutorial-hooks-usereducer/
https://qiita.com/seira/items/2fbad56e84bda885c84c