第一次让 AI 帮你写代码:用 JavaScript 完成一个待办事项清单

0 阅读11分钟

在这里插入图片描述

前面的文章中,我们了解了什么是 AI 编程,也认识了常见的 AI 编程工具。

这一篇不再停留在概念层面,而是通过一个完整的小案例,体验一次真实的 AI 编程流程:

让 AI 帮我们实现一个简单的 JavaScript 待办事项清单。

这个案例虽然简单,但包含了日常开发中比较典型的步骤:

  • 分析需求
  • 拆分功能
  • 生成代码
  • 运行代码
  • 发现问题
  • 修改代码
  • 验证结果

掌握这套流程后,后面无论是开发表单、后台页面,还是实现接口功能,都可以使用类似的方法。

一、先明确我们要做什么

在让 AI 写代码之前,不能只说一句:

帮我写一个待办事项应用。

这个需求过于简单,AI 不知道我们具体需要什么功能,可能会出现以下问题:

  • 使用什么技术实现不明确
  • 页面需要哪些功能不明确
  • 数据是否需要保存不明确
  • 是否需要处理空输入不明确
  • 代码应该放在哪个文件中不明确
  • 最终运行方式不明确

因此,在开始之前,我们先把需求整理清楚。

本次要实现的待办事项清单包含以下功能:

功能说明
添加任务输入任务内容后点击按钮添加
展示任务将已添加的任务显示在列表中
完成任务点击复选框后标记任务已完成
删除任务删除不再需要的任务
数据保存刷新页面后任务仍然保留
空输入校验不允许添加空白任务

技术要求如下:

  • 使用 HTML、CSS 和 JavaScript
  • 不使用前端框架
  • 不依赖第三方库
  • 所有代码放在一个 HTML 文件中
  • 双击文件即可在浏览器中运行

需求越明确,AI 生成的结果越接近我们真正想要的结果。

二、先让 AI 拆解需求

很多人使用 AI 时,习惯直接让它输出最终代码。

更稳定的方式是先让 AI 分析需求,确认实现思路后,再让它生成代码。

可以使用下面这段提示词:

我想使用原生 HTML、CSS 和 JavaScript,
开发一个简单的待办事项清单。

功能要求:
1. 用户可以输入任务并添加
2. 任务显示在列表中
3. 每条任务前有一个复选框
4. 勾选后显示完成状态
5. 每条任务都可以删除
6. 使用 localStorage 保存数据
7. 输入为空时不能添加

请先不要生成完整代码,
先帮我拆分功能模块,
说明每个模块的实现思路,
并列出需要注意的边界情况。

一个比较可靠的 AI 回复,通常会把需求拆分成以下模块:

  1. 页面结构模块:输入框、添加按钮、任务列表。
  2. 数据模块:使用数组保存任务数据。
  3. 渲染模块:将任务数据转换成页面内容。
  4. 添加模块:获取输入内容并创建新任务。
  5. 完成模块:修改任务的完成状态。
  6. 删除模块:从数组中移除指定任务。
  7. 持久化模块:将任务保存到 localStorage。
  8. 校验模块:处理空输入和无效数据。

这一步的价值在于:我们可以先检查 AI 的理解是否正确。

如果需求拆解错了,及时纠正即可;如果直接生成几百行代码,后面再发现方向不对,修改成本会更高。

三、再让 AI 生成代码

确认实现思路后,可以继续给 AI 下面的提示词:

请根据刚才的功能拆解,
生成一个完整可运行的 index.html 文件。

具体要求:
1. 使用原生 HTML、CSS 和 JavaScript
2. 不使用任何第三方库
3. 所有代码放在同一个 HTML 文件中
4. 页面布局简洁,适合初学者阅读
5. JavaScript 使用清晰的函数拆分
6. 使用 localStorage 保存任务
7. 添加任务时校验输入内容
8. 删除任务前不需要弹窗确认
9. 代码中添加必要注释
10. 输出完整代码,并说明运行方法

这里增加了“单文件”“不使用第三方库”“函数拆分”等限制,可以减少 AI 输出不符合要求的代码。

四、完整可运行代码

下面是一份可以直接运行的示例代码。

将代码保存为 index.html,然后使用浏览器打开即可。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>我的待办事项</title>
  <style>
    * {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      min-height: 100vh;
      padding: 40px 16px;
      background: #f3f5f7;
      color: #1f2937;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
        "PingFang SC", "Microsoft YaHei", sans-serif;
    }

    .todo-app {
      width: min(100%, 640px);
      margin: 0 auto;
      padding: 28px;
      background: #ffffff;
      border-radius: 12px;
      box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
    }

    h1 {
      margin: 0 0 24px;
      text-align: center;
      font-size: 28px;
    }

    .input-row {
      display: flex;
      gap: 10px;
    }

    #task-input {
      flex: 1;
      min-width: 0;
      height: 42px;
      padding: 0 12px;
      border: 1px solid #d1d5db;
      border-radius: 6px;
      font-size: 15px;
    }

    #task-input:focus {
      outline: none;
      border-color: #2563eb;
      box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12);
    }

    button {
      border: 0;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
    }

    #add-button {
      padding: 0 18px;
      background: #2563eb;
      color: #ffffff;
    }

    #add-button:hover {
      background: #1d4ed8;
    }

    #task-list {
      margin: 24px 0 0;
      padding: 0;
      list-style: none;
    }

    .task-item {
      display: flex;
      align-items: center;
      gap: 10px;
      min-height: 48px;
      padding: 8px 0;
      border-bottom: 1px solid #e5e7eb;
    }

    .task-text {
      flex: 1;
      overflow-wrap: anywhere;
    }

    .task-item.completed .task-text {
      color: #9ca3af;
      text-decoration: line-through;
    }

    .delete-button {
      padding: 6px 10px;
      background: #fee2e2;
      color: #b91c1c;
    }

    .delete-button:hover {
      background: #fecaca;
    }

    .empty-message {
      padding: 20px 0;
      color: #6b7280;
      text-align: center;
    }

    @media (max-width: 480px) {
      .todo-app {
        padding: 20px 16px;
      }

      .input-row {
        flex-direction: column;
      }

      #add-button {
        height: 42px;
      }
    }
  </style>
</head>
<body>
  <main class="todo-app">
    <h1>我的待办事项</h1>

    <div class="input-row">
      <input
        id="task-input"
        type="text"
        placeholder="请输入待办事项"
        maxlength="100"
      >
      <button id="add-button" type="button">添加任务</button>
    </div>

    <ul id="task-list"></ul>
  </main>

  <script>
    const STORAGE_KEY = "todo-tasks";

    const taskInput = document.querySelector("#task-input");
    const addButton = document.querySelector("#add-button");
    const taskList = document.querySelector("#task-list");

    let tasks = loadTasks();

    // 从 localStorage 读取任务数据
    function loadTasks() {
      try {
        const savedTasks = localStorage.getItem(STORAGE_KEY);
        const parsedTasks = savedTasks ? JSON.parse(savedTasks) : [];

        return Array.isArray(parsedTasks) ? parsedTasks : [];
      } catch (error) {
        console.error("读取任务失败:", error);
        return [];
      }
    }

    // 保存任务数据
    function saveTasks() {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
    }

    // 将任务数据渲染到页面
    function renderTasks() {
      taskList.innerHTML = "";

      if (tasks.length === 0) {
        const emptyMessage = document.createElement("li");
        emptyMessage.className = "empty-message";
        emptyMessage.textContent = "暂时没有待办事项";
        taskList.appendChild(emptyMessage);
        return;
      }

      tasks.forEach((task) => {
        const taskItem = document.createElement("li");
        taskItem.className = "task-item";

        if (task.completed) {
          taskItem.classList.add("completed");
        }

        const checkbox = document.createElement("input");
        checkbox.type = "checkbox";
        checkbox.checked = task.completed;
        checkbox.setAttribute("aria-label", `完成任务:${task.text}`);

        checkbox.addEventListener("change", () => {
          toggleTask(task.id);
        });

        const taskText = document.createElement("span");
        taskText.className = "task-text";
        taskText.textContent = task.text;

        const deleteButton = document.createElement("button");
        deleteButton.className = "delete-button";
        deleteButton.type = "button";
        deleteButton.textContent = "删除";

        deleteButton.addEventListener("click", () => {
          deleteTask(task.id);
        });

        taskItem.append(checkbox, taskText, deleteButton);
        taskList.appendChild(taskItem);
      });
    }

    // 添加任务
    function addTask() {
      const text = taskInput.value.trim();

      if (!text) {
        alert("请输入待办事项");
        taskInput.focus();
        return;
      }

      tasks.unshift({
        id: Date.now(),
        text,
        completed: false
      });

      saveTasks();
      renderTasks();

      taskInput.value = "";
      taskInput.focus();
    }

    // 切换任务完成状态
    function toggleTask(taskId) {
      tasks = tasks.map((task) => {
        if (task.id === taskId) {
          return {
            ...task,
            completed: !task.completed
          };
        }

        return task;
      });

      saveTasks();
      renderTasks();
    }

    // 删除任务
    function deleteTask(taskId) {
      tasks = tasks.filter((task) => task.id !== taskId);

      saveTasks();
      renderTasks();
    }

    addButton.addEventListener("click", addTask);

    taskInput.addEventListener("keydown", (event) => {
      if (event.key === "Enter") {
        addTask();
      }
    });

    renderTasks();
  </script>
</body>
</html>

五、如何运行这段代码

按照下面的步骤操作:

  1. 创建一个名为 index.html 的文件。
  2. 将上面的完整代码复制到文件中。
  3. 保存文件。
  4. 使用浏览器打开 index.html
  5. 在输入框中添加几条任务。
  6. 勾选任务,观察完成状态。
  7. 删除一条任务。
  8. 刷新浏览器,检查任务是否仍然存在。

如果刷新后任务还在,说明 localStorage 保存功能正常。

六、如何验证 AI 生成的代码

代码能够运行,并不代表代码完全正确。

建议至少验证下面这些情况:

测试场景预期结果
直接点击添加按钮提示输入任务
输入空格后添加提示输入任务
输入正常内容后添加任务成功显示
按回车键添加任务成功显示
勾选任务任务显示完成状态
取消勾选任务恢复未完成状态
删除任务任务从列表中消失
刷新页面已有任务仍然存在
添加较长文本页面布局不被撑破
删除全部任务显示空列表提示

这一步非常重要。

AI 生成代码时,通常会优先实现主流程,但一些边界情况可能被忽略。开发者需要通过实际操作发现问题,再把问题反馈给 AI。

例如,发现输入空内容也能添加任务,可以继续询问:

当前代码允许用户输入空格后添加任务,
请分析原因并修改代码。

要求:
1. 使用 trim 判断输入内容
2. 空内容时给出提示
3. 输入框清空后保持焦点
4. 只输出需要修改的代码片段,
   并解释修改原因

与其让 AI 每次重新生成全部代码,不如明确指出问题,让它进行局部修改。

七、AI 生成代码时常见的问题

1. 代码看起来完整,但无法运行

常见原因包括:

  • HTML 标签没有闭合
  • JavaScript 变量名称前后不一致
  • 事件绑定到了错误的元素
  • 函数名称写错
  • 代码依赖了没有安装的第三方库

解决方法是把完整报错信息和相关代码一起提供给 AI。

打开页面后,点击“添加任务”没有任何反应。

浏览器控制台报错:
[粘贴完整报错]

请定位具体原因,
指出错误代码所在位置,
然后给出最小修改方案。

2. 只实现了正常情况

例如,正常输入可以添加任务,但空输入、重复任务、超长文本没有处理。

可以让 AI 补充边界条件:

请检查这段待办事项代码,
重点分析以下边界情况:

- 空字符串
- 只有空格的输入
- 超过 100 个字符的输入
- localStorage 中保存了非法数据
- 用户连续快速点击添加按钮

请列出问题,并给出修改建议。

3. 代码能运行,但自己看不懂

这说明 AI 输出的代码超出了当前理解范围。

可以要求 AI 降低代码复杂度:

请把这段代码改写成适合 JavaScript 初学者学习的版本。

要求:
1. 保留原有功能
2. 减少复杂语法
3. 拆分成多个简单函数
4. 逐个解释每个函数
5. 说明数据是如何从输入框流向页面的

使用 AI 的过程中,不要只关注最终结果,也要关注自己是否理解实现过程。

八、一套可以复用的提示词模板

以后开发类似的小功能时,可以直接套用下面的模板:

我想开发一个【功能名称】。

使用技术:
- 【技术名称】
- 【是否使用框架】
- 【是否允许第三方库】

核心功能:
1. 【功能一】
2. 【功能二】
3. 【功能三】

输入数据:
- 【输入内容和格式】

输出结果:
- 【页面或接口应该返回什么】

特殊要求:
- 【异常处理要求】
- 【边界情况】
- 【兼容性要求】
- 【代码组织要求】

请按照以下步骤完成:
1. 先分析需求并拆分功能
2. 列出可能的边界情况
3. 给出实现方案
4. 再生成完整代码
5. 说明运行方法
6. 提供测试清单

这个模板的核心不是固定的文字,而是让 AI 清楚了解五件事:

  • 你想做什么
  • 使用什么技术
  • 需要哪些功能
  • 有哪些限制
  • 希望 AI 按什么步骤回答

九、这次实践应该掌握什么

通过这个小案例,我们完成了一次比较完整的 AI 编程流程:

明确需求
   ↓
让 AI 拆解功能
   ↓
确认实现思路
   ↓
让 AI 生成代码
   ↓
本地运行验证
   ↓
发现问题并反馈
   ↓
修改代码并再次测试

需要特别注意的是:

AI 编程不是把需求复制给 AI,然后直接复制它返回的代码。

更合理的工作方式是:

开发者负责明确目标、判断方案和验证结果,AI 负责提供实现建议和编码辅助。

即使案例很简单,也应该保留“生成、运行、验证、修改”这几个环节。

总结

  • 让 AI 写代码之前,首先要明确功能和技术要求。
  • 先让 AI 拆解需求,再让它生成代码,结果通常更稳定。
  • 代码生成后必须在本地运行,并验证正常场景和异常场景。
  • 遇到问题时,应提供完整报错、相关代码和运行环境。
  • AI 适合帮助我们快速完成基础实现,但不能替代开发者进行判断。
  • 一套清晰的提示词模板,可以复用到不同的开发任务中。

下一篇文章将继续讨论一个非常重要的问题:

为什么 AI 生成的代码不能直接使用?如何对 AI 代码进行检查和验证?


✍坚持原创,求关注,点赞,收藏