Create a Todo List in JavaScript in Five Steps

JavaScript 五个步骤制作待办事项

前言

待办事项是非常常见的习题,其中需求涵括了增、删、读、改资料,充分模拟未来在操纵资料时会碰上的各种情境与问题,可以说各种软件都是一种客制化的待办事项。随着助教写一次,未来写很多应用会轻松得多~

解题

第一步:制作界面

本步骤要完成的动作如下:

  • 提供用户输入界面
  • 使用 JavaScript 选取并监听界面动态
<!-- 提供用户输入界面 -->
<div class="todo">
<form data-todo-form class="todo__form">
<input name="userInput" type="text" placeholder="请输入事项" required />
<input type="submit" value="提交" />
</form>
<ul data-todo-list></ul>
</div>

常常同学会直接 <input> 上绑监听,虽然并不会造成什么问题但助教会更倾向使用现有的 <form> 来让用户提交资料,原因是 HTML 原生就支持客户端的资料验证,可以更方便地去检核用户输入的资料对错,也可以更语义化地描述「这是一个表单,等待用户填写内容」,而非「这是画面上随便一个可以输入的栏位」。

<!-- 使用验证强制要求用户必须输入内容 -->
<input type="text" required />

接着就是使用 JavaScript 选取并监听界面动态(表单提交与事项清单点击)

// 使用 JavaScript 选取并监听界面动态
const todoForm = document.querySelector('[data-todo-form]');
const todoList = document.querySelector('[data-todo-list]');
// 表单当提交时……
todoForm.addEventListener('submit', (e) => {
// 取消浏览器默认行为(跳转页面)
e.preventDefault();
});
// 当事项清单被点击时……
todoList.addEventListener('click', (e) => {});

第二步:显示待办事项

在界面架构好之后就需要于第一次网页加载时将 JavaScript 中的数据显示到画面上,因此先定义一笔待办事项资料(在此填充了假资料,方便同学理解):

// id = 事项独一无二的识别码
// name = 事项的名称
// isCompleted = 事项完成状态
let tasks = [
{
id: '1',
name: 'Learn HTML',
isCompleted: false,
},
{
id: '2',
name: 'Learn CSS',
isCompleted: true,
},
];

之后就是考虑如何显示这笔资料,由于这是一個会频繁执行的动作,制作一个函数将功能包装起来,方便之后引用:

function renderTask(tasks) {
// 抓取 task 资料,并用 map 跑过一轮,在每次迭代中把资料取出并放入 HTML 中回传
const tasksHTML = tasks.map(
(task) => `
<li task-id=${task.id}>
<input id=${task.id} data-task-toggle type="checkbox" ${task.isCompleted ? 'checked' : ''}>
<label for=${task.id}>${task.name}</label>
<button data-task-delete>删除</button>
</li>
`,
);
todoList.innerHTML = tasksHTML.join('');
}
renderTask(tasks);

可以特别注意这里使用了现有的数据作判断,当「isCompletedtrue」就为 input 添加打勾否则不打勾。

<input ${task.isCompleted ? "checked" : ""}>

第三步:新增待办事项

后续步骤就简单很多了,新增事项流程上就是:

  1. 检测表单被提交
  2. 将新增的事项名称丢给 addTask 函式处理
  3. 加入一笔新的资料给 tasks
  4. 呼叫 renderTask 重新渲染一次

如果不想要因为一个事项改动就触发整个待办事项重新渲染一次,可以作细微的操控去编辑 DOM,不过在本次解题中先采取最简单直观的方式 :更新完资料就整体刷新再显示。

// 01.检测表单被提交
todoForm.addEventListener('submit', (e) => {
e.preventDefault();
// 02.将提交的事项名称传入 addTask 函式
addTask(e.target.userInput.value);
// 重置表单内容
e.target.reset();
});
function addTask(taskName) {
// 03.加入新的资料给 tasks,tasks 等于(旧的 tasks + 新事项)
tasks = [
...tasks,
{
id: Date.now().toString(),
name: taskName,
isCompleted: false,
},
];
// 04.重新渲染一次
renderTask(tasks);
}

这里使用 Date.now()🔗 来得出自 1970/01/01 00:00:00 UTC 起经过的毫秒数作为 ID 使用。

Date.now().toString();

第四步:删除待办事项

要删除事项,就势必要得知「点击下去的删除按钮是哪项事项」,再根据事项 ID 与目前 tasks 数据去交互比对,如果相同就移除这笔资料并重新渲染一次所有事项。

删除事项流程就是:

  1. 检测待办清单是否被点击
  2. 确认被点击的是删除按钮
  3. 取出 task-id 属性
  4. 传入 removeTask 函式中
  5. removeTask 函式中比对 tasks 中相同 task-id 的事项
  6. 删除 tasks 中该笔事项
  7. 呼叫 renderTask 重新渲染一次
// 监听待办清单是否被点击
todoList.addEventListener('click', (e) => {
// 当被点击时,确认被点击的事项有 "data-task-delete" 属性
if (e.target.hasAttribute('data-task-delete')) {
// 如果有,将其父元素上的 task-id 属性取出,传入 removeTasks 函式内
const taskId = e.target.parentElement.getAttribute('task-id');
removeTask(taskId);
}
});
这里助教使用了一个数组方法 `reduce` 来快速剔除不需要的数据(`curr.id === targetId`)。
```javascript
function removeTask(targetId) {
// 比对 tasks 中相同 task-id 的事项,
// 最终 tasks 等于(过滤掉删除事项的事项)
tasks = tasks.reduce((prev, curr) => {
if (curr.id === targetId) {
return prev;
}
return [...prev, curr];
}, []);
renderTask(tasks);
}
```

第五步:切换待办事项

切换事项状态的思路和删除事项非常相似,切换事项流程就是:

  1. 检测待办清单是否被点击
  2. 确认被点击的是复选框
  3. 取出 task-id 属性
  4. 传入 toggleTask 函数中
  5. toggleTask 函数中比对 tasks 中相同 task-id 的事项
  6. 切换 tasks 中该笔事项的状态
// 监听待办清单是否被点击
todoList.addEventListener('click', (e) => {
// 当被点击时,确认被点击的事项有 "data-task-toggle" 属性
if (e.target.hasAttribute('data-task-toggle')) {
// 如果有,将其父元素上的 task-id 属性取出,传入 toggleTask 函数内
const taskId = e.target.parentElement.getAttribute('task-id');
toggleTask(taskId);
}
});
function toggleTask(targetId) {
// 跑过 tasks 中每件事项,如果 id 相同,翻转该事项的完成状态
tasks.forEach((task) => {
if (task.id === targetId) {
task.isCompleted = !task.isCompleted;
}
});
}

结语

See the Pen Todo vanilla JS by Riceball ( @riecball) on CodePen.

以上是助教制作整个待办事项题目的思考历程,同学可以参考但不用奉为宗旨死背或直接复制粘贴,实际操作一次效果最佳~