Reduxの公式ExampleにあるTodo Listを機能ごとに作っていくシリーズの2回目です。
1回目では、TodoをTodo Listに追加する「Add Todo」を作りました。
今回は、Todoの完了・未完了を切り替える「Toggle Todo」の機能を作っていきます。
1回目を読んでない人は、そちらを先にどうぞ。
Redux ExampleのTodo Listをはじめからていねいに(1)
1. 完了・未完了を表すcompletedによってスタイルを変える
todoにcompleted要素を追加して、とりあえず取り消し線を表示する
まず、todoごとに完了・未完了を区別するために、completedという要素を
加えます。前回つくったtodo reducerを修正します。
todo作成時は、未完了なので、デフォルトでfalseにしておきます。
デフォルトでfalseにするので、actionからなにか受け取る必要はありませんので、
addTodoのactionCreatorは変更ありません。
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
}
default:
return state
}
}
const todos = (state = [], action) => {
// ...
}
export default todos
completedによってviewを変えるので、Todoコンポーネントを修正します。
completedがtrueだったらtextDecorationをline-throughにします。
const Todo = ({ completed, text }) => (
<li style={{textDecoration: completed ? 'line-through' : 'none'}}>
{text}
</li>
)
Todo.propTypes = {
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}
これで、stateで保持されるtodoのcompletedがtrueのとき取り消し線がつきます。
動作確認は、一時的にreducers/todos.jsのcompleted: falseを
trueに変えてやればちゃんと取り消し線が付いているはずです。
actionCreatorからcompleted要素を操作する
次に、action経由で取り消し線のON/OFFを行うために、actionCreatorとreducerの作成を行います。
actionCreatorで必要なのは、todoのidだけです。
export const addTodo = (text) => {
// ...
}
export const toggleTodo = (id) => {
return {
type: 'TOGGLE_TODO',
id
}
}
reducerはtodosとtodoの両方にtoggleTodoのactionが呼び出されたときの処理が必要です。
storeにはtodos reducerが登録されており、todoはtodosが呼び出されているに過ぎません。
todos reducerでは、map関数を使って現在のtodosに格納されているすべてのtodoを
todo reducerに渡しています。
todo reducerでは、actionCreatorに渡したidと一致するtodoに対して、
completedだけを反転させています。
Object.assignで現在のstateと、completedを書き換えたstateを結合しています。
const todo = (state, action) => {
switch (action.type) {
// ...
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state
}
return Object.assign({}, state, {
completed: !state.completed
})
// ...
}
}
const todos = (state = [], action) => {
switch (action.type) {
// ...
case 'TOGGLE_TODO':
return state.map((t) =>
todo(t, action)
)
// ...
}
}
export default todos
index.jsからtoggleTodoを使ってみると、
正しく取り消し線が付いていると思います。
import { addTodo, toggleTodo } from './actions'
store.dispatch(addTodo('Hello React!'))
store.dispatch(toggleTodo(0))
2. クリックしてcompletedの値を変える
それでは、クリックしたときにcompletedの値を変更する処理を書いていきます。
stateをpropsとして使えるようにしたと同じように、dispatchをpropsとして
使えるようにします。
onTodoClickという名前でdispatchをstoreに格納します。
idを渡すと、dispatch(toggleTodo(id))のようにtoggleTodo actionCreatorで
actionをつくって、dispatchによりstoreのstateを変更します。
処理の流れは今までと同じです。
import { toggleTodo } from '../actions'
// ...
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
TodoListコンテナでonTodoClickが使えるようになったので、Todoコンテナを
つくるところで、他のpropsと同じようにonTodoClick(todo.id)も渡します。
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map((todo) =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
TodoList.propTypes = {
// ...
onTodoClick: PropTypes.func.isRequired
}
export default TodoList
TodoコンテナでTodoListから渡されたonClickを使います。
const Todo = ({ onClick, completed, text }) => (
<li
onClick={onClick}
style={{textDecoration: completed ? 'line-through' : 'none'}}
>
{text}
</li>
)
Todo.propTypes = {
onClick: PropTypes.func.isRequired,
// ...
}
export default Todo
これでクリックするとcompletedの値が変更され、取り消し線がON/OFFされます。
「Toggle Todo」機能が完成しました。
ここまでのソースコードはGitHubにあげています。
続きます。。
次回、表示するTodo Listを完了または未完了のTodoだけにする「Filter Todo」機能を実装します。
2016/3/15 update
Redux ExampleのTodo Listをはじめからていねいに(3)を書きました。