11.gif

TODOList

这里没有对组件进行封装,而是全部写到了一个页面中 如果要对组件进行拆分的话,需要拆分为三个组件,分别是:

  1. 头部搜索框
  2. 列表
  3. 底部全选框

下面是比较标准的React开发规范

  1. todolist
  2. ├─ package.json
  3. ├─ public
  4. ├─ favicon.ico
  5. └─ index.html
  6. ├─ src
  7. ├─ App.css
  8. ├─ App.jsx
  9. ├─ Components
  10. ├─ Footer
  11. ├─ index.css
  12. └─ index.jsx
  13. ├─ Header
  14. ├─ index.css
  15. └─ index.jsx
  16. ├─ item
  17. ├─ index.css
  18. └─ index.jsx
  19. └─ List
  20. ├─ index.css
  21. └─ index.jsx
  22. └─ index.js
  23. └─ yarn.lock

需求分析

  • 输入框中输入数据后,按下回车,可以将待办事项添加到列表中
  • 点击未完成的待办事项,可以将其标注为已完成。如果全部已完成,那么修改全选部分的状态
  • 鼠标悬浮到每一项后面的时候,出现删除按钮,点击删除按钮可以将元素删除
  • 全选部分可以控制待办列表的状态:全部已完成或全部未完成

    代码实现

    这里为了方便,直接引入了ant-design组件库

首先肯定需要一个列表,来保存待办的状态
image.png

添加待办

image.png
给Input框绑定value以及onChange事件

  1. <div>
  2. <Input
  3. value={this.state.text}
  4. onChange={(e) => this.setState({ text: e.target.value })}
  5. placeholder='请输入...'
  6. onKeyDown={this.handleEnterKeyDown}
  7. />
  8. </div>

按下回车要能触发事件,因此我们需要给输入框绑定onKeyDown事件,并判断按下的键是否是enter
不难发现,每一次新添加的内容需要添加到列表的首位,但其实利用ES6的解构运算,很容易实现,如下代码所示:

  1. handleEnterKeyDown = (e) => {
  2. // 监听回车事件
  3. if (e.keyCode === 13) {
  4. const { text, list } = this.state;
  5. if (text.trim() === '') {
  6. message.error('请输入内容');
  7. } else {
  8. const { list } = this.state;
  9. let item = {
  10. id: list.length + 1,
  11. isFinished: false,
  12. text,
  13. };
  14. const newList = [item, ...list];
  15. let [sum, flag] = [0, false];
  16. newList.forEach((e) => (e.isFinished ? (sum += 1) : (sum += 0)));
  17. flag = sum === newList.length;
  18. this.setState({
  19. list: newList,
  20. text: '',
  21. isFinishedAll: flag,
  22. });
  23. message.success('添加成功');
  24. }
  25. }
  26. };

这里的小细节是,当输入之前,所有的待办处于已完成的状态,那么我们新添加一个待办之后,势必要将全选框的状态进行修改(因为此时已经不是全部都完成了)

完成待办

我们这里使用到了ant-design的列表组件,所以我们可以直接给List.Item绑定点击事件,从而进行逻辑的处理
image.png

有一个小细节,就是当所有的待办都被完成之后,要修改下面全选框的状态

点击事件如下:
image.png

删除待办

我们的需求是:鼠标悬浮在某一项上时,该项后面出现删除按钮。并且同一时间下有且仅有一项后面可以出现这个按钮。
因此我们需要监听鼠标的移入和移出事件,并且设定状态来判断鼠标悬浮于第几个待办上
image.png
image.png

全选/全不选

checkbox添加一个切换事件
image.png

完整代码

  1. import { Button, Checkbox, Divider, Input, List, message, Typography } from 'antd';
  2. import React, { Component } from 'react';
  3. export default class TodoList extends Component {
  4. state = {
  5. list: [
  6. {
  7. id: 1,
  8. isFinished: false,
  9. text: '吃饭',
  10. },
  11. ],
  12. isFinishedAll: false,
  13. mouse: {
  14. in: false,
  15. index: -1,
  16. },
  17. text: '',
  18. };
  19. handleEnterKeyDown = (e) => {
  20. // 监听回车事件
  21. if (e.keyCode === 13) {
  22. const { text, list } = this.state;
  23. if (text.trim() === '') {
  24. message.error('请输入内容');
  25. } else {
  26. const { list } = this.state;
  27. let item = {
  28. id: list.length + 1,
  29. isFinished: false,
  30. text,
  31. };
  32. const newList = [item, ...list];
  33. let [sum, flag] = [0, false];
  34. newList.forEach((e) => (e.isFinished ? (sum += 1) : (sum += 0)));
  35. flag = sum === newList.length;
  36. this.setState({
  37. list: newList,
  38. text: '',
  39. isFinishedAll: flag,
  40. });
  41. message.success('添加成功');
  42. }
  43. }
  44. };
  45. handleFinish = (item) => {
  46. console.log(item);
  47. let { list } = this.state;
  48. if (!item.isFinished) {
  49. // 找出list中与item的id相同的元素
  50. let index = list.findIndex((e) => e.id === item.id);
  51. console.log('index', index);
  52. list[index].isFinished = true;
  53. this.setState({
  54. list,
  55. });
  56. message.success('恭喜您,任务完成!');
  57. }
  58. let sum = 0;
  59. list.forEach((e) => (e.isFinished ? sum++ : null));
  60. if (sum === list.length) {
  61. this.setState({
  62. isFinishedAll: true,
  63. });
  64. }
  65. };
  66. handleMouseOver = (flag, item) => {
  67. this.setState({
  68. mouse: {
  69. in: flag,
  70. index: item.id,
  71. },
  72. });
  73. };
  74. handleCheckBoxChecked = (e) => {
  75. const { checked } = e.target;
  76. let { list } = this.state;
  77. list.forEach((e) => (e.isFinished = checked));
  78. this.setState({
  79. list,
  80. isFinishedAll: checked,
  81. });
  82. const msg = checked ? '完成' : '取消';
  83. message.success(`已${msg}全部任务`);
  84. };
  85. handleDelete = (e, item) => {
  86. console.log('delete');
  87. e.stopPropagation();
  88. let { list } = this.state;
  89. this.setState({
  90. list: list.filter((e) => e.id !== item.id),
  91. });
  92. message.success(`已删除${item.text}`);
  93. };
  94. render() {
  95. return (
  96. <div>
  97. <h2>待办事项</h2>
  98. <div>
  99. <Input
  100. value={this.state.text}
  101. onChange={(e) => this.setState({ text: e.target.value })}
  102. placeholder='请输入...'
  103. onKeyDown={this.handleEnterKeyDown}
  104. />
  105. </div>
  106. <>
  107. <Divider orientation='left'>列表</Divider>
  108. <List
  109. footer={
  110. <div>
  111. <span>
  112. <Checkbox
  113. onChange={this.handleCheckBoxChecked}
  114. checked={this.state.isFinishedAll}
  115. />
  116. </span>
  117. &nbsp;&nbsp;
  118. <span>已完成{this.state.list.filter((e) => e.isFinished).length}项</span>
  119. &nbsp;/&nbsp;
  120. <span>全部{this.state.list.length}项</span>
  121. </div>
  122. }
  123. bordered
  124. dataSource={this.state.list}
  125. renderItem={(item) => (
  126. <List.Item
  127. actions={[
  128. <Button
  129. onClick={(e) => this.handleDelete(e, item)}
  130. key='list-loadmore-edit'
  131. type='danger'
  132. style={{
  133. display:
  134. this.state.mouse.in === true && this.state.mouse.index === item.id
  135. ? 'inline-block'
  136. : 'none',
  137. }}>
  138. 删除
  139. </Button>,
  140. ]}
  141. onMouseOver={() => this.handleMouseOver(true, item)}
  142. onMouseOut={() => this.handleMouseOver(false, item)}
  143. onClick={() => this.handleFinish(item)}>
  144. <Typography.Text mark={item.isFinished ? true : false}>
  145. [&nbsp;&nbsp;]
  146. </Typography.Text>
  147. {item.text}
  148. </List.Item>
  149. )}
  150. />
  151. </>
  152. </div>
  153. );
  154. }
  155. }