todoList_2-逻辑实现
逻辑实现
一、渲染 todo 列表
-
App.js 初始化状态, 把状态传给 List
export default class App extends Component { //初始化状态 state = { todos: [ { id: "001", name: "吃饭", done: true }, { id: "002", name: "睡觉", done: true }, { id: "003", name: "打代码", done: false }, { id: "004", name: "逛街", done: false }, ], }; render() { const { todos } = this.state; ... <List todos={todos} /> } } -
List 接受传参, 并传递给 Item
import PropTypes from "prop-types"; export default class List extends Component { //对接收的props进行:类型、必要性的限制 static propTypes = { todos: PropTypes.array.isRequired, }; render() { const { todos } = this.props; return ( <div className="list"> {todos.map((todo) => { return <Item key={todo.id} {...todo} />; })} </div> ); } } -
Item 渲染数据
export default class Item extends Component { render() { const { id, name, done } = this.props; return ( <> <li> <label style={{ display: "flex" }}> <input type="checkbox" style={{ verticalAlign: "middle", marginTop: 0 }} /> <span>{name}</span> </label> <button style={{ display: "none" }}>删除</button> </li> </> ); } } -
如图

二、实现添加 todo 对象
-
App.js 将添加 todo 的方法传递给 Header, 并处理 Header 添加的 todo 对象
export default class App extends Component { // addTodo用于添加一个todo, 接收的参数是todo对象 addTodo = (todoObj) => { // 获取原todos const { todos } = this.state; // 追加一个todo const newTodos = [todoObj, ...todos]; // 更新状态 this.setState({ todos: newTodos }); }; render() { ... <Header addTodo={this.addTodo} />; } } -
Header 接受方法参数添加业务逻辑,并传回要添加的 todo 对象
import PropTypes from "prop-types"; // 生成随机ID, 需要提前 `npm add nanoid` import { nanoid } from "nanoid"; export default class Header extends Component { // 对接收的props进行:类型、必要性的限制 static propTypes = { addTodo: PropTypes.func.isRequired, }; // 键盘事件的回调 handleKeyUp = (event) => { // 解构赋值获取keyCode,target const { keyCode, target } = event; // 判断是否是回车按键 if (keyCode !== 13) return; // 添加的todo名字不能为空 if (target.value.trim() === "") { alert("输入不能为空"); return; } // 准备好一个todo对象 const todoObj = { id: nanoid(), name: target.value, done: false }; // 将todoObj传递给App this.props.addTodo(todoObj); // 清空输入 target.value = ""; }; render() { return ( <> <input className="header-input" onKeyUp={this.handleKeyUp} type="text" placeholder="请输入任务名称, 摁回车键确认" /> </> ); } }
三、实现删除 tudo 对象
-
App.js 添加删除 todo 方法, 传递给 List, 并处理 List 回传的 id,根据 id 删除
export default class App extends Component { //deleteTodo用于删除一个todo对象 deleteTodo = (id) => { //获取原来的todos const { todos } = this.state; //删除指定id的todo对象 const newTodos = todos.filter((todoObj) => { return todoObj.id !== id; }); //更新状态 this.setState({ todos: newTodos }); }; render() { return ( ... <List todos={todos} deleteTodo={this.deleteTodo} /> ); } } -
List 接受传参, 并直接发送给 Item
export default class List extends Component { //对接收的props进行:类型、必要性的限制 static propTypes = { ... deleteTodo: PropTypes.func.isRequired, }; render() { const { todos, deleteTodo } = this.props; return ( <div className="list"> {todos.map((todo) => { return <Item key={todo.id} {...todo} deleteTodo={deleteTodo} />; })} </div> ); } } -
Item 处理业务逻辑, 并回传任务 id
export default class Item extends Component { state = { mouse: false }; // 标识鼠标移入、移出 // 鼠标移入、移出的回调 handleMouse = (flag) => { return () => { this.setState({ mouse: flag }); }; }; // 删除一个todo的回调 handleDelete = (id) => { if (window.confirm("确定删除吗?")) { this.props.deleteTodo(id); } }; render() { const { id, name, done } = this.props; const { mouse } = this.state; return ( <> <li style={{ backgroundColor: mouse ? "#ddd" : "white" }} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)} > <label style={{ display: "flex" }}> <input type="checkbox" style={{ verticalAlign: "middle", marginTop: 0 }} /> <span>{name}</span> </label> <button style={{ display: mouse ? "block" : "none" }} onClick={() => this.handleDelete(id)} > 删除 </button> </li> </> ); } }
四、实现 checkbox 更新 tudo 对象
-
App.js 实现更新方法, 并传递给 List
export default class App extends Component { // updateTodo用于更新一个todo对象 updateTodo = (id, done) => { // 获取状态中的todos const { todos } = this.state; // 匹配处理数据 const newTodos = todos.map((todoObj) => { if (todoObj.id === id) return { ...todoObj, done }; else return todoObj; }); this.setState({ todos: newTodos }); }; render() { const { todos } = this.state; return ( ... <List todos={todos} deleteTodo={this.deleteTodo} updateTodo={this.updateTodo} /> ); } } -
List 传递给 Item 处理
export default class List extends Component { static propTypes = { ... updateTodo: PropTypes.func.isRequired, }; render() { const { todos, deleteTodo, updateTodo } = this.props; return ( ... <Item key={todo.id} {...todo} deleteTodo={deleteTodo} updateTodo={updateTodo} /> ); } } -
Item 处理并回传 item 的 id 和选中的状态
export default class Item extends Component { // 勾选、取消勾选某一个todo的回调 handleCheck = (id) => { return (event) => { this.props.updateTodo(id, event.target.checked); }; }; render() { const { id, name, done } = this.props; const { mouse } = this.state; return ( ... <input type="checkbox" style={{ verticalAlign: "middle", marginTop: 0 }} checked={done} onChange={this.handleCheck(id)} /> ); } }
五、实现 Footer 对 todo 全选
-
App.js 将 todo 列表传给 Footer
export default class App extends Component { render() { const { todos } = this.state; return ( ... <Footer todos={todos}/> ); } } -
Footer 实时渲染
export default class Footer extends Component { render() { const { todos } = this.props; // 已完成的个数 const doneCount = todos.reduce( (pre, todo) => pre + (todo.done ? 1 : 0), 0 ); // 总数 const total = todos.length; return ( <div className="footer"> <label> <input type="checkbox" style={{ verticalAlign: "middle", marginTop: 0 }} checked={doneCount === total && total !== 0 ? true : false} /> </label> <span> <span>已完成{doneCount}</span> / 全部{total} </span> <button>清除已完成任务</button> </div> ); } } -
App.js 将全部选定/取消的方法传给 Footer
export default class App extends Component { // checkAllTodo用于全选 checkAllTodo = (done) => { // 获取原来的todos const { todos } = this.state; // 加工数据 const newTodos = todos.map((todoObj) => { return { ...todoObj, done }; }); // 更新状态 this.setState({ todos: newTodos }); }; render() { ... return ( ... <Footer todos={todos} checkAllTodo={this.checkAllTodo} /> ); } } -
Footer 实现逻辑, 并回传
export default class Footer extends Component { // 全选checkbox的回调 handleCheckAll = (event) => { this.props.checkAllTodo(event.target.checked); }; render() { ... <input type="checkbox" style={{ verticalAlign: "middle", marginTop: 0 }} checked={doneCount === total && total !== 0 ? true : false} onChange={this.handleCheckAll} />; } }
六、实现 Footer 对 todo 选中删除
-
App.js 删除选中的方法
export default class App extends Component { // clearAllDone用于清除所有已完成的 clearAllDone = () => { // 获取原来的todos const { todos } = this.state; // 过滤数据 const newTodos = todos.filter((todoObj) => { return !todoObj.done; }); // 更新状态 this.setState({ todos: newTodos }); }; render() { <Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone} />; } } -
Footer 触发方法即可
export default class Footer extends Component { // 清除已完成任务的回调 handleClearAllDone = () => { this.props.clearAllDone(); }; render() { ... <button onClick={this.handleClearAllDone}>清除已完成任务</button>; } }