用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。。。