todoMvc 例子
一、reducer
combineReducers
const rootReducer = combineReducers({
todos
})
初始化:
const initialState = [
{
text: 'Use Redux',
completed: false,
id: 0
}
]
添加记录
- 计算ID: maxId + 1
- 设置值
case ADD_TODO:
return [
...state,
{
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
completed: false,
text: action.text
}
]
根据id 删除
case DELETE_TODO:
return state.filter(todo =>
todo.id !== action.id
)
修改text
case EDIT_TODO:
return state.map(todo =>
todo.id === action.id ?
{ ...todo, text: action.text } :
todo
)
设置状态
case COMPLETE_TODO:
return state.map(todo =>
todo.id === action.id ?
{ ...todo, completed: !todo.completed } :
todo
)
设置全选
case COMPLETE_ALL:
const areAllMarked = state.every(todo => todo.completed)
return state.map(todo => ({
...todo,
completed: !areAllMarked
}))
清除状态是 completed 的记录
case CLEAR_COMPLETED:
return state.filter(todo => todo.completed === false)
二、设置常量
ActionTypes.js
export const ADD_TODO = 'ADD_TODO'
export const DELETE_TODO = 'DELETE_TODO'
export const EDIT_TODO = 'EDIT_TODO'
export const COMPLETE_TODO = 'COMPLETE_TODO'
export const COMPLETE_ALL = 'COMPLETE_ALL'
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
TodoFilters.js
export const SHOW_ALL = 'show_all'
export const SHOW_COMPLETED = 'show_completed'
export const SHOW_ACTIVE = 'show_active'
三、设置Action
import * as types from '../constants/ActionTypes'
export const addTodo = text => ({ type: types.ADD_TODO, text })
export const deleteTodo = id => ({ type: types.DELETE_TODO, id })
export const editTodo = (id, text) => ({ type: types.EDIT_TODO, id, text })
export const completeTodo = id => ({ type: types.COMPLETE_TODO, id })
export const completeAll = () => ({ type: types.COMPLETE_ALL })
export const clearCompleted = () => ({ type: types.CLEAR_COMPLETED })
设置comtainers
mapStateToProps
mapDispatchToProps
const App = ({todos, actions}) => (
<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>
)
const mapStateToProps = state => ({
todos: state.todos
})
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(TodoActions, dispatch)
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
四、component
Header.js
- handleSave 保存 dispatch(action)
export default class Header extends Component {
handleSave = text => {
if (text.length !== 0) {
this.props.addTodo(text)
}
}
render() {
return (
<header className="header">
<h1>todos</h1>
<TodoTextInput newTodo
onSave={this.handleSave}
placeholder="What needs to be done?" />
</header>
)
}
}
TodoTextInput
- state 设置初始状态
- handleSubmit 提交 dispatch(action)
- handleChange handleBlur 组件内部事件,处理组件内部input 值改变后更新内部组件的state,重新渲染
export default class TodoTextInput extends Component {
state = {
text: this.props.text || ''
}
handleSubmit = e => {
const text = e.target.value.trim()
if (e.which === 13) {
this.props.onSave(text)
if (this.props.newTodo) {
this.setState({ text: '' })
}
}
}
handleChange = e => {
this.setState({ text: e.target.value })
}
handleBlur = e => {
if (!this.props.newTodo) {
this.props.onSave(e.target.value)
}
}
render() {
return (
<input className={
classnames({
edit: this.props.editing,
'new-todo': this.props.newTodo
})}
type="text"
placeholder={this.props.placeholder}
autoFocus="true"
value={this.state.text}
onBlur={this.handleBlur}
onChange={this.handleChange}
onKeyDown={this.handleSubmit} />
)
}
}
五、MainSection 列表
- 全选
- 列表 全部、未完成、已完成
- 列表底部:未完成数量。 切换列表查询状态
- state 初始状态:显示全部记录
- handleClearCompleted 清除已经完成的记录,dispatch actions
- handleShow 根据状态改变列表展示,内部事件。
- renderToggleAll全选
- renderFooter 列表底部
const TODO_FILTERS = {
[SHOW_ALL]: () => true,
[SHOW_ACTIVE]: todo => !todo.completed,
[SHOW_COMPLETED]: todo => todo.completed
}
export default class MainSection extends Component {
state = { filter: SHOW_ALL }
handleClearCompleted = () => {
this.props.actions.clearCompleted()
}
handleShow = filter => {
this.setState({ filter })
}
renderToggleAll(completedCount) {
const { todos, actions } = this.props
if (todos.length > 0) {
return (
<span>
<input className="toggle-all"
type="checkbox"
checked={completedCount === todos.length}
/>
<label onClick={actions.completeAll}/>
</span>
)
}
}
renderFooter(completedCount) {
const { todos } = this.props
const { filter } = this.state
const activeCount = todos.length - completedCount
if (todos.length) {
return (
<Footer completedCount={completedCount}
activeCount={activeCount}
filter={filter}
onClearCompleted={this.handleClearCompleted}
onShow={this.handleShow} />
)
}
}
render() {
const { todos, actions } = this.props
const { filter } = this.state
const filteredTodos = todos.filter(TODO_FILTERS[filter])
const completedCount = todos.reduce((count, todo) =>
todo.completed ? count + 1 : count,
0
)
return (
<section className="main">
{this.renderToggleAll(completedCount)}
<ul className="todo-list">
{filteredTodos.map(todo =>
<TodoItem key={todo.id} todo={todo} {...actions} />
)}
</ul>
{this.renderFooter(completedCount)}
</section>
)
}
}
六、TodoItem 列表项
- state 初始状态,不能编辑
- handleDoubleClick 双击后,可编辑
- handleSave 编辑状态下,修改 dispatch action
- render 渲染,根据editing 是否可编辑渲染不同的组件
export default class TodoItem extends Component {
state = {
editing: false
}
handleDoubleClick = () => {
this.setState({ editing: true })
}
handleSave = (id, text) => {
if (text.length === 0) {
this.props.deleteTodo(id)
} else {
this.props.editTodo(id, text)
}
this.setState({ editing: false })
}
render() {
const { todo, completeTodo, deleteTodo } = this.props
let element
if (this.state.editing) {
element = (
<TodoTextInput text={todo.text}
editing={this.state.editing}
onSave={(text) => this.handleSave(todo.id, text)} />
)
} else {
element = (
<div className="view">
<input className="toggle"
type="checkbox"
checked={todo.completed}
onChange={() => completeTodo(todo.id)} />
<label onDoubleClick={this.handleDoubleClick}>
{todo.text}
</label>
<button className="destroy"
onClick={() => deleteTodo(todo.id)} />
</div>
)
}
return (
<li className={classnames({
completed: todo.completed,
editing: this.state.editing
})}>
{element}
</li>
)
}
}
七、Footer底部
- renderTodoCount 显示未处理条数
- renderFilterLink 状态 button 可切换状态: ALL、Active、Completed
- renderClearButton 清除 Completed 状态的数据,dispatch action
const FILTER_TITLES = {
[SHOW_ALL]: 'All',
[SHOW_ACTIVE]: 'Active',
[SHOW_COMPLETED]: 'Completed'
}
export default class Footer extends Component {
renderTodoCount() {
const { activeCount } = this.props
const itemWord = activeCount === 1 ? 'item' : 'items'
return (
<span className="todo-count">
<strong>{activeCount || 'No'}</strong> {itemWord} left
</span>
)
}
renderFilterLink(filter) {
const title = FILTER_TITLES[filter]
const { filter: selectedFilter, onShow } = this.props
return (
<a className={classnames({ selected: filter === selectedFilter })}
style={{ cursor: 'pointer' }}
onClick={() => onShow(filter)}>
{title}
</a>
)
}
renderClearButton() {
const { completedCount, onClearCompleted } = this.props
if (completedCount > 0) {
return (
<button className="clear-completed"
onClick={onClearCompleted} >
Clear completed
</button>
)
}
}
render() {
return (
<footer className="footer">
{this.renderTodoCount()}
<ul className="filters">
{[ SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED ].map(filter =>
<li key={filter}>
{this.renderFilterLink(filter)}
</li>
)}
</ul>
{this.renderClearButton()}
</footer>
)
}
}