4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Django+Reactで学ぶプログラミング基礎(34): Reduxチュートリアル(TodoアプリのStore)

Last updated at Posted at 2022-06-21
[前回] Django+Reactで学ぶプログラミング基礎(33): Reduxチュートリアル(Todoアプリの実装(続き))

はじめに

前回は、TodoアプリのReducerを実装しました。
今回は、Storeの設計と実装です。

今回の内容

Todoアプリの構築

  • Redux Store
    • Storeとは
    • Storeを作成
    • 初期Stateをロード
    • actionをdispatch
    • Storeの内部構造

Storeとは

  • アプリを構成するstate/action/reducerをまとめたもので、以下の機能を持つ
    • 現在のアプリstateを保持
      • store.getState()を介して現在のstateにアクセス
      • store.dispatch(action)を介してstateを更新
      • store.subscribe(listener)を介してリスナーコールバック(listener callback)関数を登録
        • コールバック関数とは
          • ある関数を呼び出す際、引数として渡される別の関数のこと
          • 呼び出し側で用意した関数を、呼び出し先のコードが呼び出し返す(callback)
      • store.subscribe(listener)によって返されるunsubscribe関数を介してリスナーの登録を解除
    • Reduxアプリには1つのstoreしかない
      • データ処理ロジックを分割する場合は、reducerを複数に分割することで実現

Storeを作成

  • storeには単一のrootReducerが存在
    • ReduxコアライブラリのcreateStoreAPIで、storeを作成
      • まず、store.jsファイルを追加し、createStorerootReducerをインポート
      • 次に、createStoreを呼び出して、rootReducerを渡す
      • createStoredeprecatedと警告されましたが、一旦そのまま使います
src/store.js
import { createStore } from 'redux'
import rootReducer from './reducer'

const store = createStore(rootReducer)

export default store

image.png

初期stateをロード

  • createStoreの第2引数preloadedStateを用いて、store作成時の初期データを渡す
    • サーバーから送信されたHTMLページに含まれている値
    • localStorageに保持され、ページに再度アクセス時に参照される値
# preloadedStateの使用例
import { createStore } from 'redux'
import rootReducer from './reducer'

let preloadedState
const persistedTodosString = localStorage.getItem('todos')

if (persistedTodosString) {
  preloadedState = {
    todos: JSON.parse(persistedTodosString)
  }
}

const store = createStore(rootReducer, preloadedState)

actionをdispatch

  • store.dispatch(action)を呼び出すと、下記処理が行われる
    • storeがrootReducer(state, action)をコール
    • rootReducerにインポートされているsliceReducerをコール
      • todosReducer(state.todos, action)
    • storeに新しいstate値を保存
    • storeに登録されたリスナーコールバックをコール
      • リスナーからは、store.getState()をコールし、最新stateを参照
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import './api/server'

import store from './store'

// Log the initial state
console.log('Initial state: ', store.getState())
// {todos: [....], filters: {status, colors}}

// Every time the state changes, log it
// Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() =>
  console.log('State after dispatch: ', store.getState())
)

// Now, dispatch some actions

store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about reducers' })
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about stores' })

store.dispatch({ type: 'todos/todoToggled', payload: 0 })
store.dispatch({ type: 'todos/todoToggled', payload: 1 })

store.dispatch({ type: 'filters/statusFilterChanged', payload: 'Active' })

store.dispatch({
  type: 'filters/colorFilterChanged',
  payload: { color: 'red', changeType: 'added' }
})

// Stop listening to state updates
unsubscribe()

// Dispatch one more action to see what happens

store.dispatch({ type: 'todos/todoAdded', payload: 'Try creating a store' })

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)

作成したstoreが機能するかテスト

  • UIがなくても、ブラウザのデベロッパーツールで更新ロジックをテスト可能
  • VS Codeのターミナルで、アプリを起動すると、ブラウザが開かれる
C:\kanban\todo>cd redux-fundamentals-example-app
C:\kanban\todo\redux-fundamentals-example-app>npm start
  • ブラウザで、デベロッパーツールを開き(F12)、コンソールの出力を確認
    • 各actionがdispatchされたとき、Reduxのstateがどのように変化するか確認できる
    • unsubscribe()がコールされると、リスナーコールバックが削除される
      • それ以降の、action dispatchは、ログ出力されない
        image.png

Storeの内部構造

  • Redux Store実装コードのミニチュア版
function createStore(reducer, preloadedState) {
  let state = preloadedState
  const listeners = []

  function getState() {
    return state
  }

  function subscribe(listener) {
    listeners.push(listener)
    return function unsubscribe() {
      const index = listeners.indexOf(listener)
      listeners.splice(index, 1)
    }
  }

  function dispatch(action) {
    state = reducer(state, action)
    listeners.forEach(listener => listener())
  }

  dispatch({ type: '@@redux/INIT' })

  return { dispatch, subscribe, getState }
}
  • storeには、現在のstateとreducer関数が存在
  • getStateは現在のstateを返す
  • subscribeは、リスナーコールバックの配列を保持し、新しいコールバックを削除するための関数を返す
  • dispatchは、reducerを呼び出し、stateを保存し、listenerを実行
  • store起動時に、1つのactionをdispatchし、reducerをそのstateで初期化
  • store APIは、内部に{dispatch、subscribe、getState}を持つオブジェクト
    • getStateでstateを取得可能
    • state値を直接書き換えたり、配列のsort()関数を使ってはいけない
      • array.sort()は、既存の配列要素を書き換える(mutate)
const state = store.getState()
// ❌ Don't do this - it mutates the current state!
state.filters.status = 'Active'

おわりに

Redux Storeの概念を深掘りし、実装を行いました。
次回も続きます。お楽しみに。

[次回] Django+Reactで学ぶプログラミング基礎(35): Reduxチュートリアル(TodoアプリのStoreをカスタマイズ)
4
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
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?