我曾在之前实现过 vue 实现对数据的增删改查(CURD) ,这里我们使用 React 来实现类似的功能。
实现一个 todo list,是最快能熟悉一门语言和框架的。
- 源码的 GitHub 地址:react-todo-list;
- demo 演示地址: codesanbox-demo;
1. 准备工作 #
我们使用官方提供的 create-react-app 来快速创建一个项目。
$ npx create-react-app my-app
$ cd my-app
$ npm start
即使用脚手架创建一个 React 项目。然后进入到项目的目录,并启动。
2. 展示数据 #
我们的 todo list 结构很简单:
- id: 标识当前的数据;
- title: 要展示的文本;
渲染时,以id
作为 key。
const App = () => {
const [list, setList] = useState([
{ id: 1, title: "看电影" },
{ id: 2, title: "学习React的todo list" },
]);
return (
<div className="App">
<div className="list">
{list.map((item) => (
<div key={item.id} className="item">
<input type="checkbox" />
<span>{item.title}</span>
</div>
))}
</div>
</div>
);
};
一个简单的列表就展示出来了。React 中,我们可以使用.map()
来展示数据中的每项数据,注意,这里我们使用了key
属性,这个属性是 React 渲染列表时,用于标识当前列表项的唯一标识。
checkbox
组件是方便我们后续可以标识已完成该项,并进行去除。
3. 新增数据 #
我们使用一个input
组件来输入数据,但在 React 中没有双向绑定
的特性,我们需要通过useState()
来获取输入的值。然后再调用setList()
方法,将输入的数据添加到列表中。
const App = () => {
const [list, setList] = useState([]);
const [value, setValue] = useState("");
// 新增数据
const handleAdd = () => {
if (!value) {
// 输入框没有值时,不进行任何操作
return;
}
/**
* 将输入的数据添加到列表中
* 1. id要保持唯一,这里使用时间戳来作为唯一值;
* 2. 这里使用 concat 方法,将新数据添加到数组的开头,这样新数据会排在最前面
**/
const newList = [{ id: Date.now(), title: value }].concat(list);
setList(newList);
// 添加到列表后,清除输入框中的数据
setValue("");
};
return (
<div className="App">
<div className="editor">
<input
type="text"
value={value}
onInput={(e: any) => setValue(e.target.value)}
/>
<button onClick={handleAdd}>添加</button>
</div>
<div className="list">
{list.map((item) => (
<div key={item.id} className="item">
<input type="checkbox" />
<span>{item.title}</span>
</div>
))}
</div>
</div>
);
};
React 并没有实现 Vue 的双向绑定,需要我们手动实现。
在 React 的 useState()
的 hook 中,我们是不能直接操作list
的,需要通过setList()
来更新数据。先通过concat()
方法把新数据和之前的数据拼接到一起,然后再通过setList()
更新数据。
4. 删除数据 #
我们在 todo list 中添加了一个选中按钮,选中该按钮,表示已完成此项,删除对应的数据。
const App = () => {
// 删除该id的数据
const handleDel = (id: number) => {
// 实际上可以直接删除的,我们做个延时操作,是为了在页面上能看到选中的操作
setTimeout(() => {
setList(list.filter((item) => item.id !== id));
// 另一种删除方式
// const newList = [...list];
// const index = newList.findIndex(item => item.id === data.id);
// newList.splice(index, 1); // 删除该位置的元素
// setList(newList);
}, 300);
};
return (
<div className="App">
<div className="list">
{list.map((item) => (
<div key={item.id} className="item">
<input
className="check"
type="checkbox"
onChange={() => handleDel(item.id)}
/>
<span>{item.title}</span>
</div>
))}
</div>
</div>
);
};
删除列表中的数据有多种方式,但都不要直接在 list 上直接操作:
- 找到该数据所在的下标,使用列表的副本执行
splice(index, 1)
;然后再调用setList()
更新页面; - 如上面样例那样,在列表中过滤掉不需要的数据,
filter()
方法本来就会返回过滤后的新数组;
5. 修改数据 #
修改数据时,我们新建了一个弹窗,在弹窗中输入新的数据,然后再回填到列表中。
// edit-modal.js
import { useEffect, useState } from "react";
import "./edit-modal.css";
const EditModal = ({ open, data, onCancel, onOk }) => {
const [value, setValue] = useState(""); // 与输入框绑定,获取输入框的值
// 监听弹窗是否打开,若打开了弹窗并且有值,就将该数据填充到输入框中
useEffect(() => {
if (open && data) {
setValue(data.title);
}
}, [open, data]);
if (!open) {
// 若 open 为 false,表示不展示该弹窗
return null;
}
// 点击确认按钮,将最新的数据给到列表,这个onOk的具体操作在调用方
const handleClick = () => {
if (value) {
onOk({ ...data, title: value });
}
};
return (
<div className="modal">
<div className="modal-overlay"></div>
<div className="modal-body">
<button className="close" onClick={onCancel}>
x
</button>
<div className="modal-content">
<input
className="input"
placeholder="请输入内容"
value={value}
onInput={(e) => setValue(e.target.value)}
/>
</div>
<div className="btn-group">
<button onClick={onCancel}>取消</button>
<button onClick={handleClick}>确定</button>
</div>
</div>
</div>
);
};
export default EditModal;
我们再看下列表页的操作:
// App.js
const App = () => {
const [list, setList] = useState([
{ id: 1, title: "看电影" },
{ id: 2, title: "学习React的todo list" },
]);
const [openModal, setOpenModal] = useState({ open: false });
/**
* 修改数据
* @param {{ id: number; title: string }} data
*/
const handleEdit = (data) => {
// 循环数组中的数据,若找到id相同的数据,则修改数据,否则不修改
setList(list.map((item) => (item.id === data.id ? data : item)));
};
return (
<div className="App">
<div className="list">
{list.map((item) => (
<div key={item.id} className="item">
<input
className="check"
type="checkbox"
onChange={() => handleDel(item.id)}
/>
<span>{item.title}</span>
<button
className="edit-btn"
onClick={() => setOpenModal({ open: true, data: item })}
>
修改
</button>
</div>
))}
</div>
<EditModal
{...openModal}
onCancel={() => setOpenModal({ open: false })}
onOk={(data) => {
handleEdit(data);
setOpenModal({ open: false });
}}
/>
</div>
);
};
修改数据,我们也不是直接在 list 上修改,而是判断某一项是否是修改项,然后赋予最新的数据。
6. 总结 #
完整的代码,我已放到 github 上: react-todo-list,欢迎自取。
我们在上面的删除和修改操作中,都是以列表项的id
作为标识,去列表中进行匹配。并没有使用下标。
在官方文档中也说明了 key 的必要性:渲染列表。同时,我在之前的文章React18 源码解析之 key 的作用里也分析过不同类型的 key 的区别(如随机数、数组下标、对象 id 等)。