3.7.4 实战演练:综合练习
本节提供四个综合练习,帮助你内化本章所学技能。建议认真完成每个练习,然后对照参考答案自评。
练习一:提示词改写
任务说明
下面有三个「差提示词」,请用 S.C.A.F.F. 框架改写成「好提示词」。
差提示词 1
帮我做一个登录页面你的改写:
(请先自己尝试改写,再看参考答案)
点击查看参考答案
【Situation - 情境】
我正在开发一个个人博客的管理后台。
技术栈:Next.js 14 + 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 分以上为优秀。
差提示词 2
帮我分析一下这段代码有什么问题你的改写:
点击查看参考答案
【当前代码】
```typescript
const fetchUsers = async () => {
const response = await fetch('/api/users');
const data = response.json();
setUsers(data);
}【问题现象】 调用这个函数后,users 状态是一个 Promise 对象而不是数组,导致渲染时报错。
【期望行为】 users 应该是从 API 获取的用户数组
【请帮我】
- 指出代码中的问题
- 解释为什么会出现这个问题
- 给出修正后的代码
- 说明如何避免类似错误
【关于我】 我是 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 分以上为优秀。
练习二:PRD 编写实战
任务说明
假设你想做一个「读书笔记」工具,帮助自己记录读书心得。
请整合第二章的思维工具,完成以下内容:
- 用 JTBD 格式描述用户任务
- 用减法思维确定 MVP 范围(P0/P1/不做)
- 回答灵魂三问
- 写出一份精简版 PRD
你的答案
(请先自己完成,再看参考答案)
点击查看参考答案
1. JTBD 任务描述
当我读完一本书的某个章节时,我想快速记录关键观点和我的思考,这样我在需要回顾时能够快速找到这些笔记,而不是重新翻书。
2. MVP 范围
| 优先级 | 功能 | 原因 |
|---|---|---|
| P0 | 创建笔记(书名 + 章节 + 内容) | 核心功能 |
| P0 | 查看所有笔记列表 | 核心功能 |
| P0 | 按书名筛选笔记 | 找笔记必须快 |
| P1 | 编辑已有笔记 | 经常需要修改补充 |
| P1 | 删除笔记 | 清理无用笔记 |
| 不做 | 笔记标签/分类 | 增加复杂度,MVP 不需要 |
| 不做 | 笔记分享 | 个人使用,不需要 |
| 不做 | Markdown 编辑器 | 简单文本足够 |
| 不做 | 书籍信息自动获取 | 手动输入书名即可 |
3. 灵魂三问
| 问题 | 回答 |
|---|---|
| 用户是谁? | 我自己——喜欢读书但经常忘记书中内容的人 |
| 痛点在哪? | 读过的书记不住,需要时找不到当时的思考 |
| 为什么做这个? | 现有工具(Notion、备忘录)要么太重,要么不好按书检索 |
4. 精简版 PRD
# 读书笔记工具 PRD
## 项目背景
### 问题
我喜欢读书,但经常读完就忘。需要回顾时,要么重新翻书,要么在各种 app 里找当时的笔记,很费时间。
### JTBD
当我读完一本书的某个章节时,我想快速记录关键观点和我的思考,这样我在需要回顾时能够快速找到这些笔记。
## 目标用户
- 身份:喜欢读书的个人用户
- 场景:读完一章后记录、需要引用时检索
- 核心需求:快速记录 + 快速检索
## P0 功能
### 功能1:创建笔记
- 必填:书名、笔记内容
- 选填:章节名、页码
- 交互:打开页面直接可以开始写
### 功能2:查看笔记列表
- 按创建时间倒序显示
- 每条显示:书名、章节、内容摘要、时间
- 点击可查看完整内容
### 功能3:按书名筛选
- 页面顶部有书名筛选器
- 选择后只显示该书的笔记
## 技术方案
- 前端:React + TypeScript + Tailwind CSS
- 数据:localStorage(个人使用,不需要后端)
- 响应式:支持手机和电脑
## 不做清单
- 不做笔记标签/分类
- 不做笔记分享功能
- 不做 Markdown 编辑器
- 不做书籍封面/信息自动获取
- 不做多端同步
## 成功标准
自己使用 2 周,记录至少 10 条笔记,并成功检索到之前的笔记。自评标准:
- ✅ JTBD 描述完整(场景 + 任务 + 价值)
- ✅ MVP 范围有优先级,有「不做清单」
- ✅ 灵魂三问都能回答
- ✅ PRD 结构完整,AI 能理解执行
练习三:迭代对话模拟
任务说明
假设你让 AI 帮你实现「读书笔记」的创建功能,AI 给了你以下代码,但有问题。
请模拟 3 轮迭代对话,逐步修正问题。
AI 第一次输出
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>
);
}问题识别
请找出这段代码的问题,然后写出你的反馈。
点击查看问题分析和迭代示例
问题清单
- localStorage.getItem 可能返回 null:第一次使用时会报错
- 没有生成 note id:后续编辑/删除需要 id
- 没有记录创建时间:列表需要按时间排序
- 表单提交后没有清空输入:用户体验差
- 没有表单验证:空内容也能提交
- handleSubmit 没有阻止默认行为:表单会刷新页面
第一轮反馈
【问题】
1. localStorage.getItem('notes') 第一次会返回 null,导致 notes.push 报错
2. handleSubmit 没有 e.preventDefault(),表单会刷新页面
【原因】
localStorage 第一次使用时没有数据,JSON.parse(null) 会出错
【期望】
1. 处理 localStorage 为空的情况,初始化为空数组
2. 阻止表单默认提交行为AI 第二次输出
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));
}第二轮反馈
【问题】
1. 笔记没有 id,后续无法编辑或删除特定笔记
2. 没有创建时间,列表无法按时间排序
【期望】
每条笔记需要包含:
- id:使用 Date.now() 或 uuid
- createdAt:创建时间戳
- bookName:书名
- content:内容AI 第三次输出
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));
}第三轮反馈
【问题】
1. 保存成功后没有清空输入框
2. 没有表单验证,书名和内容为空也能提交
3. 没有保存成功的反馈提示
【期望】
1. 保存成功后清空 bookName 和 content
2. 书名和内容都不能为空(去除空格后判断)
3. 保存成功后显示简短提示(如 toast)
【示例】
保存成功后:
- 输入框清空
- 显示「笔记已保存」提示 2 秒后消失最终代码
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 个问题,不要一次性列出所有问题
- 反馈格式:问题 + 原因 + 期望
- 重要问题(如报错)优先处理
练习四:幻觉识别与修正
任务说明
假设 AI 给了你以下代码片段。请识别其中的幻觉问题,并说明如何验证和修正。
AI 输出
// 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]);
});
}
}你的答案
请识别上述代码中的幻觉问题,并说明验证方法。
点击查看参考答案
幻觉识别
| 代码位置 | 问题类型 | 问题描述 | 验证方法 |
|---|---|---|---|
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 包
npm search super-easy-storage结果:没有找到这个包。这是一个包幻觉。
修正方案:
- 使用真实存在的库,如
zustand或jotai - 或者自己封装一个简单的 localStorage hook
2. 验证 findLast() API
结果:
- ES2023 引入
- Chrome 97+、Firefox 104+、Safari 15.4+
- Node.js 18+
修正方案:
// 兼容写法
const lastNote = [...notes].reverse().find(note => note.bookName === currentBook);
// 或添加 polyfill3. 验证 useTransition
结果:React 18 确实有这个 Hook。
需要确认:
- 项目的 React 版本是否 >= 18
- 当前场景是否真的需要 useTransition(这里可能是过度优化)
修正后的代码
// 使用 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);
}
}验证习惯总结
- 遇到不熟悉的包:先 npm search 验证存在性
- 遇到不熟悉的 API:查阅 MDN 或官方文档
- 遇到新语法:确认项目环境是否支持
- 遇到「高级用法」:先验证是否真的需要
练习自评表
完成四个练习后,用这个表格自评:
| 练习 | 完成情况 | 主要收获 | 需要加强的地方 |
|---|---|---|---|
| 练习一:提示词改写 | □ 完成 □ 部分完成 □ 未完成 | ||
| 练习二:PRD 编写 | □ 完成 □ 部分完成 □ 未完成 | ||
| 练习三:迭代对话 | □ 完成 □ 部分完成 □ 未完成 | ||
| 练习四:幻觉识别 | □ 完成 □ 部分完成 □ 未完成 |
本节要点
✓ 提示词改写:用 S.C.A.F.F. 框架把模糊需求转化为结构化提示词
✓ PRD 编写:整合第二章思考成果,形成 AI 能理解的任务书
✓ 迭代对话:每轮反馈 2-3 个问题,格式为「问题 + 原因 + 期望」
✓ 幻觉识别:对不熟悉的包和 API,先验证再使用
恭喜你完成了第三章的所有学习内容!下一节我们将盘点你学到的技能,并为第四章的实战开发做准备。
