---
title: "3.7.4 实战演练：综合练习"
---


![03-technique_3.7-summary_3.7.4-exercises.png](/images/Basic/03-technique_3.7-summary_3.7.4-exercises.png)
# 3.7.4 实战演练：综合练习

本节提供四个综合练习，帮助你内化本章所学技能。建议认真完成每个练习，然后对照参考答案自评。


## 练习一：提示词改写

### 任务说明

下面有三个「差提示词」，请用 S.C.A.F.F. 框架改写成「好提示词」。

### 差提示词 1

```
帮我做一个登录页面
```

**你的改写**：

（请先自己尝试改写，再看参考答案）

<details>
<summary>点击查看参考答案</summary>

```markdown
【Situation - 情境】
我正在开发一个个人博客的管理后台。
技术栈：Next.js 16 + TypeScript + Tailwind CSS
当前进度：首页和文章列表页已完成，现在需要添加登录功能
设计风格：极简风格，参考 Notion 的登录页面

【Challenge - 挑战】
实现管理员登录页面：
- 表单包含：邮箱输入框、密码输入框、登录按钮
- 点击登录后，验证格式（邮箱格式、密码不为空）
- 格式错误时，在对应输入框下方显示红色提示
- 暂时不需要真正的后端验证，先做前端界面和交互

【Audience - 受众】
- 用户：只有我一个人（博客管理员）
- 代码维护者：我自己，React 初学者
- 请在关键逻辑处添加注释

【Format - 格式】
请提供：
1. 完整的登录页面组件代码（单文件）
2. 包含 TypeScript 类型定义
3. 样式使用 Tailwind CSS
4. 文件命名：LoginPage.tsx

【Foundations - 约束】
- 不使用任何 UI 组件库
- 不使用第三方表单库
- 状态管理用 React 原生的 useState
- 响应式设计：移动端和桌面端都要适配
```

**评分标准**：
- ✅ 有项目背景和技术栈（2分）
- ✅ 任务描述具体，有输入输出（2分）
- ✅ 说明了受众和技术水平（1分）
- ✅ 指定了输出格式（2分）
- ✅ 有明确的约束条件（2分）
- ✅ 使用了框架结构（1分）

满分 10 分，8 分以上为优秀。

</details>


### 差提示词 2

```
帮我分析一下这段代码有什么问题
```

**你的改写**：

<details>
<summary>点击查看参考答案</summary>

**改写后的提示词**：

```markdown
【当前代码】
const fetchUsers = async () => {
  const response = await fetch('/api/users');
  const data = response.json();
  setUsers(data);
}

【问题现象】
调用这个函数后，users 状态是一个 Promise 对象而不是数组，导致渲染时报错。

【期望行为】
users 应该是从 API 获取的用户数组

【请帮我】
1. 指出代码中的问题
2. 解释为什么会出现这个问题
3. 给出修正后的代码
4. 说明如何避免类似错误

【关于我】
我是 JavaScript 初学者，对 async/await 还不太熟悉
```

**评分标准**：
- ✅ 提供了具体的代码（2分）
- ✅ 描述了问题现象（2分）
- ✅ 说明了期望行为（2分）
- ✅ 明确了需要什么帮助（2分）
- ✅ 说明了自己的技术水平（2分）

满分 10 分，8 分以上为优秀。

</details>


### 差提示词 3

```
我想做个记账软件
```

**你的改写**：

<details>
<summary>点击查看参考答案</summary>

```markdown
【Situation - 情境】
我想做一个个人记账网页应用，帮助自己追踪日常开销。
技术栈：React + TypeScript + Tailwind CSS
目标用户：我自己（月光族，想知道钱花哪了）
设计原则：极简，记一笔账不超过 5 秒

【Challenge - 挑战】
第一版（MVP）实现以下功能：
1. 快速记账：选择分类 + 输入金额 + 可选备注
2. 查看账单：按日期查看当天的所有记录
3. 月度汇总：显示当月总支出和各分类占比

【Audience - 受众】
- 用户：我自己，需要简单快捷
- 代码维护者：我自己，有一点 React 基础

【Format - 格式】
这是第一次对话，请先帮我：
1. 确认这个 MVP 范围是否合理
2. 建议项目的文件结构
3. 列出需要实现的组件清单

【Foundations - 约束】
- 数据先存在 localStorage，不需要后端
- 分类固定为：餐饮、交通、购物、娱乐、其他
- 只记支出，不记收入（简化）
- 不需要用户登录

【不做清单】
- 不做多账本
- 不做预算功能
- 不做数据导出
- 不做图表可视化（第二版考虑）
```

**评分标准**：
- ✅ 有完整的项目背景（2分）
- ✅ MVP 功能范围明确（2分）
- ✅ 有「不做清单」（2分）
- ✅ 第一次对话请求合理（规划而非直接写代码）（2分）
- ✅ 约束条件具体（2分）

满分 10 分，8 分以上为优秀。

</details>


## 练习二：PRD 编写实战

### 任务说明

假设你想做一个「读书笔记」工具，帮助自己记录读书心得。

请整合第二章的思维工具，完成以下内容：

1. 用 JTBD 格式描述用户任务
2. 用减法思维确定 MVP 范围（P0/P1/不做）
3. 回答灵魂三问
4. 写出一份精简版 PRD

### 你的答案

（请先自己完成，再看参考答案）

<details>
<summary>点击查看参考答案</summary>

### 1. JTBD 任务描述

> 当我读完一本书的某个章节时，我想快速记录关键观点和我的思考，这样我在需要回顾时能够快速找到这些笔记，而不是重新翻书。

### 2. MVP 范围

| 优先级 | 功能 | 原因 |
|-------|------|------|
| **P0** | 创建笔记（书名 + 章节 + 内容） | 核心功能 |
| **P0** | 查看所有笔记列表 | 核心功能 |
| **P0** | 按书名筛选笔记 | 找笔记必须快 |
| **P1** | 编辑已有笔记 | 经常需要修改补充 |
| **P1** | 删除笔记 | 清理无用笔记 |
| **不做** | 笔记标签/分类 | 增加复杂度，MVP 不需要 |
| **不做** | 笔记分享 | 个人使用，不需要 |
| **不做** | Markdown 编辑器 | 简单文本足够 |
| **不做** | 书籍信息自动获取 | 手动输入书名即可 |

### 3. 灵魂三问

| 问题 | 回答 |
|-----|------|
| **用户是谁？** | 我自己——喜欢读书但经常忘记书中内容的人 |
| **痛点在哪？** | 读过的书记不住，需要时找不到当时的思考 |
| **为什么做这个？** | 现有工具（Notion、备忘录）要么太重，要么不好按书检索 |

### 4. 精简版 PRD

```markdown
# 读书笔记工具 PRD

## 项目背景

### 问题
我喜欢读书，但经常读完就忘。需要回顾时，要么重新翻书，要么在各种 app 里找当时的笔记，很费时间。

### JTBD
当我读完一本书的某个章节时，我想快速记录关键观点和我的思考，这样我在需要回顾时能够快速找到这些笔记。

## 目标用户

- 身份：喜欢读书的个人用户
- 场景：读完一章后记录、需要引用时检索
- 核心需求：快速记录 + 快速检索

## P0 功能

### 功能1：创建笔记
- 必填：书名、笔记内容
- 选填：章节名、页码
- 交互：打开页面直接可以开始写

### 功能2：查看笔记列表
- 按创建时间倒序显示
- 每条显示：书名、章节、内容摘要、时间
- 点击可查看完整内容

### 功能3：按书名筛选
- 页面顶部有书名筛选器
- 选择后只显示该书的笔记

## 技术方案

- 前端：React + TypeScript + Tailwind CSS
- 数据：localStorage（个人使用，不需要后端）
- 响应式：支持手机和电脑

## 不做清单

- 不做笔记标签/分类
- 不做笔记分享功能
- 不做 Markdown 编辑器
- 不做书籍封面/信息自动获取
- 不做多端同步

## 成功标准

自己使用 2 周，记录至少 10 条笔记，并成功检索到之前的笔记。
```

**自评标准**：
- ✅ JTBD 描述完整（场景 + 任务 + 价值）
- ✅ MVP 范围有优先级，有「不做清单」
- ✅ 灵魂三问都能回答
- ✅ PRD 结构完整，AI 能理解执行

</details>


## 练习三：迭代对话模拟

### 任务说明

假设你让 AI 帮你实现「读书笔记」的创建功能，AI 给了你以下代码，但有问题。

请模拟 3 轮迭代对话，逐步修正问题。

### AI 第一次输出

```typescript
const CreateNote = () => {
  const [bookName, setBookName] = useState('');
  const [content, setContent] = useState('');

  const handleSubmit = () => {
    const note = { bookName, content };
    const notes = JSON.parse(localStorage.getItem('notes'));
    notes.push(note);
    localStorage.setItem('notes', JSON.stringify(notes));
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={bookName} onChange={e => setBookName(e.target.value)} />
      <textarea value={content} onChange={e => setContent(e.target.value)} />
      <button type="submit">保存</button>
    </form>
  );
}
```

### 问题识别

请找出这段代码的问题，然后写出你的反馈。

<details>
<summary>点击查看问题分析和迭代示例</summary>

### 问题清单

1. **localStorage.getItem 可能返回 null**：第一次使用时会报错
2. **没有生成 note id**：后续编辑/删除需要 id
3. **没有记录创建时间**：列表需要按时间排序
4. **表单提交后没有清空输入**：用户体验差
5. **没有表单验证**：空内容也能提交
6. **handleSubmit 没有阻止默认行为**：表单会刷新页面

### 第一轮反馈

```markdown
【问题】
1. localStorage.getItem('notes') 第一次会返回 null，导致 notes.push 报错
2. handleSubmit 没有 e.preventDefault()，表单会刷新页面

【原因】
localStorage 第一次使用时没有数据，JSON.parse(null) 会出错

【期望】
1. 处理 localStorage 为空的情况，初始化为空数组
2. 阻止表单默认提交行为
```

### AI 第二次输出

```typescript
const handleSubmit = (e: React.FormEvent) => {
  e.preventDefault();
  const note = { bookName, content };
  const existingNotes = localStorage.getItem('notes');
  const notes = existingNotes ? JSON.parse(existingNotes) : [];
  notes.push(note);
  localStorage.setItem('notes', JSON.stringify(notes));
}
```

### 第二轮反馈

```markdown
【问题】
1. 笔记没有 id，后续无法编辑或删除特定笔记
2. 没有创建时间，列表无法按时间排序

【期望】
每条笔记需要包含：
- id：使用 Date.now() 或 uuid
- createdAt：创建时间戳
- bookName：书名
- content：内容
```

### AI 第三次输出

```typescript
const handleSubmit = (e: React.FormEvent) => {
  e.preventDefault();
  const note = {
    id: Date.now().toString(),
    bookName,
    content,
    createdAt: new Date().toISOString()
  };
  const existingNotes = localStorage.getItem('notes');
  const notes = existingNotes ? JSON.parse(existingNotes) : [];
  notes.push(note);
  localStorage.setItem('notes', JSON.stringify(notes));
}
```

### 第三轮反馈

```markdown
【问题】
1. 保存成功后没有清空输入框
2. 没有表单验证，书名和内容为空也能提交
3. 没有保存成功的反馈提示

【期望】
1. 保存成功后清空 bookName 和 content
2. 书名和内容都不能为空（去除空格后判断）
3. 保存成功后显示简短提示（如 toast）

【示例】
保存成功后：
- 输入框清空
- 显示「笔记已保存」提示 2 秒后消失
```

### 最终代码

```typescript
const CreateNote = () => {
  const [bookName, setBookName] = useState('');
  const [content, setContent] = useState('');
  const [showToast, setShowToast] = useState(false);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    
    // 表单验证
    if (!bookName.trim() || !content.trim()) {
      return;
    }

    const note = {
      id: Date.now().toString(),
      bookName: bookName.trim(),
      content: content.trim(),
      createdAt: new Date().toISOString()
    };
    
    const existingNotes = localStorage.getItem('notes');
    const notes = existingNotes ? JSON.parse(existingNotes) : [];
    notes.push(note);
    localStorage.setItem('notes', JSON.stringify(notes));
    
    // 清空输入并显示提示
    setBookName('');
    setContent('');
    setShowToast(true);
    setTimeout(() => setShowToast(false), 2000);
  }

  return (
    <>
      <form onSubmit={handleSubmit}>
        <input 
          value={bookName} 
          onChange={e => setBookName(e.target.value)}
          placeholder="书名"
        />
        <textarea 
          value={content} 
          onChange={e => setContent(e.target.value)}
          placeholder="笔记内容"
        />
        <button type="submit">保存</button>
      </form>
      {showToast && <div className="toast">笔记已保存</div>}
    </>
  );
}
```

**迭代要点**：
- 每轮只反馈 2-3 个问题，不要一次性列出所有问题
- 反馈格式：问题 + 原因 + 期望
- 重要问题（如报错）优先处理

</details>


## 练习四：幻觉识别与修正

### 任务说明

假设 AI 给了你以下代码片段。请识别其中的幻觉问题，并说明如何验证和修正。

### AI 输出

```typescript
// AI 推荐使用 super-easy-storage 库来简化 localStorage 操作
import { createStore, useStore } from 'super-easy-storage';

const notesStore = createStore('notes', []);

const CreateNote = () => {
  const [notes, setNotes] = useStore(notesStore);
  
  // 使用 array.findLast() 获取最后一条笔记
  const lastNote = notes.findLast(note => note.bookName === currentBook);
  
  // 使用 React 18 的 useTransition 处理大列表
  const [isPending, startTransition] = useTransition();
  
  const handleSubmit = () => {
    startTransition(() => {
      setNotes([...notes, newNote]);
    });
  }
}
```

### 你的答案

请识别上述代码中的幻觉问题，并说明验证方法。

<details>
<summary>点击查看参考答案</summary>

### 幻觉识别

| 代码位置 | 问题类型 | 问题描述 | 验证方法 |
|---------|---------|---------|---------|
| `super-easy-storage` | 🔴 包幻觉 | 这个包可能不存在 | `npm search super-easy-storage` 或访问 npmjs.com |
| `array.findLast()` | 🟡 API 幻觉 | ES2023 新 API，旧环境不支持 | 查 MDN 文档确认兼容性 |
| `useTransition` | ✅ 正确 | React 18 确实有这个 Hook | 但需确认项目用的是 React 18 |

### 验证步骤

**1. 验证 super-easy-storage 包**

```bash
npm search super-easy-storage
```

结果：没有找到这个包。这是一个**包幻觉**。

**修正方案**：
- 使用真实存在的库，如 `zustand` 或 `jotai`
- 或者自己封装一个简单的 localStorage hook

**2. 验证 findLast() API**

访问 [MDN - Array.findLast()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLast)

结果：
- ES2023 引入
- Chrome 97+、Firefox 104+、Safari 15.4+
- Node.js 18+

**修正方案**：
```typescript
// 兼容写法
const lastNote = [...notes].reverse().find(note => note.bookName === currentBook);

// 或添加 polyfill
```

**3. 验证 useTransition**

访问 [React 文档 - useTransition](https://react.dev/reference/react/useTransition)

结果：React 18 确实有这个 Hook。

**需要确认**：
- 项目的 React 版本是否 >= 18
- 当前场景是否真的需要 useTransition（这里可能是过度优化）

### 修正后的代码

```typescript
// 使用 zustand 替代不存在的 super-easy-storage
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface NotesState {
  notes: Note[];
  addNote: (note: Note) => void;
}

const useNotesStore = create<NotesState>()(
  persist(
    (set) => ({
      notes: [],
      addNote: (note) => set((state) => ({ 
        notes: [...state.notes, note] 
      })),
    }),
    { name: 'notes-storage' }
  )
);

const CreateNote = () => {
  const { notes, addNote } = useNotesStore();
  
  // 使用兼容写法替代 findLast
  const lastNote = [...notes]
    .reverse()
    .find(note => note.bookName === currentBook);
  
  const handleSubmit = () => {
    addNote(newNote);
  }
}
```

### 验证习惯总结

1. **遇到不熟悉的包**：先 npm search 验证存在性
2. **遇到不熟悉的 API**：查阅 MDN 或官方文档
3. **遇到新语法**：确认项目环境是否支持
4. **遇到「高级用法」**：先验证是否真的需要

</details>


## 练习自评表

完成四个练习后，用这个表格自评：

| 练习 | 完成情况 | 主要收获 | 需要加强的地方 |
|-----|---------|---------|---------------|
| 练习一：提示词改写 | □ 完成 □ 部分完成 □ 未完成 | | |
| 练习二：PRD 编写 | □ 完成 □ 部分完成 □ 未完成 | | |
| 练习三：迭代对话 | □ 完成 □ 部分完成 □ 未完成 | | |
| 练习四：幻觉识别 | □ 完成 □ 部分完成 □ 未完成 | | |


## 本节要点

✓ **提示词改写**：用 S.C.A.F.F. 框架把模糊需求转化为结构化提示词

✓ **PRD 编写**：整合第二章思考成果，形成 AI 能理解的任务书

✓ **迭代对话**：每轮反馈 2-3 个问题，格式为「问题 + 原因 + 期望」

✓ **幻觉识别**：对不熟悉的包和 API，先验证再使用


恭喜你完成了第三章的所有学习内容！下一节我们将盘点你学到的技能，并为第四章的实战开发做准备。
