Redux TodoList 示例

之前使用 React + Redux 写了一个计算功能,现在再来看一个稍微复杂一点的例子。设想有这样一个待办事项列表,用户可以进行的操作为:

  • 添加待办事项;
  • 更新待办事项为已办事项;
  • 删除待办事项;

接下来的这个例子将完成上面所设定的功能。

step1: 定义 state 结构

1
2
3
4
5
6
7
8
9
var state = {
todos: [
{
text: "",
index: 0,
completed: false
}
]
};

定义 state 的结构如上,所有的事项包含在 state.todos 里面,第一个 todotextindexcompleted 构成。

step2: 定义 action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export const ADD_TODO = "ADD_TODO";
export const TOGGLE_TODO = "TOGGLE_TODO";
export const REMOVE_TODO = "REMOVE_TODO";
export const addTodo = (text) => ({
type: ADD_TODO,
text: text
});
export const toggleTodo = (index) => ({
type: TOGGLE_TODO,
index: index
});
export const removeTodo = (index) => ({
type: REMOVE_TODO,
index: index
});

定义三种 action,分别表示 新增更新删除

step3: 定义 reducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import {ADD_TODO, REMOVE_TODO, TOGGLE_TODO} from './actions.js';
var index = 0;
function todoApp(state = {todos: []}, action) {
switch (action.type) {
case ADD_TODO: // 新增
var newTodo = {
text: action.text + index,
index: index++,
completed: false
};
return Object.assign({}, state, {
todos: [...state.todos, newTodo]
});
break;
case REMOVE_TODO: // 删除
var todos = state.todos.filter((todo)=> {
return (todo.index != action.index);
});
return Object.assign({}, state, {todos: todos});
break;
case TOGGLE_TODO: // 更新状态
var todos = state.todos.map((todo)=> {
if (todo.index == action.index) {
return {
text: todo.text,
index: todo.index,
completed: !todo.completed
}
}
return todo;
});
return Object.assign({}, state, {todos: todos});
break;
default:
return state;
}
}
module.exports = todoApp;
  • ADD_TODO : 新增一个 todo
  • REMOVE_TODO : 删除一个 todo
  • TOGGLE_TODO : 更新 todo 的状态

上面的代码使用了 Object.assign({}, oldObj, newObj) 来更新拷贝一个对象;

step4: 定义组件并渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React from "react";
import ReactDOM from "react-dom";
import {createStore} from 'redux';
import reducer from "./reducer";
import {addTodo, toggleTodo, removeTodo} from "./actions";
import style from "./style.css"
var store = createStore(reducer);
const App = React.createClass({
handleClick: function (e) {
store.dispatch(addTodo('abcde'));
},
render: function () {
const todos = store.getState().todos.map(function (todo) {
var className = todo.completed ? style.completed : style.unCompleted;
return (<li key={todo.index} data={todo.index} onClick={()=> {
store.dispatch(toggleTodo(todo.index));
}} className={className}>
<span>{todo.text}</span>
<span>{todo.completed ? " ok" : " nok"}</span>
<button onClick={()=>{
store.dispatch(removeTodo(todo.index));
}}>Remove
</button>
</li>);
}.bind(this));
return <div>
<h2 onClick={this.handleClick}>AddTodo</h2>
<ul>{todos}</ul>
</div>;
}
});
var render = ()=> {
ReactDOM.render(<App />, document.getElementById("root"));
};
render();
store.subscribe(render);

上面的组件把所有 待办事项 都列出来,以不同的样式区分待办事项是否已经完成,点击按钮可以添加、删除事项,在事项上点击可以切换事项的完成状态。

style.css

1
2
3
4
5
6
7
.unCompleted {
color: #ff6600;
}
.completed {
color: #0c77f8;
}

由于使用到了 style,所以需要在 webpack.config.js 中添加样式处理器 style-loadercss-loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var path = require("path");
module.exports = {
entry: './index.js',
output: {
path: 'public',
filename: 'bundle.js',
publicPath: '/',
chunkFilename: '[name].[chunkhash:5].chunk.js'
},
module: {
loaders: [
{
test: /\.js$/,
exclude: nodemodules,
loader: 'babel-loader?presets[]=es2015&presets[]=react'
},
{
test: /\.css$/,
loader: 'style-loader!css-loader?modules'
}
]
}
}