[前回] Django+Reactで学ぶプログラミング基礎(36): Reduxチュートリアル(MiddlewareでStoreをカスタマイズ)
はじめに
前回は、Middlewareを用いてStoreをカスタマイズしました。
今回は、RuduxとUIレイヤーの統合です。
今回の内容
- ReduxとUIレイヤー
- ReactでReduxを使用
ReduxとUIレイヤー
-
ReduxはスタンドアロンのJSライブラリ
- クライアントとサーバーの両方で使用可能
- 任意のUIフレームワークで使用可能で、すべてのUIで同じように機能
- React、Vue、Angular、Ember、jQuery、vanilla JavaScriptで使用可能
- 特にReactと連携するように設計され、UIをstateの関数として記述できる
-
ReduxとUIレイヤーの統合手順
- Redux storeを作成
- state更新をsubscribe
- subscriptionコールバックの処理
- 現在storeのstateを取得
- UIに必要なデータを抽出
- データでUIを更新
- 必要に応じ、UIを初期stateでレンダリング
- Redux actionをdispatchし、UI入力に応答
-
上記統合手順を、カウンターアプリの例から
// 1) Redux storeを作成
const store = Redux.createStore(counterReducer)
// 2) state更新をSubscribeし、データ更新時に再レンダリング可能に
store.subscribe(render)
// UIはtextのみのHTML要素
const valueEl = document.getElementById('value')
// 3) subscription callback内の処理:
function render() {
// 3.1) 現在storeのstateを取得
const state = store.getState()
// 3.2) 必要データを抽出
const newValue = state.value.toString()
// 3.3) 新しい値でUIを更新
valueEl.innerHTML = newValue
}
// 4) UIをstoreの初期stateでレンダリング
render()
// 5) UI入力に応じ、actionをdispatch
document.getElementById('increment').addEventListener('click', function () {
store.dispatch({ type: 'counter/incremented' })
})
ReactでReduxを使用
-
React-Redux
UIバインディングライブラリを使用- インストール:
npm install react-redux
- インストール:
ビジネス要件に基づき、コンポーネントツリーを設計
-
<App>
: アプリ全般をレンダリングするルートコンポーネント-
<Header>
:新しいtodo
入力とすべてのtodoを完了
するチェックボックス -
<TodoList>
: フィルタリング結果に基づき表示されるToDoリスト-
<TodoListItem>
: 単一ToDoリストアイテム- 完了ステータスを切り替えるチェックボックスとカラーカテゴリセレクターを持つ
-
-
<Footer>
: アクティブなタスクの数を表示- 完了ステータスと色のカテゴリを用いてリストをフィルタリングするコントロールも表示
-
useSelectorフックを使用し、storeからstateを取得
-
ToDoアイテムリストを表示するため
- storeからtodoリストを読み取る
- todoアイテムごとに
<TodoListItem>
コンポーネントを表示する<TodoList>
コンポーネントを作成
-
useSelectorフック
- React-Reduxフックで、ReactコンポーネントとRedux storeとの通信機能を提供
- stateを取得しactionをdispatch
- selector関数を引数として受け取る
- selectorは、Redux Storeのstateから、値を読み取り、結果を返す関数
- selectorの例1: todoアイテム配列のstate.todosを返す
const selectTodos = state => state.todos
- selectorの例2: 完了マークされたtodoの数を返す
- selectorの例1: todoアイテム配列のstate.todosを返す
- React-Reduxフックで、ReactコンポーネントとRedux storeとの通信機能を提供
const selectTotalCompletedTodos = state => {
const completedTodos = state.todos.filter(todo => todo.completed)
return completedTodos.length
}
- ToDoの配列を
<TodoList>
コンポーネントに読み込む- まず、react-reduxライブラリからuseSelectorフックをインポート
- 次に、selector関数を引数として呼び出す
src/features/todos/TodoList.js
import React from 'react'
import { useSelector } from 'react-redux'
import TodoListItem from './TodoListItem'
const selectTodos = state => state.todos
const TodoList = () => {
const todos = useSelector(selectTodos)
// since `todos` is an array, we can loop over it
const renderedListItems = todos.map(todo => {
return <TodoListItem key={todo.id} todo={todo} />
})
return <ul className="todo-list">{renderedListItems}</ul>
}
export default TodoList
-
コード説明
- Reactの
<TodoList>
コンポーネントが初めてレンダリングされるとき- useSelectorフックはselectTodosを呼び出し、Redux stateを渡す
- selectorの返却値はフックによって
<TodoList>
コンポーネントに渡される- コンポーネントの
const todos
に、Redux storeのstate内のstate.todos配列が代入される
- コンポーネントの
-
{type:'todos/todoAdded'}
actionがdispatchされたら- useSelectorは自動的にRedux Storeにsubscribeし、storeの変更をlisten
- Reduxのstateがreducerによって更新されると、Reactの
<TodoList>
コンポーネントは、新しいToDoのリストを検知し再レンダリング
- Reactの
-
※注意: selector関数でstate.todos配列への新しい参照を返却してはいけない
- useSelectorは、厳密な
===
参照比較を使用し結果を比較 - selectorが新しい参照を作成して返却すると
- stateが変更しなくても、actionがdispatchされる度にコンポーネントが再レンダリングされる
- useSelectorは、厳密な
// Bad: array.map()は常に新しい配列参照を返す
const selectTodoDescriptions = state => {
// This creates a new array reference!
return state.todos.map(todo => todo.text)
}
- ※ selector関数を個別変数として宣言する必要なし
const todos = useSelector(state => state.todos)
useDispatchを使用しactionをdispatch
-
Reactのコンポーネントから、Redux storeにアクションをdispatchする方法
- Reactコンポーネントからstoreにアクセスできないため、
store.dispatch(action)
をコールできない - React-Redux useDispatchフックを使用し、storeのdispatchを実現
-
const dispatch = useDispatch()
を定義し、必要に応じてdispatch(someAction)
を呼び出す
-
- Reactコンポーネントからstoreにアクセスできないため、
-
<Header>
コンポーネントで、新しいtodoアイテムを作成する{type:'todos/todoAdded'
actionをdispatch- まず、React formコンポーネントを作成し、フォームから
制御された入力
を可能に - 次に、ユーザーがEnterキーを押すと、actionがdispatchされる
- まず、React formコンポーネントを作成し、フォームから
src/features/header/Header.js
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
const Header = () => {
const [text, setText] = useState('')
const dispatch = useDispatch()
const handleChange = e => setText(e.target.value)
const handleKeyDown = e => {
const trimmedText = e.target.value.trim()
// If the user pressed the Enter key:
if (e.key === 'Enter' && trimmedText) {
// Dispatch the "todo added" action with this text
dispatch({ type: 'todos/todoAdded', payload: trimmedText })
// And clear out the text input
setText('')
}
}
return (
<input
type="text"
placeholder="What needs to be done?"
autoFocus={true}
value={text}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
)
}
export default Header
Reactコンポーネントで使用するstoreをReact-Reduxに指示
-
React-ReduxフックはJS関数で、store.jsからstoreを自動的にインポートできない
- コンポーネントが通信を行うRedux storeストアを指示する必要あり
-
<App>
全体で<Provider>
コンポーネントをレンダリングし、Redux storeをpropとして<Provider>
に渡す- アプリのすべてのコンポーネントがRedux storeにアクセスできるようになる
-
メインのindex.jsファイルに追加
src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import store from './store'
ReactDOM.render(
// Render a `<Provider>` around the entire `<App>`,
// and pass the Redux store to as a prop
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
)
- コード説明
- React-ReduxをReactで使用する際の重要な部分が含まれている
- useSelectorフックを呼び出し、Reactコンポーネントのデータを読み取る
- useDispatchフックを呼び出し、Reactコンポーネントのactionをdispatch
-
<App>
コンポーネント全体に<Providerstore={store}>
を配置し、他のコンポーネントがstoreと通信できるように
- React-ReduxをReactで使用する際の重要な部分が含まれている
アプリを起動しUIを確認
- VS Codeのターミナルで、アプリを起動すると、ブラウザが開かれる
C:\kanban\todo>cd redux-fundamentals-example-app
C:\kanban\todo\redux-fundamentals-example-app>npm start
- ブラウザで、アプリを操作してみる
おわりに
ReduxとUIレイヤーのReactとの連携を勉強しました。
次回も続きます。お楽しみに。