LoginSignup
0
0

More than 1 year has passed since last update.

React + Reduxを基本からまとめてみた【10】【Redux Toolkit ②】

Last updated at Posted at 2022-01-19

Redux とは

『Redux』 とは、JavaScript製の状態管理ライブラリ。

※ 「状態」とは、アプリケーションで扱う動的なデータという理解でよい。⇨ 例えば、ユーザー名とか、いいねの数が「状態」で、状態は時系列とともに変化する。

状態管理ライブラリが解決する問題は、コンポーネントをまたいだデータの共有で、React でも Vue.js でも、コンポーネントは、DOM と同様にツリー構造をなす。
複数のコンポーネントで同じデータを使いたい、というケースがあるとする。
深い階層のコンポーネントがあるデータを必要とする場合、下図のように、実際にはそのデータを利用しないコンポーネントも含めて props のバケツリレーが発生する。

image.png

これではアプリのコードを複雑にし、さらに途中のコンポーネントに無関係な props が定義される為、不必要な依存も産みやすい。
そこで考え出されたのが、状態管理ライブラリで、『ストア』と呼ばれるデータの入れ物を用意して、各コンポーネントが直接ストアとコミュニケーションし、これにより、上述の問題が解決される。

image.png

Redux の特徴

『Redux』の特徴は、コンポーネントからのアクセスに一定のルールが設けられている点である。
React におけるデータフローが props を通した親から子への流れに限定されているのも、コードをスパゲッティ化から守るための意図がある。
状態管理がグローバル変数のように濫用できてしまうと、コードにアンチパターンを仕込む結果になってしまう。

Redux は上記のような問題に配慮し、コードを予測可能にしてバグが出にくくするため、ルールを設けている。

さて、以下がコンポーネントと Redux ストアとのデータのやり取りを表した図です({} はオブジェクト、f(x) は関数であることを示しています)。

image.png

Reduxのモジュール

State

状態を格納するオブジェクト。

sample.js
{ count: 0 }

Reducer

受け取った Action(後述)に応じて State を変更する関数。
正確には、新たな State を返却する関数。

sample.js
function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 }
    case 'ADD':
      return { count: state.count + action.payload }
    default:
      return state;
  }
}

Action

状態の更新を指示するオブジェクト。一般的に以下のプロパティを持ちます。

sample.js
{ type: 'INCREMENT' } // typeがStateに対する操作を表す
{ type: 'ADD', payload: 3 } // payloadは任意の引数

処理の流れ

image.png

1. コンポーネントが、Action Creator を呼び出して Action を取得する。
2. 取得した Action を Reducer に渡す。 ※これを『dispatch』という。
3. Reducer は、渡された Action に応じて State を更新する。
4. コンポーネントは State に変更があれば、関連する UI を書き換える。

設計のポイント

このようなルールの重要なポイントは、コンポーネントが自由に状態(State)を書き換えられない点です。状態を更新するためには必ず Reducer に更新処理を依頼する必要があり、さらに Reducer はあらかじめ決められた Action にしか反応しない。

状態に対して定義された変更しか加えられないこのルールは、予期しない更新処理を防ぎ、コードを予測しやすくする。

また、Action Creator も不確実性の排除に一役買って、Reducer に対しては決められた属性および値を持つ Action を渡さなければならない。type の値など間違った Action を渡さないように、Action Creator を介して Action を取得する。

Redux Toolkit

Redux 導入の課題

『Redux』は最小限の機能のため、導入する際は上で説明した各モジュールを自分で準備しなくてはいけない。

※ それらのコーディングは煩雑で、初学者にはハードルになり、ある程度習得したあとも、定型文のようなセットアップコードを書くのは大変で、また、アプリの規模によってはストアを適切に分割する必要がある。

『Redux Toolkit』は、上記の問題を解決するために開発されたライブラリである。

1. Redux ToolkitとReact-Reduxのパッケージをプロジェクトに追加。
$ npm install --save @reduxjs/toolkit react-redux
2. Reduxストアの作成

src/app/store.js という名前のファイルを作成する。
Redux ToolkitからconfigureStore APIをインポートし、まずは空のReduxストアを作成し、エクスポートするところから始める。

app/store.js
//JavaScript
import { configureStore } from '@reduxjs/toolkit'

export const store = configureStore({
  reducer: {},
})

//TypeScript
import { configureStore } from '@reduxjs/toolkit'

export const store = configureStore({
  reducer: {},
})

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch

これにより、Reduxストアが作成され、また、開発中にストアを検査できるように、
Redux DevToolsエクステンションが自動的に設定される。

3. ReduxストアをReactに提供

ストアを作成したら、src/index.jsにあるアプリケーションの周りにReact-Redux Provider を置くことで、Reactコンポーネントから利用可能にすることができる。
先ほど作成したReduxストアをインポートし、Appの周りにProviderを配置し、propとしてストアを渡す。

index.js
//JavaScript  //TypeScript
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
//追加
import { store } from './app/store'
import { Provider } from 'react-redux'

ReactDOM.render(
//追加
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
4.ReduxのState Sliceを作成

src/features/counter/counterSlice.js という名前のファイルを新規に追加し、そのファイルで、Redux ToolkitからcreateSlice APIをインポートする。

スライスを作成するには、スライスを識別するための文字列名、状態の初期値、状態を更新する方法を定義するための1つ以上のreducer関数が必要で、スライスを作成すると、生成されたReduxのアクションクリエイターとスライス全体のレデューサー関数をエクスポートすることができる。

Reduxでは、データのコピーを作成し、そのコピーを更新することで、すべての状態の更新をイミュータブルに記述する必要がある。しかし、Redux ToolkitのcreateSliceとcreateReducerのAPIは内部でImerを使って、正しいimmutable更新となる「mutating」更新ロジックを書くことができるようになっている。

features/counter/counterSlice.js
//JavaScript
import { createSlice } from '@reduxjs/toolkit'

const initialState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
  },
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer

//TypeScript

import { createSlice, PayloadAction } from '@reduxjs/toolkit'

export interface CounterState {
  value: number
}

const initialState: CounterState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  },
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer
5.ストアにスライスのリデューサを追加

次に、カウンタスライスからreducer関数をインポートして、ストアに追加する必要がある。reducerパラメータ内にフィールドを定義することで、その状態に対するすべての更新を処理するためにこのスライスreducer関数を使用するようにストアに指示する。

app/store.js
//JavaScript  //TypeScript
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export default configureStore({
  reducer: {
    counter: counterReducer,
  },
})
6.ReactコンポーネントでReduxのStateとActionを使う

React-Reduxフックを使って、ReactコンポーネントがReduxストアと対話できるようになった。useSelector を使ってストアからデータを読み、useDispatch を使ってアクションをディスパッチすることができる。src/features/counter/Counter.js ファイルを作成して コンポーネントを作成し、そのコンポーネントを App.js にインポートして 内にレンダリングする。

features/counter/Counter.js
//JavaScript
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'

export function Counter() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}

//TypeScript
import React from 'react'
import { RootState } from '../../app/store'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'

export function Counter() {
  const count = useSelector((state: RootState) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}

これで、「増量」「減量」ボタンをクリックすることができる。

  • 対応するReduxのアクションがストアにディスパッチされます。
  • カウンタースライスのレデューサーがアクションを見て、状態を更新します。
  • Counterコンポーネントは、ストアから新しい状態の値を見て、新しいデータで自分自身を再レンダリングする。

学んだこと

  • configureStoreでReduxストアを作成する
  • configureStoreは、名前付き引数としてreducer関数を受け取ります。
  • configureStoreは自動的に良いデフォルト設定でストアをセットアップする。
  • ReduxストアをReactアプリケーションのコンポーネントに提供する。
  • React-Redux の コンポーネントを の周りに配置する。
  • Reduxストアをとして渡す。
  • Reduxの "スライス "レデューサーをcreateSliceで作成する。

参考サイト

HookとRedux ToolkitでReact Reduxに入門する
Redux Toolkit

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