React with Redux toDoList パート3
前回の記事の続きになります。また、以下の記事を参照して、まとめていきます。
Reduxとは
- UI要素はアクションを送出(dispach)します。
- そのアクションは関連情報のみを含むオブジェクトで、アクションはreducerに送信されます。
- reducerはアプリとアクションの現在のstateを受け取り、新しいstateを返します。
- 新しいstateはUIを管理し、そのstateが変化したときに合わせて、UIも効率的に更新されます。
submitボタンのアクション
まず、次のファイルを作ります。
src/constants/index.js
const types = {
SUBMIT_TODO: 'SUBMIT_TODO',
};export default types;
さらに、アクションのテストを作ります。
src/actions/test.js
/* global expect, it, describe */import actions from '.';
import types from '../constants/';describe('Actions', () => {
const todoText = 'A todo';it('Should create an action to add a todo', () => {
const expectedAction = {
type: types.SUBMIT_TODO,
id: 1,
text: todoText,
};expect(actions.submitTodo(todoText)).toEqual(expectedAction);
});
});
定数expectedActionは、submitTodo関数を呼んだ時に、Appから送られるアクションを定義しています。
実際の値とexpectedActionを比較して、期待通りの機能かどうかのテストになります。
このテストを yarn run test で実行して、エラーが出ないようなindexファイルを書いてきます。
src/actions/index.js
import types from '../constants';let todoId = 0;const nextId = () => {
todoId += 1;
return todoId;
};const actions = {
submitTodo(text) {
return {
type: types.SUBMIT_TODO,
id: nextId(),
// text: text
text,
};
},
};export default actions;
submitボタンのreducer
actionからreducerへとstateが渡されて、reducerはそれを管理(store, and return new one)しています。
まず、reducerのテストを書きます。
src/reducers/test.js
/* global expect, it, describe */import types from '../constants';
import { reducer, initialState } from '.';describe('Reducer', () => {
const todoText = 'A todo';it('Should return the initialState when no action passed', () => {
expect(reducer(undefined, {})).toEqual(initialState);
});describe('submit todo', () => {
it('Should return the correct state', () => {
// set up the action expected to be passed to reducer
const action = {
type: types.SUBMIT_TODO,
id: 1,
text: todoText,
};
// expected state from reducer
const expectedState = {
todos: [
{
id: 1,
text: todoText,
},
],
};expect(reducer(undefined, action)).toEqual(expectedState);
});
});
});
src/reducers/index.js
import types from '../constants/';export const initialState = {
todos: [],
};export const reducer = (state = initialState, action) => {
switch (action.type) {
case types.SUBMIT_TODO:
return {
// return unpacked state, which means all the contents of the state, including todo
...state,
todos: [
...state.todos,
{
id: action.id,
text: action.text,
},
],
};
default:
return state;
}
};export default reducer;
上の処理では、reducerの第二引数actionがSUBMIT_TODOタイプの時に、何をreturnをしているかが定義されています。
...state は第一引数のstateのコンテンツ全てをreturnしています。
todo: [...state.todos, {id: action.id, text: action.text}]
todo配列をunpackedしたものをreturnし、つまりstateの配列全てを展開して、新しい要素(id, text)を持つオブジェクトを加えます。
よって、todo配列に固有のid,textが代入されます。
Redux
まず、reduxのインスールを行いstoreをつくります。
npm install --save redux react-reduxsrc/store.jp
import { combineReducers, createStore } from 'redux';
import todoListApp from './reducers';const reducers = combineReducers({
todoListApp,
});export default createStore(reducers);
reducerを一つにまとめる(combineReducers)ことで、全ての state treeの管理を行っています。
次にindexページをrefactorします。
src/index.js
/* global document */import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';
import registerServiceWorker from './registerServiceWorker';ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);registerServiceWorker();
<Provider> は、storeのchild componentsを全てwrapしています。
src/App.js
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import AddTodo from './components/addTodo/';
import actions from './actions/';export const App = ({ submitTodo }) => (
<div>
<h1>Todo list</h1>
<AddTodo submitTodo={submitTodo} />
</div>
);App.propTypes = {
submitTodo: PropTypes.func.isRequired,
};const mapStateToProps = state => state.todoListApp;const mapDispatchToProps = dispatch => ({
submitTodo: (text) => {
if (text) {
dispatch(actions.submitTodo(text));
}
},
});export default connect(mapStateToProps, mapDispatchToProps)(App);
定数mapStateToPropsは、store内にあるstateを、コンポーネントのpropsとして扱えるようにする関数です。
また、定数mapDispatchToPropsはアクションをpropsとして扱えるようにする関数になります。また、textを引数にとるsubmitTodo関数を入れ、action.submitTodoを発信します。
connectモジュールは、アクションをdispatchしてstateをsubscribeすることで、Appコンポーネントとstateとがつながるような役割を持っています。
参考リンク
- google devTools for React
storeに入っている値を確認したりできます。 - Redux
Redux のドキュメント - Reduxの概要
また、Reduxがどのような流れで処理が行われているのかを、具体例をもって説明された、以下の記事が分かりやすかったです。
