Wenzi

使用 React 实现 todo list 的 curd 操作

蚊子前端博客
发布于 2024/09/06 22:56
项目 todo list 能够帮助我们快速熟悉 React 框架!

我曾在之前实现过 vue 实现对数据的增删改查(CURD) ,这里我们使用 React 来实现类似的功能。

实现一个 todo list,是最快能熟悉一门语言和框架的。

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 上直接操作:

  1. 找到该数据所在的下标,使用列表的副本执行splice(index, 1);然后再调用 setList() 更新页面;
  2. 如上面样例那样,在列表中过滤掉不需要的数据,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 等)。

标签:react
阅读(243)
Simple Empty
No data