文章目录
- 目标实现效果
- 实现思路
- 实现步骤
- 第一步:定义更改 Todo 状态的方法,以供调用
- 第二步:App 组件传递更改 Todo 状态的方法给子组件 List
- 第三步:List 组件传递更改 Todo 状态的方法给子组件 Item
- 第四步:Item 调用更改 Todo 状态的方法,更新状态
- 完整代码
- App 组件完整代码
- List 组件完整代码
- Item 组件完整代码
本文实现点击 Item 组件的复选框 checkbox 时,切换复选框的选中状态,同时更新 TodoList 在组件状态 State 中的数据状态。以此来模拟实现 TodoList 的真实业务逻辑,即选中表示完成某条代办事项,取消表示未完成。
当然真实项目中,除了更改组件状态 State,同时还需要通过请求服务端的 API 接口,将数据保存到数据库中。
目标实现效果
实现思路
通过上文的介绍,要涉及到组件之间的通信,由于存在用户操作触发组件状态数据的更新,所以实现思路上和添加一条 Todo 类似。但是添加 Todo 只是 App 组件和 Header 组件两级之间的通信逻辑,而 Item 组件和 App 组件没有直接的关系,无法直接通信,所以需要通过 List 组件来作为通信的桥梁。虽然是三级,但实现本质都是一样的。
实现步骤
第一步:定义更改 Todo 状态的方法,以供调用
先引入一个概念:
状态在哪里,操作(更改)状态的方法就在哪里。
通过 TodoList 来解释上面的概念,通俗的讲,我们要修改组件状态中的 TodoList 的数据,而 TodoList 定义在 App 组件的状态中,那么我们就要在 App 组件中定义更改 Todo 的方法。可以回头想想,添加 Todo 代码是不是这样的呢?
代码片段如下:
// file: src/App.js
/**
* App 组件
*/
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) => {
// TODO .....
};
// 用于更新一个 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 });
};
render() {
// TODO .....
}
}
第二步:App 组件传递更改 Todo 状态的方法给子组件 List
App 组件通过 props 将更改 TodoList 状态的方法传给子组件 List。
代码片段如下:
// file: src/App.js
// 给 List 组件添加一个 props 属性,值为更改 Todo 状态回调方法
<List todoList={todoList} updateTodo={this.updateTodo} />
第三步:List 组件传递更改 Todo 状态的方法给子组件 Item
List 组件通过 props 接收到更改 TodoList 状态的方法,并将该方法传给子组件 Item。
代码片段如下:
// file: src/components/List/index.jsx
// 在 List 组件的 render() 方法中,从 接收获取到更新状态的方法
const { todoList, updateTodo } = this.props;
// 给 Item 组件添加一个 props 属性,值为从 props 接收到的更改 Todo 状态方法
<Item key={todo.id} {...todo} updateTodo={updateTodo} />;
第四步:Item 调用更改 Todo 状态的方法,更新状态
Item 组件通过 props 接收到更改 TodoList 状态的方法,当鼠标点击 Item 组件时,通过 onChange 事件进行监听,在监听回调中获取当前复选框的选中状态,也就是当前操作的 Item 组件实例的状态,然后调用从 props 接收到的更改 Todo 状态的方法,类似冒泡的形式,最终由 App 组件中的对应方法执行从而更改 TodoList 的状态。
代码片段如下:
// file: src/components/Item/index.jsx
// 定义勾选、取消勾选某一个 Todo 的回调方法
handleCheck = (id) => {
// 此处一定要使用高阶函数,为什么?请阅读本专栏《受控组件和非受控组件》章节相关内容内容
return (event) => {
/**
* 注意两点:
* 1. 需要通过 event.target 的 checked 属性来获取复选框是否选中的状态值,而不是 value;
* 2. 直接通过 this.props 调用方法
*/
this.props.updateTodo(id, event.target.checked);
};
};
// 给 checkbox 绑定 onChange 事件
<input type="checkbox" defaultChecked={done} onChange={this.handleCheck(id)} />;
至此我们就完成了更改 Item 的状态。
完整代码
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 });
};
render() {
const { todoList } = this.state;
return (
<div className="todo-container">
<div className="todo-wrap">
<Header addTodo={this.addTodo} />
<List todoList={todoList} updateTodo={this.updateTodo} />
<Footer />
</div>
</div>
);
}
}
List 组件完整代码
// file: src/components/List/index.jsx
import React, { Component } from "react";
import Item from "../Item";
import "./index.css";
export default class List extends Component {
render() {
const { todoList, updateTodo } = this.props;
return (
<ul className="todo-main">
{todoList.map((todo) => {
return <Item key={todo.id} {...todo} updateTodo={updateTodo} />;
})}
</ul>
);
}
}
Item 组件完整代码
// file: src/components/Item/index.jsx
import React, { Component } from "react";
import "./index.css";
export default class Item extends Component {
state = { mouse: false }; //标识鼠标移入、移出
// 鼠标移入、移出的回调
handleMouse = (flag) => {
return () => {
this.setState({ mouse: flag });
};
};
// 勾选、取消勾选某一个 Todo 的回调
handleCheck = (id) => {
return (event) => {
this.props.updateTodo(id, event.target.checked);
};
};
render() {
const { id, name, done } = this.props;
const { mouse } = this.state;
return (
<li
style={{ backgroundColor: mouse ? "#eee" : "#fff" }}
onMouseEnter={this.handleMouse(true)}
onMouseLeave={this.handleMouse(false)}
>
<label className="container">
<input
type="checkbox"
defaultChecked={done}
onChange={this.handleCheck(id)}
/>
<span>{name}</span>
</label>
<button
className="btn btn-danger btn-sm"
style={{ display: mouse ? "block" : "none" }}
>
删除
</button>
</li>
);
}
}