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-redux

src/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とがつながるような役割を持っています。


参考リンク

また、Reduxがどのような流れで処理が行われているのかを、具体例をもって説明された、以下の記事が分かりやすかったです。

Like what you read? Give Tuyoshi Akiyama a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.