Redux入門 State, Actions, and Reducers 基礎的な解説 | React

Reduxは、「アクション」と呼ばれるイベントを使って、

 

アプリケーションの状態を管理・更新するためのパターンとライブラリです。

 

これは、アプリケーション全体で使用される必要のあるステートを、

 

集中的に保存する役割を果たし、

 

ルールによってステートが予測可能な方法でのみ更新されるようになります。

 

今回のコンテンツでは、Reduxに関する基礎的な用語や使い方を体系的に解説していきます。

 

例によって、今回のコンテンツは公式の英語のドキュメントの和訳と、分かりやすく短くまとめたものなので、

 

より詳しいことを知りたい方は公式のドキュメントを是非ともご覧ください。

 

 

 

 

なぜReduxを使うべきなのか?

 

Reduxでは、「グローバル」な状態、

 

つまりアプリケーションの多くの部分で必要とされる状態を管理することができます。

 

Reduxが提供するパターンとツールにより、

 

アプリケーション内の状態がいつ、どこで、なぜ、どのように更新されるのか、

 

そして、その変更が発生したときにアプリケーションロジックがどのように動作するのかを、

 

容易に理解することができます。

 

どんなときにReduxを使うべきか?

 

Reduxは、共有された状態管理に対処するのに役立ちますが、

 

他のツールと同様、トレードオフがあり、覚えるべき概念が増え、書くべきコードも増えます。

 

短期的な生産性と長期的な生産性のトレードオフの関係をよく考えて使うべきですし、

 

そういう点を踏まえると、

 

アプリの様々な場所で必要となる大量のアプリケーション・ステートがあり、

アプリの状態は、時間の経過とともに頻繁に更新され、状態を更新するロジックが複雑で、

アプリには中規模または大規模なコードベースがあり、多くの人が作業している可能性がある場合、

 

Reduxを有効活用できます。

 

Reduxの概念

 

実際にコードを書く前に、Reduxを使う上で知っておくべき概念について説明します。

 

まず、小さなReactのカウンターコンポーネントを見てみましょう。

 

このコンポーネントは、コンポーネントの状態で数値を追跡し、

 

ボタンがクリックされると数値を増加させます。

 

function Counter() {
  // State: a counter value
  const [counter, setCounter] = useState(0)

  // Action: code that causes an update to the state when something happens
  const increment = () => {
    setCounter(prevCounter => prevCounter + 1)
  }

  // View: the UI definition
  return (
    <div>
      Value: {counter} <button onClick={increment}>Increment</button>
    </div>
  )
}

 

これは、「一方通行のデータフロー」の小さな例です。

ユーザーがボタンをクリックするなど、何かが発生すると、

 

発生した内容に基づいて状態が更新され、

新しい状態に基づいてUIが再レンダリングされます。

 

引用画像

 

しかし、同じステートを共有して使用する必要のある複数のコンポーネントがある場合、

 

特にそれらのコンポーネントがアプリケーションの異なる部分に配置されている場合には、

 

このシンプルさが崩れてしまいます。

 

この問題は、親コンポーネントにステートを「持ち上げる」ことで解決できる場合もありますが、

 

必ずしも解決できるわけではありません。

 

これを解決する1つの方法は、コンポーネントから共有された状態を抽出し、

 

コンポーネントツリーの外側にある集中的な場所に置くことです。

 

これにより、コンポーネントツリーは大きな「ビュー」となり、どのコンポーネントもツリーのどこにいても、

 

ステートにアクセスしたり、アクションを起こしたりすることができます。

 

状態管理に関わる概念を定義して分離し、ビューと状態の間の独立性を維持するルールを適用することで、コードに構造性と保守性を与えます。

 

これがReduxの基本的な考え方です。

 

アプリケーションのグローバルな状態を格納する単一の集中的な場所と、

 

その状態を更新する際に従うべき特定のパターンにより、

 

コードを予測可能にします。

 

Reduxは不変性を大事にする

 

“Mutable “は「変更可能な」という意味です。

 

何かが「不変」であれば、それは決して変更できません。

 

JavaScriptのオブジェクトや配列は、デフォルトではすべて変更可能です。

 

オブジェクトを作成すると、そのフィールドの内容を変更することができます。

 

配列を作成すると、その内容も変更することができます。

 

const obj = { a: 1, b: 2 }
// still the same object outside, but the contents have changed
obj.b = 3

const arr = ['a', 'b']
// In the same way, we can change the contents of this array
arr.push('c')
arr[1] = 'd'

 

これをオブジェクトや配列のミューティングと呼びます。

 

メモリ内のオブジェクトや配列の参照は同じですが、オブジェクト内のコンテンツが変更されています。

 

値を不変的に更新するためには、コードは既存のオブジェクトや配列のコピーを作成し、そのコピーを変更する必要があります。

 

JavaScript の配列/オブジェクト拡散演算子や、

 

元の配列を変更する代わりに配列の新しいコピーを返す配列メソッドを使用して、

 

手作業でこれを行うことができます。

 

const obj = {
  a: {
    // To safely update obj.a.c, we have to copy each piece
    c: 3
  },
  b: 2
}

const obj2 = {
  // copy obj
  ...obj,
  // overwrite a
  a: {
    // copy obj.a
    ...obj.a,
    // overwrite c
    c: 42
  }
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')

 

Reduxでは、すべての状態の更新が不変的に行われることを期待しています。

 

このことがどこで、どのように重要なのか、

 

また、不変的な更新ロジックを簡単に書く方法については、後ほど説明します。

 

Reduxの用語解説

 

先に進む前に、いくつかの重要なReduxの用語を知っておく必要があります。

アクション

アクションは、Typeフィールドを持つプレーンなJavaScriptオブジェクトです。

 

アクションは、アプリケーションで起こったことを記述するイベントと考えることができます。

 

typeフィールドには、”todos/todoAdded “のように、アクションに説明的な名前を与える文字列を入力します。

 

通常、このタイプの文字列は “domain/eventName “のように記述します。

 

最初の部分はこのアクションが属する機能やカテゴリーで、2番目の部分は具体的に起こったことを表しています。

 

アクションオブジェクトは、何が起こったかについての追加情報を持つ他のフィールドを持つことができます。

 

慣習的に、その情報はペイロードと呼ばれるフィールドに入れます。

 

典型的なアクションオブジェクトは次のようなものです。

 

const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}

 

アクションクリエータ

アクションクリエイターとは、アクションオブジェクトを作成して返す関数です。

 

一般的には、アクションオブジェクトを毎回手で書く必要がないように、これらを使用します。

 

const addTodo = text => {
  return {
    type: 'todos/todoAdded',
    payload: text
  }
}

 

レデューサー

レデューサーとは、現在の状態とアクションオブジェクトを受け取り、

 

必要に応じて状態をどのように更新するかを決定し、新しい状態を返す関数です。

 

(state, action) => newState. レデューサーは、受け取ったアクション(イベント)の種類に応じて、

 

イベントを処理するイベントリスナーと考えることができます。

 

const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === 'counter/increment') {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1
    }
  }
  // otherwise return the existing state unchanged
  return state
}

 

ストア

 

現在のReduxアプリケーションの状態は、ストアと呼ばれるオブジェクトに格納されています。

 

ストアはレデューサーを渡すことで作成され、

 

現在の状態の値を返すgetStateというメソッドを持っています。

 

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}

 

そしてアクションのディスパッチは、

 

アプリケーションでの「イベントのトリガー」と考えることができます。

 

何かが起こって、それをストアに知らせたいのです。

 

レデューサーはイベントリスナーのような役割を果たし、

 

関心のあるアクションを聞くと、それに応じて状態を更新します。

 

一般的には、アクションクリエイターを呼び出して、正しいアクションをディスパッチします。

 

const increment = () => {
  return {
    type: 'counter/increment'
  }
}

store.dispatch(increment())

console.log(store.getState())
// {value: 2}

 

まとめ

 

つまり、ここまでの話をまとめると、

 

Reduxとは、データの状態を管理やすくするツールということ。

 

ビューと、ステートを切り分けて、いつでも同じ場所から、同じように、同じ方法で、予測可能な方法でデータを出し入れしたいよねということ。

 

ディスパッチを使ってレデューサーを呼び出すことで、適切なデータ更新を行い新しいステートを返して、

 

データの形というのはアクションと呼ばれるオブジェクトに定義すると、そしてアクションを使って呼び出すと。

 

こんな形なのかなと思います。

藤沢瞭介(Ryosuke Hujisawa)
  • りょすけと申します。18歳からプログラミングをはじめ、今はフロントエンドでReactを書いたり、AIの勉強を頑張っています。off.tokyoでは、ハイテクやガジェット、それからプログラミングに関する情報まで、エンジニアに役立つ情報を日々発信しています!

React

コメントする