💡

Reduxの全体像をざっくり理解する奴

2021/12/02に公開

💡全体像をざっくり理解する

登場人物

Store
あらゆる状態を管理している倉庫のような奴

Reducer
Storeにある状態を更新する奴
Storeにある状態を更新できるのはReducerだけ
倉庫(Store)の中にいる仕分け人のようなイメージ
Reducerが状態を更新するときは、現在のstateactionを受け取る
actionに応じて新しい状態を返す

dispatch
Reducerに現在のstateactionを届けるだけな奴
「こういう注文(action)来てます〜。現在のstate(状態)もお届けしました〜。 Reducerさん、後はよろしくです〜。」なイメージ

action
dispatchされる注文伝票みたいな奴
action(注文伝票)をReducerに届けることで、Reduceractionに応じて新しい状態を返す

例えばカウントを制御するアプリケーションの場合、
「カウント増やして〜」とか「カウント減らして〜」などのactionがある

state
状態のこと。Storeで管理されている。

全体の流れ

ここではわかりやすいようにカウントを制御するだけのReactアプリケーションを想定します。

  1. ユーザー「カウント+1ボタンポチッと」
  2. 現在のstate(count: 0)と action(カウント増やして~)を Storedispatch(送る)する
  3. Storeにいる仕分け人Reducerが 現在のstate(count: 0)とaction(カウント増やして~)を受け取る
  4. Reducer「えぇーと、現在のstate(count: 0)とactionが"カウント増やして〜"ね、OK! 」
  5. Reducer「はい完了! state(count: 1)」
  6. React「お!stateが更新された。よっしゃー画面を新しい状態に更新するぞー」
  7. ユーザー「カウントが"1"になった〜」

💡Redux ToolKitで全体像を理解する

アプリは単一のコンポーネントと単一のストアを持つ

Reactアプリケーションが階層の一番上に単一のコンポーネントを持っているように、Storeも単一です。
1つのアプリケーションで状態を管理するStoreは1つ。
全てのstateを1つのStoreで管理し、必要なデータを適宜取り出せる
stateをコンポーネントツリーの外部にあるStoreで持つイメージ
アプリの状態は単一のStore内のオブジェクトツリーに保存される

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { store } from './app/store'
import { Provider } from 'react-redux'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('app'),
)

Slice

Storeが倉庫だとしたらSliceは倉庫の中にある棚のようなイメージ
アプリケーションで扱う状態に応じてSliceを切り分けることで、状態の管理がしやすくなる。
例えばcountを扱うSliceやtodoを扱うSliceなどに分けることができる
createSlicestate,reducer,actionをまとめて作成する関数(便利!!)

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

const initialState = {
  value: 0,
}

// Sliceの作成
export const counterSlice = createSlice({
  name: 'counter', 	// actionの名前の一部になる
  initialState, 	// stateの初期状態
  reducers: { 
    increment: (state) => { // 状態を更新する関数(actionの名前の一部になる)
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
	// 非同期処理を扱うときに使用する
	extrareducer: ...
})

// コンポーネントからactionをdispatchできるようにexport
export const { increment, decrement } = counterSlice.actions

// コンポーネントからstateを参照するための関数をexport
export const selectCount = (state) => state.counter.value

// Storeに登録するためにexport
export default counterSlice.reducer

state

stateinitialState に記述する

const initialState = {
  value: 0,
}

export const counterSlice = createSlice({
  //省略
  initialState,
 //省略
})

こう書いてもOK

export const counterSlice = createSlice({
  //省略
  initialState: {
    value: 0,
	}
 //省略
})

reducer

reducersの中に状態を変更する関数をまとめる
incrementdecrementreducerにあたる

export const counterSlice = createSlice({
	//省略
  reducers: { 
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
	//省略
})

action

actionの名前はname/reducerというような形になる(Redux DevToolsでみると分かりやすい)
下記で言うとcounter/incrementのような形になる
ただし、Redux ToolKitが内部でよしなに色々とやってくれるので、reducersに登録したreducerの名前を下記のようにexportしてあげればOK
exportしたactionをコンポーネントからdispatchすることで状態が更新される

export const counterSlice = createSlice({
  name: 'counter',
  reducers: { 
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
})

// コンポーネントからactionをdispatchできるようにexport
export const { increment, decrement } = counterSlice.actions

SliceをStoreへ結合

Storeに登録するReducerkeystateにアクセスできる
Reducerのキーがcounterなのでstate.counterでアクセスできる

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

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

状態の参照(useSelector)

useSelector...stateに変更があったら自動的に再実行され、コンポーネントを再描画する

import { useSelector } from 'react-redux'
import { selectCount } from '../features/counter/counterSlice'

const count = useSelector(selectCount) 

const Counter = () => {
 return(
  <>
    <button>クリック!!</button>
    <span>{count}</span>
  </>
  )
}

状態の更新(useDispatch)

useDispatchdispatch関数を返す
actionを引数にdispatch関数を実行することでReducerが作動し、stateが更新される
actionに引数を渡したらReduceraction.payloadとして値を受け取れる

import { useDispatch, useSelector } from 'react-redux'
import { selectCount, increment } from '../features/counter/counterSlice'

const dispatch = useDispatch()
const count = useSelector(selectCount) 

const Counter = () => {
  return(
    <>
      <button onClick={()=> dispatch(increment())}>クリック!!</button>
      <span>{count}</span>
    </>
  )
}

💡全体の流れを簡単におさらい

  1. アプリは単一のコンポーネントと単一のストアを持つ
  2. Sliceを作成してStoreへ結合する
  3. useSelectorを使用して状態の参照ができる
  4. useDispatchを使用して状態の更新ができる

Tips

はるか昔、actionを作るのに色々とごちゃごちゃしていましたが、 actionの作成に関してあまり意識しなくても良くなりました。
Redux ToolKitが中でいい感じに仕事してくれてると思って頂ければ良いと思います。興味がある方はググって見て下さい。

Discussion