文章目录
- 目标实现效果
- 实现已完成和全部数量统计和显示
- 实现全选和全不选
- 实现清除已完成功能
- 完整代码
- App 组件完整代码
- Footer 组件完整代码
 
通过前面的章节已经完成 TodoList 的增删改的功能,本文我们来实现底部相关功能:
- 已完成和全部数量实时统计,在增加、勾选取消、删除时,已完成和全部数量实时更新。
- 底部的 checkbox 实现全选和全不选,并与列表的 checkbox 联动;
- 实现清除已完成功能。
目标实现效果

实现已完成和全部数量统计和显示
已完成和全部数量的统计是基于 TodoList 数据的,所以需要通过 props 把 TodoList 的数据传递给 Footer 组件。
- 首先,从 App 组件中将 Todolist 数据通过 props 传递给 Footer 组件,代码片段如下:
// file: src/App.js
<Footer todoList={todoList} />
- 然后,在 Footer 组件的 render() 中通过 prosps 接收到 TooList 列表数据,进行计算统计数据和渲染,代码片段如下:
// file: src/components/Footer/index.
// 以下代码在 render 方法中实现
// 从 props 获取 todoList
const { todoList } = this.props;
// 已完成数量
const doneCount = todoList.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);
// 全部数量
const total = todoList.length;
// DOM渲染
<span>
  <span>已完成 {doneCount}</span> / 全部 {total}
</span>;
此时,界面上已经可以正确显示已完成(也就是已勾选)和全部数量(也就是 TodoList 的长度),并且更改列表中每条 Todo 的 checkbox 的勾选状态时,统计数据也会同时更新。
实现全选和全不选
底部组件还有一个 checkbox,用以控制全选和全不选,选中为全选,取消选中为全不选。
 这个 checkbox 本身只是一个操作元素,不需要表示自己状态的 state,它的状态依赖于 TodoList 列表中每个 Item 的状态。而它本身勾选和不勾选,又会修改 TodoList 列表中每个 Item 的状态。
- 首先,需要在 App.js 中定义处理全选和全不选的处理回调方法,并且通过 props 传递给 Footer 组件,代码片段如下:
// file: src/App.js
// 用于处理全选和全不选,参数 done 为 true 表示全选,否则 false 表示全不选
checkAllTodo = (done) => {
  // 获取原来的 todoList
  const { todoList } = this.state;
  // 加工数据
  const newTodoList = todoList.map((todoObj) => {
    return { ...todoObj, done };
  });
  // 更新状态
  this.setState({ todoList: newTodoList });
};
// 将上面定义的方法传递给 Footer 组件
<Footer todoList={todoList} checkAllTodo={this.checkAllTodo} />;
- 然后,在 Footer 组件中接收全选和全不选的处理方法,并通过 checkbox 的 onChange 事件回调中调用,代码片段如下:
// file: src/components/Footer/index.jsx
// 定义 checkbox 的onChange 事件回调,用来处理全选和全不选
handleCheckAll = (event) => {
  this.props.checkAllTodo(event.target.checked);
};
// 给 checkBox 添加 onChange 事件,并动态控制其勾选状态
<input
  type="checkbox"
  checked={doneCount === total && total !== 0 ? true : false}
  onChange={this.handleCheckAll}
/>;
实现清除已完成功能
清除已完成的原理就是,把 TodoList 中已完成的数据删除掉。
- 首先,需要在 App.js 中定义处理清除已完成的处理方法,并且通过 props 传递给 Footer 组件,代码片段如下:
// file: src/App.js
// 清除已完成
clearDone = () => {
  const { todoList } = this.state;
  // 过滤数据
  const newTodoList = todoList.filter((todoObj) => {
    return !todoObj.done;
  });
  this.setState({ todoList: newTodoList });
};
// 将上面定义的方法传递给 Footer 组件
<Footer
  todoList={todoList}
  checkAllTodo={this.checkAllTodo}
  clearDone={this.clearDone}
/>;
- 然后,在 Footer 组件中接收清除已完成的处理方法,并在 button 的 onClick 事件回调中调用,代码片段如下:
// file: src/components/Footer/index.jsx
// 定义清除所有已完成的回调方法
handleClearAllDone = () => {
  this.props.clearDone();
};
// 给 button 添加 onClick 事件
<button onClick={this.handleClearAllDone} className="btn btn-danger">
  清除已完成任务
</button>;
至此,完成了清除已完成功能。
完整代码
App 组件完整代码
// file: src/App.js
// 创建“外壳”组件
import React, { Component } from "react";
import Header from "./components/Header";
import List from "./components/List";
import Footer from "./components/Footer";
import "./App.css";
export default class App extends Component {
  // 总结:状态在哪里,操作状态的方法就在哪里。
  // 初始化状态
  state = {
    todoList: [
      { id: 1, name: "参加晨会", done: true },
      { id: 2, name: "A功能开发", done: true },
      { id: 3, name: "B功能开发", done: false },
    ],
  };
  /**
   * addTodo 用于添加一条 Todo 记录,接收的参数是 Todo 对象
   */
  addTodo = (todoObj) => {
    // 获取原 TodoList
    const { todoList } = this.state;
    // 追加一条 Todo
    const newTodoList = [todoObj, ...todoList];
    // 更新状态
    this.setState({ todoList: newTodoList });
  };
  // 用于更新一个 Todo 对象
  updateTodo = (id, done) => {
    // 获取状态的中 todoList
    const { todoList } = this.state;
    // 匹配处理数据
    const newTodoList = todoList.map((todoObj) => {
      if (todoObj.id === id) return { ...todoObj, done };
      else return todoObj;
    });
    // 更新状态
    this.setState({ todoList: newTodoList });
  };
  // 用于删除一条 Todo
  deleteTodo = (id) => {
    // 获取原来的 todoList
    const { todoList } = this.state;
    // 删除指定 id 的 todo 对象
    const newTodoList = todoList.filter((todoObj) => {
      return todoObj.id !== id;
    });
    // 更新状态
    this.setState({ todoList: newTodoList });
  };
  // 用于处理全选和全不选,参数 done 为 true 表示全选,否则 false 表示全不选
  checkAllTodo = (done) => {
    // 获取原来的 todoList
    const { todoList } = this.state;
    // 加工数据
    const newTodoList = todoList.map((todoObj) => {
      return { ...todoObj, done };
    });
    // 更新状态
    this.setState({ todoList: newTodoList });
  };
  // 清除已完成
  clearDone = () => {
    const { todoList } = this.state;
    // 过滤数据
    const newTodoList = todoList.filter((todoObj) => {
      return !todoObj.done;
    });
    this.setState({ todoList: newTodoList });
  };
  render() {
    const { todoList } = this.state;
    return (
      <div className="todo-container">
        <div className="todo-wrap">
          <Header addTodo={this.addTodo} />
          <List
            todoList={todoList}
            updateTodo={this.updateTodo}
            deleteTodo={this.deleteTodo}
          />
          <Footer
            todoList={todoList}
            checkAllTodo={this.checkAllTodo}
            clearDone={this.clearDone}
          />
        </div>
      </div>
    );
  }
}
Footer 组件完整代码
// file: src/components/Footer/index.jsx
import React, { Component } from "react";
import "./index.css";
export default class Footer extends Component {
  // 全选和全不选的回调
  handleCheckAll = (event) => {
    this.props.checkAllTodo(event.target.checked);
  };
  // 定义清除所有已完成的回调方法
  handleClearAllDone = () => {
    this.props.clearDone();
  };
  render() {
    const { todoList } = this.props;
    // 已完成数量
    const doneCount = todoList.reduce(
      (pre, todo) => pre + (todo.done ? 1 : 0),
      0
    );
    // 全部
    const total = todoList.length;
    return (
      <div className="todo-footer">
        <div>
          <label>
            <input
              type="checkbox"
              checked={doneCount === total && total !== 0 ? true : false}
              onChange={this.handleCheckAll}
            />
          </label>
          <span>
            <span>已完成 {doneCount}</span> / 全部 {total}
          </span>
        </div>
        <div>
          <button onClick={this.handleClearAllDone} className="btn btn-danger">
            清除已完成任务
          </button>
        </div>
      </div>
    );
  }
}





![[ MySQL ] 使用 MySQL Workbentch 进行MySQL数据库备份 / 还原(Part 3:备份.sql文件方式)](https://img-blog.csdnimg.cn/a6c9c2a48c47486f9a9d82320a3cbc04.png)


![基础算法系列之基础(二)[大数问题]](https://img-blog.csdnimg.cn/d36f91d237aa4f9891da47bb5836a58d.png)










