0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ReactのuseState、useReducerの違いを理解する

Last updated at Posted at 2024-02-18

はじめに

Reactの状態管理のためのフックとして、useStateuseReducerの2つがあります。
2つの違いがいまいちピンときておらず、なんとなくuseStateを使えばいいのかな、と思っていました。
なんとなくの理解から確実に自分の知識とするために、それぞれの詳細を確認し、その違いを理解していきます。

useState

基本の形

const [state, setState] = useState(init);

引数

init:
stateに設定したい初期値。どんな型の値でもよい。関数も可。

戻り値

戻り値は配列で渡されるため、受け取る際は分割代入で受け取るのが一般的。
また、その際の変数名は何でもよいが、状態xxxxに対して、関数はsetXxxxとするのが一般的らしい。

state:
現在の状態。一番初めはinitで設定した値が入る。
initに関数を設定した場合は、その関数の戻り値が入る。

setState:
新しい状態を設定するための関数。
通常、引数には新しい状態を直接指定する。
前の状態に基づいて新しい状態を設定する場合には、関数を渡すこともできる。
関数を渡す場合は以下

setState(prev => {
  // do something
  return newState;
});

setStateにわたす関数には、前の状態(prev)が引数として渡される。これも名前は何でもいい。
そして、新しく設定したい状態をreturnとして返す。
また、setStateを呼び出すとコンポーネントが再レンダリングされる。

使用例

Example.js
import { useState } from "react";
const Example = () => {
    const [count, setCount] = useState(0);
    const countUp = () => setCount(prev => prev + 1);
    const countDown = () => setCount(prev => prev - 1);
    return (
        <>
            <h3>{count}</h3>
            <button type="button" onClick={countUp}>count up</button>
            <button type="button" onClick={countDown}>count down</button>
        </>
    );
};
export default Example;

[count up]ボタンを押すたび、画面上の値が1ずつインクリメントされ、[count down]ボタンを押すと画面上の値がデクリメントされます。

useReducer

基本の形

const [state, dispatch] = userReducer(reducer, init);

引数

reducer:
stateを更新するための関数。
この関数の第一引数には、現在の状態が渡される。第二引数には後述のdispatchに渡した引数が設定される。
新しいstateに設定したい値を戻り値として返すようにする。

reducerの例
const reducer = (state, action){
  switch(action.type) {
    case anything:
      return newState;
  }
}

init:
stateに設定したい初期値。

useReducerは第三引数として、初期値を生成する関数を設定できますが、省略可能なためここでは割愛します。

戻り値

状態管理ライブラリであるReduxの影響を受けているらしい。
そのため、更新用関数の変数名はdispatchとするのが一般的
state:
現在の状態。一番初めはinitで設定した値が入る。

dispatch:
引数で設定したreducerを呼び出す。引数に何を設定してもよいが、オブジェクトで渡すのが一般的。
プロパティとしてtypeを設定し、それによって処理方法を分岐する。

使用例

Example.js
import { useReducer } from "react";

const reducer = (state, action) => {
    switch (action.type) {
        case '+':
            return state + 1;
        case '-':
            return state - 1;
        default:
            return state;
    }
};

const Example = () => {
    const [count, dispatch] = useReducer(reducer, 0);
    const countUp = () => dispatch({ type: '+' });
    const countDown = () => dispatch({ type: '-' });
    return (
        <>
            <h3>{count}</h3>
            <button type="button" onClick={countUp}>count up</button>
            <button type="button" onClick={countDown}>count down</button>
        </>
    );
};
export default Example;

実行結果はuseStateのときと同じ

2つの共通点

useState, useReducerの両者とも

  • 現在の状態を持つ
  • 状態を更新する
  • 状態の更新時、コンポーネントを再レンダリングする

ということが共通しています。
そのため、画面の操作によって画面上の値などを変化させたい場合、どちらを使ってもよいように思えます。

2つの相違点

決定的な違いは、更新の方法を決めるタイミングです。

useStateの場合

useStateの戻り値である更新用関数のsetStateを実行するときにstateをどのように更新するかを決定します。
つまり、コンポーネントの内部で更新方法を決定します。

useReducerの場合

useReducerreducerとして関数を渡し、stateをどのように更新するかを決定します。
reducerはコンポーネントの外部で設定することも可能です。
useReducerの戻り値である更新用の関数dispatchを実行する際には、すでにreducerによって更新方法が決定されているため、単にアクションを引数として渡すだけです。

どちらを使うべきか

基本的にはuseStateでよいと考えています。
しかし、複雑なロジックや、互いに関連する状態を管理する場合にはuseReducerのほうがわかりやすくなると思います。

以下のように、入力された項目と、カウント数を表示する画面があるとします。

これをuseReducerを使って表現すると以下のようになります。

Example.js
import { useReducer } from "react";
const ACTIONS = {
    PLUS: 'PLUS'
    , MINUS: 'MINUS'
    , CHANGE_TXT: 'CHANGE_TXT'
}
const INIT_DATA = { text: 'apple', count: 0 };

const reducer = (state, action) => {
    switch (action.type) {
        case 'PLUS':
            return { ...state, count: state.count + 1 };
        case 'MINUS':
            return { ...state, count: state.count - 1 };
        case 'CHANGE_TXT':
            return { ...state, text: action.text };
        default:
            return state;
    }
};

const Example = () => {
    const [state, dispatch] = useReducer(reducer, INIT_DATA);
    const countUp = () => dispatch({ type: ACTIONS.PLUS });
    const countDown = () => dispatch({ type: ACTIONS.MINUS });
    const changeText = (e) => dispatch({ type: ACTIONS.CHANGE_TXT, text: e.target.value })
    return (
        <>
            <h3>{state.text}:{state.count}</h3>
            <button type="button" onClick={countUp}>+</button>
            <button type="button" onClick={countDown}>-</button>
            <input type="text" value={state.text} onChange={changeText}></input>
        </>
    );
};

export default Example;

カウントの増減、テキストの変更の処理がすべてreducerの中にまとまっており、シンプルです。

同じ処理をuseStateを使って実装すると、更新用関数であるsetStateの呼び出し時に処理を記載することになるため、処理がバラバラの場所に記載されます。

まとめ

useStateuseReducerについて、基本的には実現できることは変わらないことがわかりました。
通常はだいたいuseStateでよさそうですが、複雑な処理や複数の状態を管理する場合には、useReducerの方がより理解しやすいといえます。

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?