Skip to content

Commit 22decdf

Browse files
committed
redux todo app added
1 parent 05bf150 commit 22decdf

File tree

18 files changed

+1263
-0
lines changed

18 files changed

+1263
-0
lines changed

Diff for: Section-08-React Redux/React Redux App/README.md

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Simple TODO with React and Redux
2+
3+
## Action
4+
5+
Redux 里的 action 本质上是 JavaScript 的普通对象,这个普通对象肯定会有一个 `type` 属性,常常是一个字符串常量,还可能会有其他的属性就是这个 action 要传入的参数
6+
7+
```javascript
8+
// 没有参数的 action
9+
export function completeAll() {
10+
return {
11+
type: 'COMPLETE_ALL'
12+
};
13+
}
14+
15+
// 有参数的 action
16+
export function addTodo(text) {
17+
return {
18+
type: 'ADD_TODO',
19+
text
20+
};
21+
}
22+
```
23+
24+
当应用规模越来越大时,建议使用单独的模块或文件来存放 action 的类型,然后我们就可以在 action 文件中引入这些类型来使用
25+
26+
```javascript
27+
// constants/ActionTypes.js
28+
29+
export const ADD_TODO = 'ADD_TODO';
30+
export const DELETE_TODO = 'DELETE_TODO';
31+
// ...
32+
33+
// actions/todos.js
34+
35+
import * as types from '../constants/ActionTypes';
36+
// ...
37+
```
38+
39+
action 创建函数的结果传给 `dispatch()` 方法即可实例化 dispatch,但我们一般用 `bindActionCreators()` 自动把多个 action 创建函数绑定到 `dispatch()` 方法上。
40+
41+
## Reducer
42+
43+
action 只是描述了有事情发生了这一事实,并没有指明应用如何更新 state。而这正是 reducer 要做的事情。
44+
45+
reducer 其实很简单,接收旧的 state 和 action,返回新的 state
46+
47+
```javascript
48+
(previousState, action) => newState
49+
```
50+
51+
保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作
52+
53+
- 修改传入参数
54+
- 执行有副作用的操作,如 API 请求和路由跳转
55+
56+
要做到不修改传入的参数,其实一般来说
57+
58+
- 对于数组,我们使用 ES6 的 Spread Operator
59+
60+
```javascript
61+
let myStuff = [
62+
{name: 'Jason'}
63+
];
64+
65+
return [{ name: 'Liao' }, ...myStuff];
66+
```
67+
68+
- 对于对象,我们使用 ES6 的 `Object.assign()`
69+
70+
```javascript
71+
let myStuff = {
72+
name: 'Jason',
73+
age: 19
74+
};
75+
76+
return Object.assign({}, myStuff, { name: 'Liao', age: 21 });
77+
```
78+
79+
最后,Redux 提供了 `combineReducers()` 来把我们所有 reducers 合并起来,虽然现在只有一个
80+
81+
```javascript
82+
import { combineReducers } from 'redux';
83+
import todos from './todos';
84+
85+
const rootReducer = combineReducers({
86+
todos
87+
});
88+
89+
export default rootReducer;
90+
```
91+
92+
## Store
93+
94+
我们学会了使用 action 来描述发生了什么,和使用 reducers 来根据 action 更新 state 的用法。Store 就是把它们联系到一起的对象,Store 有以下职责
95+
96+
- 维持应用的 state
97+
- 提供 `getState()` 方法获取 state
98+
- 提供 `dispatch(action)` 方法更新 state
99+
- 通过 `subscribe(listener)` 注册监听器
100+
101+
再次强调一下 Redux 应用只有一个单一的 store。当需要拆分处理数据的逻辑时,使用 reducer 组合 而不是创建多个 store
102+
103+
我们可以很简单的根据我们的 reducer 来创建 store
104+
105+
```javascript
106+
import { createStore } from 'redux';
107+
import rootReducer from '../reducers';
108+
109+
export default store = createStore(rootReducer);
110+
```
111+
112+
`createStore()` 的第二个参数可以设置初始状态。 这对开发同构应用时非常有用,可以用于把服务器端生成的 state 转变后在浏览器端传给应用。
113+
114+
所以我们会在我们的 store 里创建一个函数,用来创建一个可能会有初始状态的 store
115+
116+
```javascript
117+
import { createStore } from 'redux';
118+
import rootReducer from '../reducers';
119+
120+
export default function configureStore(initialState) {
121+
const store = createStore(rootReducer, initialState);
122+
123+
return store;
124+
}
125+
```
126+
127+
## *Provider*
128+
129+
`<Provider store>` 使组件层级中的 `connect()` 方法都能够获得 Redux store
130+
131+
## *connect*
132+
133+
连接操作不会改变原来的组件类,反而返回一个新的已与 Redux store 连接的组件类。参数是函数
134+
135+
如果什么参数都不加,就会默认只注入 `dispatch`
136+
137+
```javascript
138+
export default connect()(TodoApp);
139+
```
140+
141+
注入 `dispatch` 和全局 `state` (DON'T DO THAT)
142+
143+
```javascript
144+
export default connect(state => state)(TodoApp);
145+
```
146+
147+
注入 `dispatch``todos`
148+
149+
```javascript
150+
function mapStateToProps(state) {
151+
return { todos: state.todos };
152+
}
153+
154+
export default connect(mapStateToProps)(TodoApp);
155+
```
156+
157+
要注意的是,`mapStateToProps()` 里的 `state`,是 `combineReducers()` 里的对象属性,而 `mapStateToProps()` 里返回的 `todos` 就会成为组件里的 `props`,所以组件里的 `this.props` 就会有 `dispatch``todos` 对象
158+
159+
[read more](http://camsong.github.io/redux-in-chinese/docs/react-redux/api.html)
160+

Diff for: Section-08-React Redux/React Redux App/index.html

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Redux TodoMVC example</title>
5+
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
6+
</head>
7+
<body>
8+
<div class="todoapp" id="root">
9+
</div>
10+
<script src="bundle.js"></script>
11+
</body>
12+
</html>

Diff for: Section-08-React Redux/React Redux App/index.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react';
2+
import { Provider } from 'react-redux';
3+
import App from './javascripts/containers/App';
4+
import configureStore from './javascripts/store/configureStore';
5+
6+
const store = configureStore();
7+
8+
React.render(
9+
<Provider store={store}>
10+
{() => <App />}
11+
</Provider>,
12+
document.getElementById('root')
13+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import * as types from '../constants/ActionTypes';
2+
3+
export function addTodo(text) {
4+
return {
5+
type: types.ADD_TODO,
6+
text
7+
};
8+
}
9+
10+
export function deleteTodo(id) {
11+
return {
12+
type: types.DELETE_TODO,
13+
id
14+
};
15+
}
16+
17+
export function editTodo(id, text) {
18+
return {
19+
type: types.EDIT_TODO,
20+
id,
21+
text
22+
};
23+
}
24+
25+
export function completeTodo(id) {
26+
return {
27+
type: types.COMPLETE_TODO,
28+
id
29+
};
30+
}
31+
32+
export function completeAll() {
33+
return {
34+
type: types.COMPLETE_ALL
35+
};
36+
}
37+
38+
export function clearCompleted() {
39+
return {
40+
type: types.CLEAR_COMPLETED
41+
};
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React, { PropTypes, Component } from 'react';
2+
import classnames from 'classnames';
3+
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters';
4+
5+
const FILTER_TITLES = {
6+
[SHOW_ALL]: 'All',
7+
[SHOW_ACTIVE]: 'Active',
8+
[SHOW_COMPLETED]: 'Completed'
9+
};
10+
11+
class Footer extends Component {
12+
renderTodoCount() {
13+
const { activeCount } = this.props;
14+
const itemWord = activeCount === 1 ? 'item' : 'items';
15+
16+
return (
17+
<span className="todo-count">
18+
<strong>{activeCount || 'No'}</strong> {itemWord} left
19+
</span>
20+
);
21+
}
22+
23+
renderFilterLink(filter) {
24+
const title = FILTER_TITLES[filter];
25+
const { filter: selectedFilter, onShow } = this.props;
26+
27+
return (
28+
<a className={classnames({ selected: filter === selectedFilter })}
29+
style={{ cursor: 'pointer' }}
30+
onClick={() => onShow(filter)}>
31+
{title}
32+
</a>
33+
);
34+
}
35+
36+
renderClearButton() {
37+
const { completedCount, onClearCompleted } = this.props;
38+
if (completedCount > 0) {
39+
return (
40+
<button className="clear-completed"
41+
onClick={onClearCompleted} >
42+
Clear completed
43+
</button>
44+
);
45+
}
46+
}
47+
48+
render() {
49+
return (
50+
<footer className="footer">
51+
{this.renderTodoCount()}
52+
<ul className="filters">
53+
{[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map(filter =>
54+
<li key={filter}>
55+
{this.renderFilterLink(filter)}
56+
</li>
57+
)}
58+
</ul>
59+
{this.renderClearButton()}
60+
</footer>
61+
);
62+
}
63+
}
64+
65+
Footer.propTypes = {
66+
completedCount: PropTypes.number.isRequired,
67+
activeCount: PropTypes.number.isRequired,
68+
filter: PropTypes.string.isRequired,
69+
onClearCompleted: PropTypes.func.isRequired,
70+
onShow: PropTypes.func.isRequired
71+
};
72+
73+
export default Footer;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React, { PropTypes, Component } from 'react';
2+
import TodoTextInput from './TodoTextInput';
3+
4+
class Header extends Component {
5+
handleSave (text) {
6+
if (text.length !== 0) {
7+
this.props.addTodo(text);
8+
}
9+
}
10+
render () {
11+
return (
12+
<header className="header">
13+
<h1>todos< /h1>
14+
<TodoTextInput newTodo onSave={this.handleSave.bind(this)} placeholder="What needs to be done?" />
15+
</header>
16+
);
17+
}
18+
}
19+
20+
Header.propTypes = {
21+
addTodo: PropTypes.func.isRequired
22+
};
23+
24+
export default Header;

0 commit comments

Comments
 (0)