はじめに
以下の2つの記事をめっちゃ参考にしました。
何ができる?
Reactのstateを一元管理することができる
登場人物
- ActionCreator
- Actionを作成するメソッド
- Action
- 実行する処理
- Store
- State置き場
- State
- データ
- Reducer
- stateを更新するメソッド
処理の流れ
- ユーザーが送信ボタン等をクリックする
- ActionCreatorがActionを作成する
- ActionをStoreに送る(dispatchする)
- 受け取ったActionと現在のStateをReducerに渡す
- 新しいStateを作成する
- 受け取ったActionによってStoreの扱い方を判断する
- 元のStateを改変するのではなく、新しいStateを作る
- 古いStateを破棄して、新たに作成されたStateを保存する
以下の記事から拝借しました。
Redux入門【ダイジェスト版】10分で理解するReduxの基礎
3原則
- Single source of truth
- アプリケーション内でStoreは1つだけ。Stateは単独のオブジェクトとしてStoreに保持される。
- State is read-only
- Stateを直接変更することはできず、actionをStoreへdispatchすることでしかStateは変更できない。
- Mutations are written as pure functions
- Stateを変更する関数(Reducer)はpureな関数にする。
Reduxを使ったTODOアプリを作ってみる
セットアップ
$ create-react-app react-redux-todo
$ cd react-redux-todo
$ npm i redux react-redux redux-logger
$ rm -rf src
$ mkdir src
$ cd src
$ mkdir actions components containers reducers
$ touch actions/Todo.js components/Todo.js containers/Todo.js reducers/Todo.js createStore.js App.js index.js
$ cd ..
Storeを作成
src/createStore.js
import { createStore as reduxCreateStore, applyMiddleware, combineReducers } from "redux";
import logger from "redux-logger";
import { todoReducer } from "./reducers/Todo";
export default function createStore() {
const store = reduxCreateStore(
//複数のReducerを扱うために、combineReducerを利用
combineReducers({
todo: todoReducer,
}),
applyMiddleware(
logger,
)
);
return store;
}
Reducer(仮)を作成
src/reducers/Todo.js
export const todoReducer = (state = {}) => state;
ReactにReduxを組み込む
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import createStore from './createStore';
const store = createStore();
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
-
createStore
とreact-redux
をimport - createStoreで
store
を生成 - storeを
Provider
に渡し、Appコンポーネントをラップ
次にApp.jsのひな形を作成。
src/App.js
import React from 'react';
function App() {
return (
<div>
Hello World
</div>
);
}
export default App;
$ npm start
とりあえずここまででエラーが発生していないことを確認
TODOコンポーネントを作成
ひとまず入力エリア、ボタン、リストを作っておきます。
src/components/Todo.js
import React from 'react';
export default class Todo extends React.Component {
render() {
return (
<div>
<input type="text" />
<button>追加</button>
<ul>
<li>TODO1</li>
<li>TODO2</li>
</ul>
</div>
);
}
}
そしてApp.js
で読み込みます。
src/App.js
import React from 'react';
import Todo from './components/Todo';
function App() {
return (
<div>
<Todo />
</div>
);
}
export default App;
Actionを定義
src/actions/Todo.js
export const addTodo = (todo) => {
return {
type: 'ADD_TODO',
payload: { todo: todo }
};
}
-
type
が処理のキーで、全てのactionに必ず1つ設定されています- どの
reducer
を使うかの判断材料になります
- どの
-
payload
は処理に使うパラメーターです
Reducerを修正
src/reducers/Todo.js
const initialState = {
todoList: []
}
export const todoReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TODO':
const todo = action.payload.todo;
const newState = Object.assign({}, state);
newState.todoList.push(todo);
return newState;
default:
return state;
}
};
- 現在のstateを定義
- 引数に現在のstateと、受け取ったactionをとったメソッドを作成
-
action.type
に応じて処理を切り分ける -
Object.assign
メソッドで現在のstateを複製 - 複製したstateに値を追加して返す
- 元のstateの値を直接編集することはできない点に注意
Containerを定義
コンテナを定義して、reduxとコンポーネントを結びつけます
src/containers/Todo.js
import { connect } from 'react-redux';
import * as actions from '../actions/Todo';
import Todo from '../components/Todo';
const mapStateToProps = state => {
return {
todo: state.todo,
}
}
const mapDispatchToProps = dispatch => {
return {
addTodo: (todo) => dispatch(actions.addTodo(todo)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Todo)
-
mapStateToProps
メソッドで、必要なStateをコンポーネントのprops
から参照できるようにします -
mapDispatchToProps
メソッドで、actions/Todo.js
で定義したアクションをコンポーネントのprops
から参照できるようにします - connect関数で
Todo
コンポーネントとの紐付けを行います
最後に、App.js
でコンポーネントをimportしていた部分を、コンテナをimportするように書き換えます。
src/App.js
- import Todo from './components/Todo';
+ import Todo from './containers/Todo';
コンポーネントからStateを参照
コンテナからマッピングされて使えるようになったstate
、dispatch
用のpropsを使うように修正します。
src/components/Todo.js
import React from 'react';
export default class Todo extends React.Component {
state = {
todo: ''
}
render() {
//console.log(this.props);
const list = this.props.todo.todoList.map((todo, index) => <li key={index}>{todo}</li>)
return (
<div>
<input type="text" onChange={elm => this.setState({ todo: elm.target.value })} />
<button onClick={() => this.props.addTodo(this.state.todo)}>追加</button>
<ul>
{list}
</ul>
</div>
);
}
}
-
store
のtodoList
から現在のリストの内容を受け取ってリストを作成→表示 -
onChange={elm => this.setState({ todo: elm.target.value })}
でテキスト変更時に入力内容をローカルのstate
に反映 - 「追加」ボタンをクリックした時に、
actions
で定義したaddTodo
メソッドをdispatch- 引数にローカルの
state
を渡す
- 引数にローカルの
-
createStore.js
でredux-logger
というミドルウェアをインポートしているので、store
のstate
が変更されるたびコンソールにログが出力される
思ったことなど
-
ActionCreator
はこの仕組みでいうどこの部分?-
src/actions/Todo.js
内で定義している関数をActionCreatorと呼ぶらしい
-
-
reducer
中、const initialState = {todoList: []}
でからの配列を定義しているけれど、本来はここにDBから取ってきた値をセットするはず - 同じく
reducer
内、action
を受け取った後に発火する関数の中で、サーバーにデータを送信してDBの更新を試みるはず - あくまで他のコンポーネントと値をやりとりする時にstoreを経由しないといけないって話なので、自分のコンポーネント内でstateを変更することはできる