React(5-1):Todo-List案例

ObjectKaz Lv4

介绍

这里将结合之前所学内容,制作一个 To-Do List。

开始

需求分析

在做项目之前,我们需要思考一下,我们这个项目需要实现的功能:

  • 添加一个 To-Do
  • 删除一个 To-Do
  • 在 To-Do 之前添加复选框,显示已完成
  • 对所有 To-Do 进行统计

创建项目

使用脚手架快速创建一个项目:

1
create-react-app todo-list

创建好之后,我们可以运行康康效果:

1
2
cd todo-list
npm start

项目架构

创建完项目后,我们可以康康项目架构。

下面对整个项目的文件和目录做了一些解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
todo-list
├─.gitignore git忽略文件
├─package.json 项目信息
├─README.md 项目介绍
├─src 源码文件
| ├─App.css App 组件样式
| ├─App.js App组件
| ├─App.test.js App组件测试
| ├─index.css 全局样式
| ├─index.js 入口文件
| ├─logo.svg LOGO文件
| ├─reportWebVitals.js 页面性能分析文件
| └setupTests.js 组件单元测试文件
├─public 静态资源文件
| ├─favicon.ico 网站页面标签图标
| ├─index.html 主页面
| ├─logo192.png LOGO
| ├─logo512.png LOGO
| ├─manifest.json 应用配置
| └robots.txt 爬虫协议文件

安装 UI 框架

这里我们选用 Ant Design 作为这个项目的 UI 框架。

安装 antd

1
npm install antd --save

安装好之后,我们需要在 App.css 第一行插入:

1
@import "~antd/dist/antd.css";

现在,Ant Design 已经安装完成,我们来测试一下 Ant Design 是否安装成功。

App.js 引入 antd

src/App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import logo from './logo.svg';
import {
Button
} from 'antd';
import './App.css';

function App() {
return (

<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
<Button type="primary">Button</Button>
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}

export default App;

接下来打开浏览器,如果在网页中间能够看到一个完整的按钮,那么 antd 就安装成功了:

完成基本布局

对于我们最终需要实现的效果,需要一个页头、添加 TODO、TODO 列表、TODO 统计 四个部分。

我们先把原有的 App.css 的内容清空,只保留引入 antd 的一行:

1
@import "~antd/dist/antd.css";

接下来,我们修改 App.css 添加新的页面样式:

App.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@import "~antd/dist/antd.css";

.site-layout-content {
min-height: 280px;
padding: 24px;
background: #fff;
}

.logo {
float: left;
width: 120px;
height: 31px;
color: #fff;
font-size: 20px;
}

同时修改 App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Layout } from "antd";
import "./App.css";

const { Header, Content } = Layout;

function App() {
return (
<Layout className="layout">
<Header>
<div className="logo">TO-DO</div>
</Header>
<Content style={{ padding: "0 50px" }}>
<div className="site-layout-content">
<div>添加TODO</div>
<div>TODO列表</div>
<div>TODO统计</div>
</div>
</Content>
</Layout>
);
}

export default App;

添加完成后,界面如图所示:

数据管理

介绍

按照目前所学的知识,TODO 列表中的数据是全局性的,所以组件的数据,我们需要放在 App 组件里面。

这里呢,我们使用 Hooks 来作存储数据。

TODO 的结构

现在我们来康康每一个 TODO 的结构,根据需求,每个 TODO 应该有:

  • TODO 名称
  • 是否完成
1
2
3
4
{
title: String, // todo 名称
done: Boolean // 是否完成
}

建立 TODO 数据存储

我们现在可以建立 TODO 的数据存储了。

先创建一个状态:

1
let [list, setList] = useState([]);

接下来创建一个增加项目的函数:

1
2
3
4
const addItem = (title) => {
const newList = [...list, { title, done: false }];
setList(newList);
};

创建一个更新项目的函数:(使用 map

1
2
3
4
5
6
const updateItem = (index, done) => {
const newList = list.map((x, i) =>
i !== index ? x : Object.assign({}, x, { done })
);
setList(newList);
};

创建一个删除项目的函数:(使用 filter

1
2
3
4
const deleteItem = (index) => {
const newList = list.filter((x, i) => i !== index);
setList(newList);
};

目前的完整的 App.js 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { useState } from "react";
import { Layout } from "antd";
import "./App.css";

const { Header, Content } = Layout;

function App() {
let [list, setList] = useState([]);

const addItem = (title) => {
const newList = [...list, { title, done: false }];
setList(newList);
};

const updateItem = (index, done) => {
const newList = list.map((x, i) =>
i !== index ? x : Object.assign({}, x, { done })
);
setList(newList);
};

const deleteItem = (index) => {
const newList = list.filter((x, i) => i !== index);
setList(newList);
};

return (
<Layout className="layout">
<Header>
<div className="logo">TO-DO</div>
</Header>
<Content style={{ padding: "0 50px" }}>
<div className="site-layout-content">
<div>添加TODO</div>
<div>TODO列表</div>
<div>TODO统计</div>
</div>
</Content>
</Layout>
);
}

export default App;

TODO 增删改

修改 TODO

接下来,我们创建一个 TODO-ITEM的组件。

我们先在 src 目录创建一个 components 文件夹。

再在 components 文件夹中创建一个 ToDoItem 文件夹,在文件夹中创建 ToDoItem.jsindex.js

建好的目录结构如图所示:

我们在 index.js 导出组件模块:

1
export * from "./ToDoItem";

接下来在 ToDoItem.js添加组件,这里使用了 List 组件:

ToDoItem.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { List, Checkbox, Button } from "antd";

export function ToDoItem(props) {
return (
<List.Item>
<Checkbox checked={props.data.done} onChange={() => props.onUpdate()}>
{props.data.title}
</Checkbox>
<Button onClick={() => props.onDelete()} danger>
删除
</Button>
</List.Item>
);
}

创建组件完成后,我们需要在 App.js 引用它。为了方便,我们暂时添加了一条测试数据:

src/App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { useState } from "react";
import { Layout, List } from "antd";
import "./App.css";
import { ToDoItem } from "./components/ToDoItem";

const { Header, Content } = Layout;

function App() {
let [list, setList] = useState([{ title: "测试 0", done: false }]);

const addItem = (title) => {
const newList = [...list, { title, done: false }];
setList(newList);
};

const updateItem = (index) => {
console.log(index);
const newList = list.map((x, i) =>
i !== index ? x : { ...x, done: !x.done }
);
setList(newList);
};

const deleteItem = (index) => {
const newList = list.filter((x, i) => i !== index);
setList(newList);
};

return (
<Layout className="layout">

<Header>
<div className="logo">TO-DO</div>
</Header>
<Content style=\{\{ padding: "0 50px" \}\}>
<div className="site-layout-content">
<div>添加 TODO</div>
<div>
<List
dataSource={list}
renderItem={(item, i) => (
<ToDoItem
data={item}
onUpdate={() => updateItem(i)}
onDelete={() => deleteItem(i)}
/>
)} ></List>
</div>
<div>TODO 统计</div>
</div>
</Content>
</Layout>
);
}

export default App;

这里使用了 List 组件来渲染每一条记录。

修改好后,我们可以在浏览器上测试效果:

尝试点击复选框、删除按钮,康康效果。

添加 TODO

接下来,我们通过 Input组件来添加 ToDo。要求按 Enter 来添加。

这里,我们使用受控组件来操作表单。

src/App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import { useState } from "react";
import { Layout, List, Input } from "antd";
import "./App.css";
import { ToDoItem } from "./components/ToDoItem";

const { Header, Content } = Layout;

function App() {
let [list, setList] = useState([{ title: "测试 0", done: false }]);
let [value, setValue] = useState([]);

const addItem = (title) => {
const newList = [...list, { title, done: false }];
setList(newList);
};

const updateItem = (index) => {
console.log(index);
const newList = list.map((x, i) =>
i !== index ? x : { ...x, done: !x.done }
);
setList(newList);
};

const deleteItem = (index) => {
const newList = list.filter((x, i) => i !== index);
setList(newList);
};

return (
<Layout className="layout">

<Header>
<div className="logo">TO-DO</div>
</Header>
<Content style=\{\{ padding: "0 50px" \}\}>
<div className="site-layout-content">
<div>
<Input placeholder="输入要做的事,按 Enter 添加" />
</div>
<div>
<List
dataSource={list}
renderItem={(item, i) => (
<ToDoItem
data={item}
onUpdate={() => updateItem(i)}
onDelete={() => deleteItem(i)}
/>
)} ></List>
</div>
<div>TODO 统计</div>
</div>
</Content>
</Layout>
);
}

export default App;

我们完善一下事件:

1
2
3
4
5
const handleKeyUp = (e) => {
if (e.keyCode !== 13) return; // 13 是 Enter 键码
addItem(e.target.value);
e.target.value = ""; // 清空表单数据
};

完善后, App.js 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import { useState } from "react";
import { Layout, List, Input } from "antd";
import "./App.css";
import { ToDoItem } from "./components/ToDoItem";

const { Header, Content } = Layout;

function App() {
let [list, setList] = useState([]);
let [value, setValue] = useState([]);
const addItem = (title) => {
const newList = [...list, { title, done: false }];
setList(newList);
};

const updateItem = (index) => {
console.log(index);
const newList = list.map((x, i) =>
i !== index ? x : { ...x, done: !x.done }
);
setList(newList);
};

const deleteItem = (index) => {
const newList = list.filter((x, i) => i !== index);
setList(newList);
};

const handleKeyUp = (e) => {
if (e.keyCode !== 13) return; // 13 是 Enter 键码
addItem(e.target.value);
};

return (
<Layout className="layout">
<Header>
<div className="logo">TO-DO</div>
</Header>
<Content style={{ padding: "0 50px" }}>
<div className="site-layout-content">
<div>
<Input
onChange={(e) => setValue(e.target.value)}
value={value}
onKeyUp={handleKeyUp}
placeholder="输入要做的事,按 Enter 添加"
/>
</div>
<div>
<List
dataSource={list}
renderItem={(item, i) => (
<ToDoItem
data={item}
onUpdate={() => updateItem(i)}
onDelete={() => deleteItem(i)}
/>
)}
></List>
</div>
<div>TODO统计</div>
</div>
</Content>
</Layout>
);
}

export default App;

实现添加功能后,可以在浏览器测试一下。输入一些 TODO,按下 Enter 查看效果。

统计功能

统计功能就比较简单了。只需要使用 filter 函数对 list 进行过滤,并计数就行。

统计已完成:

1
const doneCount = list.filter((x) => x.done).length;
1
<div>已完成:{doneCount};总计:{list.length}</div>

完整的 App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import { useState } from "react";
import { Layout, List, Input } from "antd";
import "./App.css";
import { ToDoItem } from "./components/ToDoItem";

const { Header, Content } = Layout;

function App() {
let [list, setList] = useState([]);
let [value, setValue] = useState([]);

const addItem = (title) => {
const newList = [...list, { title, done: false }];
setList(newList);
};

const updateItem = (index) => {
console.log(index);
const newList = list.map((x, i) =>
i !== index ? x : { ...x, done: !x.done }
);
setList(newList);
};

const deleteItem = (index) => {
const newList = list.filter((x, i) => i !== index);
setList(newList);
};

const handleKeyUp = (e) => {
if (e.keyCode !== 13) return; // 13 是 Enter 键码
addItem(e.target.value);
};

const doneCount = list.filter((x) => x.done).length;

return (
<Layout className="layout">
<Header>
<div className="logo">TO-DO</div>
</Header>
<Content style={{ padding: "0 50px" }}>
<div className="site-layout-content">
<div>
<Input
onChange={(e) => setValue(e.target.value)}
value={value}
onKeyUp={handleKeyUp}
placeholder="输入要做的事,按 Enter 添加"
/>
</div>
<div>
<List
dataSource={list}
renderItem={(item, i) => (
<ToDoItem
data={item}
onUpdate={() => updateItem(i)}
onDelete={() => deleteItem(i)}
/>
)}
></List>
</div>
<div>
已完成:{doneCount};总计:{list.length}
</div>
</div>
</Content>
</Layout>
);
}

export default App;

完整代码

https://gitee.com/pikoyo/react-examples/tree/master/examples/todo-list

  • 标题: React(5-1):Todo-List案例
  • 作者: ObjectKaz
  • 创建于: 2021-05-14 13:12:35
  • 更新于: 2021-05-16 04:05:19
  • 链接: https://www.objectkaz.cn/a1b47b7b00c8.html
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。