文章目录
- 目标实现效果
- 实现已完成和全部数量统计和显示
- 实现全选和全不选
- 实现清除已完成功能
- 完整代码
- 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>
);
}
}