组件拆分

  • src/components/Header.js ```jsx import React, { Component } from ‘react’

class Header extends Component { render() { return (

todos

) } }

export default Header

  1. - src/components/Main.js
  2. ```jsx
  3. import React, { Component } from 'react'
  4. class Main extends Component {
  5. render() {
  6. return (
  7. <section className="main">
  8. <input className="toggle-all" type="checkbox" />
  9. <ul className="todo-list">
  10. <li className="completed">
  11. <div className="view">
  12. <input className="toggle" type="checkbox"/>
  13. <label>品味JavaScript</label>
  14. <button className="destroy"></button>
  15. </div>
  16. <input className="edit" />
  17. </li>
  18. <li>
  19. <div className="view">
  20. <input className="toggle" type="checkbox" />
  21. <label>买一个宠物</label>
  22. <button className="destroy"></button>
  23. </div>
  24. <input className="edit" />
  25. </li>
  26. </ul>
  27. </section>
  28. )
  29. }
  30. }
  31. export default Main
  • src/components/Footer.js ```jsx import React, { Component } from “react”;

class Footer extends Component { render() { return (

0 item left
  • All
  • Active
  • Completed
); } }

export default Footer;

  1. - src/App.js
  2. ```jsx
  3. import React, { Component } from 'react'
  4. import Header from './components/Header'
  5. import Main from './components/Main'
  6. import Footer from './components/Footer'
  7. class App extends Component {
  8. render() {
  9. return (
  10. <div className="todoapp">
  11. <Header />
  12. <Main />
  13. <Footer />
  14. </div>
  15. )
  16. }
  17. }
  18. export default App
  • src/index.js ```jsx import React from ‘react’; import ReactDOM from ‘react-dom’; import App from ‘./App’ import ‘./index.css’

ReactDOM.render( , document.getElementById(‘root’) );

  1. <a name="FAp1B"></a>
  2. ## 任务列表展示
  3. - src/store/actions/todo.actions.js
  4. ```javascript
  5. /**
  6. * 01 发送请求获取数据 load_todo
  7. * 02 当异步操作完成之后还需要触发新的指令 load_todo_success
  8. */
  9. import { createAction } from 'redux-actions'
  10. export const load_todo = createAction('load_todo')
  11. export const load_todo_success = createAction('load_todo_success')
  • src/store/reducers/todo.reducer.js ```javascript /**
    • 接收指令后对数据进行相应处理 */ import { handleActions as createReducer } from ‘redux-actions’ import {load_todo_success } from ‘../actions/todo.actions’

const initialState = { todos: [] }

export default createReducer({ [load_todo_success]: (state, action) => ({ todos: action.payload }) }, initialState)

  1. - src/store/reducers/root.reducer.js
  2. ```javascript
  3. import { combineReducers } from 'redux'
  4. import todoReducer from './todo.reducer'
  5. export default combineReducers({
  6. todoReducer: todoReducer
  7. })
  • src/store/saga/todo.sags.js ```javascript /**
    • 01 完成异步操作
    • 02 重新发送新的指令 */ import axios from ‘axios’ import { takeEvery, put } from ‘redux-saga/effects’ import { load_todo_success, load_todo } from ‘../actions/todo.actions’

// 实现 load_todo_data 获取数据同时传递新指令 function * load_todo_data() { let todoData = yield axios.get(‘http://localhost:3005/api/todos').then(res => res.data) console.log(todoData) yield put(load_todo_success(todoData)) }

export default function * todoSaga() { yield takeEvery(load_todo, load_todo_data) }

  1. - src/store/index.js
  2. ```javascript
  3. /**
  4. * 当前模块完成 store 的创建 + 中间件注册
  5. */
  6. import { createStore, applyMiddleware } from 'redux'
  7. import rootReducer from './reducers/root.reducer'
  8. import createSagaMiddleware from 'redux-saga'
  9. import todoSaga from './saga/todo.saga'
  10. const sagaMiddleware = createSagaMiddleware()
  11. const store = createStore(rootReducer, applyMiddleware(sagaMiddleware))
  12. sagaMiddleware.run(todoSaga)
  13. export default store
  • src/index.js ```jsx

import store from ‘./store’ import {Provider} from ‘react-redux’

ReactDOM.render( , document.getElementById(‘root’) );

  1. - src/components/Main.js
  2. ```jsx
  3. import React, { Component } from 'react'
  4. import {connect} from 'react-redux'
  5. import { bindActionCreators} from 'redux'
  6. import * as todoActions from '../store/actions/todo.actions'
  7. class Main extends Component {
  8. componentDidMount() {
  9. // 调用具体的指令函数,触发数据获取操作
  10. this.props.load_todo()
  11. }
  12. render() {
  13. console.log(this.props)
  14. return (
  15. <section className="main">
  16. <input className="toggle-all" type="checkbox" />
  17. <ul className="todo-list">
  18. {
  19. this.props.todos.map(item => (
  20. <li key={item.id}>
  21. <div className="view">
  22. <input className="toggle" type="checkbox" />
  23. <label>{item.taskName}</label>
  24. <button className="destroy"></button>
  25. </div>
  26. <input className="edit" />
  27. </li>
  28. ))
  29. }
  30. </ul>
  31. </section>
  32. )
  33. }
  34. }
  35. // 获取 store 当中数据
  36. const mapStateToProps = (state) => ({
  37. todos: state.todoReducer.todos
  38. })
  39. // 处理 dispatch
  40. const mapDispatchToProps = (dispatch) => ({
  41. ...bindActionCreators(todoActions, dispatch)
  42. })
  43. export default connect(mapStateToProps, mapDispatchToProps)(Main)

实现添加任务

  • src/store/actions/todo.actions.js

    1. // 新增 todo 列表项的相关指令
    2. export const add_todo = createAction('add_todo')
    3. export const add_todo_success = createAction('add_todo_success')
  • src/store/reducers/todo.reducer.js ```javascript

import { add_todo_success } from ‘../actions/todo.actions’

export default createReducer({

[add_todo_success]: (state, action) => ({ todos: [ …state.todos, action.payload ] }) }, initialState)

  1. - src/store/saga/todo.saga.js
  2. ```javascript
  3. import { add_todo_success, add_todo } from '../actions/todo.actions'
  4. // 实现 add_todo_data 方法
  5. function * add_todo_data (action) {
  6. // 完成异步操作
  7. let taskInfo = yield axios.post('http://localhost:3005/api/todos', {taskName: action.payload}).then(res => res.data)
  8. // 重新发送指令
  9. yield put(add_todo_success(taskInfo.task))
  10. }
  11. export default function * todoSaga() {
  12. yield takeEvery(add_todo, add_todo_data)
  13. }
  • src/components/Header.js ```jsx import React, { Component } from ‘react’ import {connect} from ‘react-redux’ import {bindActionCreators} from ‘redux’ import * as todoActions from ‘../store/actions/todo.actions’

class Header extends Component {

addTodo = (ev) => { if(ev.keyCode === 13) { // 获取当前输入框里的内容 let taskName = ev.target.value if(taskName.trim().length === 0){ alert(‘请输入任务内容’) return }

  1. // 触发新增任务的指令
  2. this.props.add_todo(taskName)
  3. // 清空文本框
  4. ev.target.value = ''
  5. }

} render() { return (

Todos

) } }

// 组件与store const mapStateToProps = (state) => ({ todos: state.todoReducer.todos })

// 组件与dispatch const mapDispatchToProps = (dispatch) => ({ …bindActionCreators(todoActions, dispatch) })

export default connect(mapStateToProps, mapDispatchToProps)(Header)

  1. <a name="EeMMW"></a>
  2. ## 删除任务
  3. - src/store/actions/todo.actions.js
  4. ```javascript
  5. // 删除指令
  6. export const remove_todo = createAction('remove_todo')
  7. export const remove_todo_success = createAction('remove_todo_success')
  • src/store/reducers/todo.reducer.js ```javascript

import { remove_todo_success } from ‘../actions/todo.actions’

export default createReducer({

[remove_todo_success]: (state, action) => { // 需要获取被删除项的 id let id = action.payload

  1. let index = state.todos.findIndex(todo => todo.id === id)
  2. let todos = JSON.parse(JSON.stringify(state.todos))
  3. todos.splice(index, 1)
  4. return { todos }

} }, initialState)

  1. - src/store/saga/todo.saga.js
  2. ```javascript
  3. import { remove_todo_success, remove_todo } from '../actions/todo.actions'
  4. // 实现 remove_todo_data
  5. function * remove_todo_data(action) {
  6. // 将 id 利用相应的接口传递给后端执行具体的删除操作
  7. let res = yield axios.delete('http://localhost:3005/api/todos', {
  8. params: {
  9. id: action.payload
  10. }
  11. }).then(res => res.data)
  12. // 重新发送指令
  13. yield put(remove_todo_success(res.tasks.id))
  14. }
  15. export default function * todoSaga() {
  16. yield takeEvery(remove_todo, remove_todo_data)
  17. }
  • src/components/Main.js ```jsx

class Main extends Component {

removeTask(id){ if(window.confirm(‘确定删除当前任务’)){ // 触发删除操作 this.props.remove_todo(id) } }

render() { console.log(this.props) return (

    { this.props.todos.map(item => (
  • )) }
) } }

  1. <a name="o9Mc0"></a>
  2. ## 修改任务状态
  3. - src/store/actions/todo.actions.js
  4. ```javascript
  5. // 修改任务状态
  6. export const modify_todo = createAction('modify_todo')
  7. export const modify_todo_success = createAction('modify_todo_success')
  • src/store/reducers/todo.reducer.js ```javascript import { modify_todo_success } from ‘../actions/todo.actions’

export default createReducer({

[modify_todo_success]: (state, action) => { let params = action.payload

  1. let index = state.todos.findIndex(todo => todo.id === params.id)
  2. let todos = JSON.parse(JSON.stringify(state.todos))
  3. todos[index].isCompleted = params.isCompleted
  4. return { todos }

} }, initialState)

  1. - src/store/saga/todo.saga.js
  2. ```javascript
  3. import { modify_todo_success, modify_todo } from '../actions/todo.actions'
  4. // 实现 modify_todo
  5. function * modify_todo_data(action) {
  6. let params = action.payload
  7. yield axios.put('http://localhost:3005/api/todos/isCompleted', params).then(res => res.data)
  8. yield put(modify_todo_success(params))
  9. }
  10. export default function * todoSaga() {
  11. yield takeEvery(modify_todo, modify_todo_data)
  12. }
  • src/components/Main.js ```jsx

class Main extends Component {

render() { console.log(this.props) return (

    { this.props.todos.map(item => (
  • {this.props.modify_todo({id: item.id, isCompleted: ev.target.checked})}}/>
  • )) }
) } }

  1. <a name="dRCoL"></a>
  2. ## 计算未完成功能
  3. - src/components/Footer.js
  4. ```jsx
  5. import React, { Component } from "react";
  6. import { connect } from 'react-redux'
  7. import { bindActionCreators } from 'redux'
  8. import * as todoActions from '../store/actions/todo.actions'
  9. class Footer extends Component {
  10. render() {
  11. // 此时我们就可以过虑出未选中的任务,然后统计它们的数据,做为具体的值使用
  12. let taskLen = this.props.todos.filter(todo => !todo.isCompleted).length
  13. return (
  14. <footer className="footer">
  15. <span className="todo-count">
  16. <strong>{taskLen}</strong> item left
  17. </span>
  18. <ul className="filters">
  19. <li>
  20. <span>All</span>
  21. </li>
  22. <li>
  23. <span>Active</span>
  24. </li>
  25. <li>
  26. <span>Completed</span>
  27. </li>
  28. </ul>
  29. <button className="clear-completed">Clear completed</button>
  30. </footer>
  31. );
  32. }
  33. }
  34. // 获取 store 当中数据
  35. const mapStateToProps = (state) => ({
  36. todos: state.todoReducer.todos
  37. })
  38. // 处理 dispatch 函数
  39. const mapDispatchToProps = (dispatch) => ({
  40. ...bindActionCreators(todoActions, dispatch)
  41. })
  42. export default connect(mapStateToProps, mapDispatchToProps)(Footer)

任务筛选功能

  • src/store/actions/todo.actions.js

    1. // 任务筛选
    2. export const modify_todo_filter = createAction('modify_todo_filter')
  • src/store/reducers/todo.reducer.js ```javascript /**

    • 接收指令后对数据进行相应处理 */ import { handleActions as createReducer } from ‘redux-actions’ import {load_todo_success, add_todo_success, remove_todo_success, modify_todo_success, modify_todo_filter } from ‘../actions/todo.actions’

const initialState = { todos: [], filter: ‘all’ }

export default createReducer({ [load_todo_success]: (state, action) => ({ …state, todos: action.payload }),

[add_todo_success]: (state, action) => ({ …state, todos: [ …state.todos, action.payload ] }),

[remove_todo_success]: (state, action) => { // 需要获取被删除项的 id let id = action.payload

  1. let index = state.todos.findIndex(todo => todo.id === id)
  2. let todos = JSON.parse(JSON.stringify(state.todos))
  3. todos.splice(index, 1)
  4. return { ...state, todos }

},

[modify_todo_success]: (state, action) => { let params = action.payload let index = state.todos.findIndex(todo => todo.id === params.id) let todos = JSON.parse(JSON.stringify(state.todos)) todos[index].isCompleted = params.isCompleted return {…state, todos } },

[modify_todo_filter]: (state, action) => { return { …state, filter: action.payload } } }, initialState)

  1. - src/components/Footer.js
  2. ```jsx
  3. <ul className="filters">
  4. <li>
  5. <span onClick={() => {this.props.modify_todo_filter('all')}}>All</span>
  6. </li>
  7. <li>
  8. <span onClick={() => {this.props.modify_todo_filter('active')}}>Active</span>
  9. </li>
  10. <li>
  11. <span onClick={() => {this.props.modify_todo_filter('completed')}}>Completed</span>
  12. </li>
  13. </ul>
  • src/components/Main.js ```jsx // 依据 filter 筛选出需要展示的数据 function filterTodos(todos, filter) { switch(filter) { case ‘all’:
    1. return todos
    case ‘active’:
    1. return todos.filter(todo => !todo.isCompleted)
    case ‘completed’:
    1. return todos.filter(todo => todo.isCompleted)
    } }

// 获取 store 当中数据 const mapStateToProps = (state) => ({ todos: filterTodos(state.todoReducer.todos, state.todoReducer.filter) })

  1. <a name="uu8lf"></a>
  2. ## 删除已完成任务
  3. - src/store/actions/todo.actions.js
  4. ```javascript
  5. // 删除已完成任务
  6. export const clear_todo_completed = createAction('clear_todo_completed')
  7. export const clear_todo_completed_success = createAction('clear_todo_completed_success')
  • src/store/reducers/todo.reducer.js ```javascript

import { clear_todo_completed_success } from ‘../actions/todo.actions’

export default createReducer({

[clear_todo_completed_success]: (state, action) => { let todos = JSON.parse(JSON.stringify(state.todos)) todos = todos.filter(todo => !todo.isCompleted) return { …state, todos } } }, initialState)

  1. - src/store/saga/todo.saga.js
  2. ```javascript
  3. import { clear_todo_completed_success, clear_todo_completed } from '../actions/todo.actions'
  4. // 实现 clear_todo_data
  5. function * clear_todo_data() {
  6. // 发送删除请求
  7. yield axios.delete('http://localhost:3005/api/todos/clearCompleted')
  8. yield put(clear_todo_completed_success())
  9. }
  10. export default function * todoSaga() {
  11. yield takeEvery(clear_todo_completed, clear_todo_data)
  12. }
  • src/components/Footer.js ```jsx

class Footer extends Component {

clear_todo = () => { if(window.confirm(‘是否删除已完成任务’)) { this.props.clear_todo_completed() } } render() {

  1. return (
  2. <footer className="footer">
  3. <button className="clear-completed" onClick={this.clear_todo}>Clear completed</button>
  4. </footer>
  5. );

} }

  1. <a name="oXNnL"></a>
  2. ## 修改任务名称(一)
  3. - src/store/actions/todo.actions.js
  4. ```javascript
  5. // 任务名称修改
  6. export const modify_todo_edit = createAction('modify_todo_edit')
  7. export const modify_todo_edit_success = createAction('modify_todo_edit_success')
  • src/store/reducers/todo.reducer.js ```javascript import { modify_todo_edit_success } from ‘../actions/todo.actions’

export default createReducer({

[modify_todo_edit_success]: (state, action) => { // 利用 id 找到需要被操作的任务项,设置 isEditing 显示具体的样式 let todos = JSON.parse(JSON.stringify(state.todos)) let index = state.todos.findIndex(todo => todo.id === action.payload.id) todos[index].isEditing = action.payload.isEditing return { …state, todos } } }, initialState)

  1. - src/store/saga/todo.saga.js
  2. ```javascript
  3. import { modify_todo_edit_success, modify_todo_edit } from '../actions/todo.actions'
  4. // 实现 modify_todo_edit_data
  5. function * modify_todo_edit_data (action) {
  6. yield axios.put('http://localhost:3005/api/todos/isEditing', action.payload)
  7. yield put(modify_todo_edit_success(action.payload))
  8. }
  9. export default function * todoSaga() {
  10. yield takeEvery(modify_todo_edit, modify_todo_edit_data)
  11. }
  • src/components/Main.js
    1. this.props.todos.map(item => {
    2. let classes = []
    3. if (item.isCompleted) classes.push('completed')
    4. if (item.isEditing) classes.push('editing')
    5. return (
    6. <li key={item.id} className={classes.join(' ')}>
    7. <div className="view">
    8. <input
    9. className="toggle"
    10. type="checkbox"
    11. defaultChecked={item.isCompleted}
    12. onChange={(ev) => {this.props.modify_todo({id: item.id, isCompleted: ev.target.checked})}} />
    13. <label
    14. onDoubleClick = {() => {this.props.modify_todo_edit({id: item.id, isEditing: true})}}
    15. >{item.taskName}</label>
    16. <button className="destroy" onClick={() => {this.removeTask(item.id)}}></button>
    17. </div>
    18. <input defaultValue={item.taskName} className="edit" />
    19. </li>
    20. )})

修改任务名称(二)

  • src/store/actions/todo.actions.js

    1. // 任务名称修改确认
    2. export const modify_todo_name = createAction('modify_todo_name')
    3. export const modify_todo_name_success = createAction('modify_todo_name_success')
  • src/store/reducers/todo.reducer.js ```javascript import { modify_todo_name_success } from ‘../actions/todo.actions’

export default createReducer({

[modify_todo_name_success]: (state, action) => { let todos = JSON.parse(JSON.stringify(state.todos)) let index = state.todos.findIndex(todo => todo.id === action.payload.id) todos[index].taskName = action.payload.taskName return { …state, todos } } }, initialState)

  1. - src/store/saga/todo.saga.js
  2. ```javascript
  3. import { modify_todo_name_success, modify_todo_name } from '../actions/todo.actions'
  4. // 实现 modify_todo_name_data
  5. function * modify_todo_name_data(action) {
  6. yield axios.put('http://localhost:3005/api/todos', action.payload)
  7. yield put(modify_todo_name_success(action.payload))
  8. }
  9. export default function * todoSaga() {
  10. yield takeEvery(modify_todo_name, modify_todo_name_data)
  11. }
  • src/components/Main.js ```jsx class Main extends Component {

modify_name(id, ev) { // 切换状态 this.props.modify_todo_edit({id: id, isEditing: false}) // 修改数据 this.props.modify_todo_name({id: id, taskName: ev.target.value}) }

render() { return (

    { this.props.todos.map(item => { let classes = [] if (item.isCompleted) classes.push(‘completed’) if (item.isEditing) classes.push(‘editing’) return (
  • {this.props.modify_todo({id: item.id, isCompleted: ev.target.checked})}} />