用redux完成事务清单

今天再来一个例子,我们从组件开始。

App.js

 1 import React, { PropTypes } from 'react'
 2 import { bindActionCreators } from 'redux'
 3 import { connect } from 'react-redux'
 4 import Header from '../components/Header'
 5 import MainSection from '../components/MainSection'
 6 import * as TodoActions from '../actions'
 7 
 8 const App = ({todos, actions}) => (
 9   <div>
10     <Header addTodo={actions.addTodo} /> //头部负责添加事项
11     <MainSection todos={todos} actions={actions} />  //这是2个组件
12   </div>
13 )
14 
15 App.propTypes = {
16   todos: PropTypes.array.isRequired,
17   actions: PropTypes.object.isRequired  //todos是数组  actions是对象
18 }
19 
20 const mapStateToProps = state => ({
21   todos: state.todos
22 })
23 
24 const mapDispatchToProps = dispatch => ({
25     actions: bindActionCreators(TodoActions, dispatch)
26 })
27 
28 export default connect(
29   mapStateToProps,
30   mapDispatchToProps
31 )(App)

head.js  

 1 import React, { PropTypes, Component } from 'react'
 2 import TodoTextInput from './TodoTextInput'
 3 
 4 export default class Header extends Component {  //es6组建写法
 5   static propTypes = {
 6     addTodo: PropTypes.func.isRequired
 7   }
 8 
 9   handleSave = text => {
10     if (text.length !== 0) {
11       this.props.addTodo(text)
12     }
13   }
14 
15   render() {
16     return (
17       <header className="header">
18         <h1>todos</h1>
19         <TodoTextInput newTodo
20                        onSave={this.handleSave} //添加事项
21                        placeholder="What needs to be done?" />
22       </header>
23     )
24   }
25 }
TodoTextInput.js
 1 import React, { Component, PropTypes } from 'react'
 2 import classnames from 'classnames'
 3 
 4 export default class TodoTextInput extends Component {
 5   static propTypes = {
 6     onSave: PropTypes.func.isRequired,
 7     text: PropTypes.string,
 8     placeholder: PropTypes.string,
 9     editing: PropTypes.bool,
10     newTodo: PropTypes.bool
11   }
12 
13   state = {
14     text: this.props.text || ''
15   }
16 
17   handleSubmit = e => {
18     const text = e.target.value.trim()
19     if (e.which === 13) {
20       this.props.onSave(text)  //保存文本值
21       if (this.props.newTodo) {
22         this.setState({ text: '' })
23       }
24     }
25   }
26 
27   handleChange = e => {
28     this.setState({ text: e.target.value })
29   }
30 
31   handleBlur = e => {
32     if (!this.props.newTodo) {
33       this.props.onSave(e.target.value)
34     }
35   }
36 
37   render() {
38     return (
39       <input className={
40         classnames({
41           edit: this.props.editing,
42           'new-todo': this.props.newTodo
43         })}
44         type="text"
45         placeholder={this.props.placeholder} //what needs to be done?
46         autoFocus="true"
47         value={this.state.text}
48         onBlur={this.handleBlur}
49         onChange={this.handleChange}  //改变state里面的值
50         onKeyDown={this.handleSubmit} /> //keycode=13 提交保存
51     )
52   }
53 }

mainSection.js

 1 import React, { Component, PropTypes } from 'react'
 2 import TodoItem from './TodoItem'
 3 import Footer from './Footer'
 4 import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters' //过滤器
 5 
 6 const TODO_FILTERS = {
 7   [SHOW_ALL]: () => true,
 8   [SHOW_ACTIVE]: todo => !todo.completed,
 9   [SHOW_COMPLETED]: todo => todo.completed
10 }
11 
12 export default class MainSection extends Component {
13   static propTypes = {
14     todos: PropTypes.array.isRequired,
15     actions: PropTypes.object.isRequired
16   }
17 
18   state = { filter: SHOW_ALL }
19 
20   handleClearCompleted = () => {
21     this.props.actions.clearCompleted() //清除完成事项
22   }
23 
24   handleShow = filter => {
25     this.setState({ filter })  //根据过滤条件展示
26   }
27 
28   renderToggleAll(completedCount) {
29     const { todos, actions } = this.props
30     if (todos.length > 0) {
31       return (
32         <input className="toggle-all"
33                type="checkbox"
34                checked={completedCount === todos.length} //是否全部完成
35                onChange={actions.completeAll} />
36       )
37     }
38   }
39 
40   renderFooter(completedCount) {
41     const { todos } = this.props
42     const { filter } = this.state
43     const activeCount = todos.length - completedCount
44 
45     if (todos.length) {
46       return (
47         <Footer completedCount={completedCount}
48                 activeCount={activeCount}
49                 filter={filter}
50                 onClearCompleted={this.handleClearCompleted.bind(this)}
51                 onShow={this.handleShow.bind(this)} />
52       )
53     }
54   }
55 
56   render() {
57     const { todos, actions } = this.props
58     const { filter } = this.state
59 
60     const filteredTodos = todos.filter(TODO_FILTERS[filter])  //初始值为showall 展示全部
61     const completedCount = todos.reduce((count, todo) =>   //统计已完成事项
62       todo.completed ? count + 1 : count,
63       0
64     )
65 
66     return (
67       <section className="main">
68         {this.renderToggleAll(completedCount)}
69         <ul className="todo-list">
70           {filteredTodos.map(todo =>
71             <TodoItem key={todo.id} todo={todo} {...actions} />
72           )}
73         </ul>
74         {this.renderFooter(completedCount)}
75       </section>
76     )
77   }
78 }

todoItems.js

 1 import React, { Component, PropTypes } from 'react'
 2 import classnames from 'classnames'
 3 import TodoTextInput from './TodoTextInput'
 4 
 5 export default class TodoItem extends Component {
 6   static propTypes = {
 7     todo: PropTypes.object.isRequired,
 8     editTodo: PropTypes.func.isRequired,
 9     deleteTodo: PropTypes.func.isRequired,
10     completeTodo: PropTypes.func.isRequired
11   }
12 
13   state = {
14     editing: false
15   }
16 
17   handleDoubleClick = () => {
18     this.setState({ editing: true })
19   }
20 
21   handleSave = (id, text) => {
22     if (text.length === 0) {
23       this.props.deleteTodo(id)
24     } else {
25       this.props.editTodo(id, text)   //编辑  不存在text则删除该项
26     }
27     this.setState({ editing: false })
28   }
29 
30   render() {
31     const { todo, completeTodo, deleteTodo } = this.props
32 
33     let element
34     if (this.state.editing) {
35       element = (
36         <TodoTextInput text={todo.text}
37                        editing={this.state.editing}
38                        onSave={(text) => this.handleSave(todo.id, text)} />
39       )
40     } else {
41       element = (
42         <div className="view">
43           <input className="toggle"
44                  type="checkbox"
45                  checked={todo.completed}
46                  onChange={() => completeTodo(todo.id)} />
47           <label onDoubleClick={this.handleDoubleClick}>  //双击修改为可编辑状态   会重新渲染
48             {todo.text}
49           </label>
50           <button className="destroy"
51                   onClick={() => deleteTodo(todo.id)} />
52         </div>
53       )
54     }
55 
56     return (
57       <li className={classnames({
58         completed: todo.completed,
59         editing: this.state.editing
60       })}>
61         {element}
62       </li>
63     )
64   }
65 }

footer.js

 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 export default class Footer extends Component {
12   static propTypes = {
13     completedCount: PropTypes.number.isRequired,
14     activeCount: PropTypes.number.isRequired,
15     filter: PropTypes.string.isRequired,
16     onClearCompleted: PropTypes.func.isRequired,
17     onShow: PropTypes.func.isRequired
18   }
19 
20   renderTodoCount() {
21     const { activeCount } = this.props
22     const itemWord = activeCount === 1 ? 'item' : 'items'  //此处语法经典
23 
24     return (
25       <span className="todo-count">
26         <strong>{activeCount || 'No'}</strong> {itemWord} left  //有多少未完成事项
27       </span>
28     )
29   }
30 
31   renderFilterLink(filter) {
32     const title = FILTER_TITLES[filter]
33     const { filter: selectedFilter, onShow } = this.props
34 
35     return (
36       <a className={classnames({ selected: filter === selectedFilter })}
37          style={{ cursor: 'pointer' }}
38          onClick={() => onShow(filter)}>
39         {title}
40       </a>
41     )
42   }
43 
44   renderClearButton() {
45     const { completedCount, onClearCompleted } = this.props
46     if (completedCount > 0) {
47       return (
48         <button className="clear-completed"
49                 onClick={onClearCompleted} >
50           Clear completed
51         </button>
52       )
53     }
54   }
55 
56   render() {
57     return (
58       <footer className="footer">
59         {this.renderTodoCount()}
60         <ul className="filters">
61           {[ SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED ].map(filter =>
62             <li key={filter}>
63               {this.renderFilterLink(filter)}   //切换不同的过滤条件
64             </li>
65           )}
66         </ul>
67         {this.renderClearButton()}
68       </footer>
69     )
70   }
71 }

好了  这就是组件  比上次有点多  不过当成是训练    最重要的是把握整体结构

indes.js

1 import * as types from '../constants/ActionTypes'
2 
3 export const addTodo = text => ({ type: types.ADD_TODO, text })
4 export const deleteTodo = id => ({ type: types.DELETE_TODO, id })
5 export const editTodo = (id, text) => ({ type: types.EDIT_TODO, id, text })  //这一看就是dispatch的内容
6 export const completeTodo = id => ({ type: types.COMPLETE_TODO, id })
7 export const completeAll = () => ({ type: types.COMPLETE_ALL })
8 export const clearCompleted = () => ({ type: types.CLEAR_COMPLETED })

todo.js

 1 import { ADD_TODO, DELETE_TODO, EDIT_TODO, COMPLETE_TODO, COMPLETE_ALL, CLEAR_COMPLETED } from '../constants/ActionTypes'
 2 
 3 const initialState = [
 4   {
 5     text: 'Use Redux',
 6     completed: false,
 7     id: 0               //初始化state
 8   }
 9 ]
10 
11 export default function todos(state = initialState, action) {
12   switch (action.type) {
13     case ADD_TODO:
14       return [
15         {
16           id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, //确定id值
17           completed: false,
18           text: action.text
19         },
20         ...state
21       ]
22 
23     case DELETE_TODO:
24       return state.filter(todo =>
25         todo.id !== action.id
26       )
27 
28     case EDIT_TODO:
29       return state.map(todo =>
30         todo.id === action.id?
31         (...todo,{text:action.text}):
32          todo
33       )
34 
35     case COMPLETE_TODO:
36       return state.map(todo =>
37         todo.id === action.id ?
38           { ...todo, completed: !todo.completed } : //用于切换
39           todo
40       )
41 
42     case COMPLETE_ALL:
43       const areAllMarked = state.every(todo => todo.completed) //全部完成 或全部未完成
44       return state.map(todo => ({
45         ...todo,
46         completed: !areAllMarked
47       }))
48 
49     case CLEAR_COMPLETED:
50       return state.filter(todo => todo.completed === false)
51 
52     default:
53       return state
54   }
55 }

index.js  整合reducers

1 import { combineReducers } from 'redux'
2 import todos from './todos'
3 
4 const rootReducer = combineReducers({
5   todos
6 })
7 
8 export default rootReducer

好了  代码就这么多  这一次reducers倒不是很复杂  反倒组件有点乱   总之是单向数据流动,根据action的类型做出相应的改变,最后重新render。。。

 
posted @ 2016-10-16 23:19  coder_231  阅读(443)  评论(0编辑  收藏  举报