从零重写:空文件夹到 4 个测试全过Write it again yourself: from an empty folder to 4 passing tests
不给答案。给需求、文件清单、验证命令和四级提示。这一关是分界线。No answer is given. You get the requirements, the file list, the commands to check your work, and hints in four levels. This lesson is the dividing line.
这一页有什么On this page4
- 在没有参考代码的情况下从空文件建出整个项目Build the whole project from empty files, with no code to copy from
- 自己把 React 项目的构建与测试配置搭起来Set up the build and test configuration of a React project yourself
- 独立实现 Q1 三个任务和 Q2 调度器Implement the three Q1 tasks and the Q2 task runner without help
- 用测试和手动场景验证自己的实现Check your implementation with the tests and by trying it by hand
填空和跟写只能证明你「看懂了」。真正的考试是打开一个空编辑器。这一关就是模拟那个时刻 —— 而且它比真实考试更难,因为连脚手架都要你自己搭。Filling in blanks and copying along only proves you followed the explanation. The real exam starts with an empty editor. This lesson recreates that moment, and it is harder than the real exam, because here you also set up the project yourself.
react-notes-app/参考项目 —— 做完之后再对照,不要提前看The reference project — compare against it after you finish; do not look early
react-notes-app/为什么必须做这一关Why you have to do this lesson
读代码用的是识别能力,写代码用的是生成能力。两者不是一回事。Reading code means recognising it. Writing code means producing it. These are two different abilities.
看着答案点头「嗯,这里用 map」很容易。 面对空文件想起「我该建几个文件、state 放哪、 useEffect 的依赖写什么」,是完全不同的一件事。
前面几节课里,你已经在 L3 练习里分别写过handleSubmitNote、handleDelete、runTasks。这一关是把它们放回一个完整项目里, 加上你自己搭的配置、你自己划分的组件。
不要跳过这一关直接看参考答案。答案就在那个门后面,跑不掉。先自己撞一遍墙 —— 撞墙的地方才是你真正的薄弱点。
Nodding at an answer and thinking “right, map goes here” is easy. Facing an empty file and recalling how many files to create, where the state goes, what the useEffect dependency should be — that is an entirely different thing.
In the earlier lessons you already wrote handleSubmitNote, handleDelete and runTasks in the L3 exercises. This stage puts them back into a whole project, on top of config you set up and components you divided yourself.
Do not skip this stage and jump to the reference answer. The answer is behind that door and it is not going anywhere. Walk into the wall yourself first — where you hit the wall is where you are actually weak.
建议的做法A suggested way to work through it
- 新建一个目录,不要在 react-notes-app 里改。比如
~/Downloads/my-notes-manager。 源项目要留着最后对照。 - 先把项目跑起来再写业务。
npm init→ 装依赖 → 建index.html/main.tsx/App.tsx→npm run dev看到「Hello」再往下走。先让空架子能跑,是所有项目的正确起手式。 - 把测试文件先抄进去。它是你的判卷器。有它在,你随时知道离终点多远。
- 一个测试一个测试地攻。先让第 1 个过(Add),再第 2 个(disabled), 依次推进。别想一次写完所有东西。
- 卡住超过 15 分钟再看提示。提示是四级递进的,从「想什么方向」到「给局部代码」。
- 最后做手动三场景验证。测试全过之后,加三条同名笔记、删中间、编辑中间 —— 这一步才是真正证明你做对了。
- Make a fresh directory. Do not edit inside react-notes-app. Something like
~/Downloads/my-notes-manager. Keep the source project intact so you can compare at the end. - Get the project running before writing any features.
npm init→ install dependencies → createindex.html/main.tsx/App.tsx→npm run devand only move on once you see “Hello”. Getting an empty shell to run first is the correct opening move for any project. - Copy the test file in first. It is your grader. With it in place you always know how far the finish line is.
- Attack one test at a time. Get the first one green (Add), then the second (disabled), and keep going. Do not try to write everything at once.
- Only open a hint after 15 minutes stuck. The hints escalate through four levels, from “which direction to think” to “here is part of the code”.
- Finish with the three manual scenarios. Once the tests are all green, add three same-named notes, delete the middle one, edit the middle one — that step is what actually proves you got it right.
动手做Get your hands on it
填空只是过渡。真正掌握的标准,是在没有答案的时候从头写出来 —— 所以做完 L2 之后一定要往 L3、L4 走。Filling blanks is a stepping stone. The real bar is writing it from nothing, so once L2 is comfortable, push on to L3 and L4.
空目录开始,建出一个 React + TypeScript + Vite 项目, 实现 Notes Manager 的增删改,让下面那四个测试全过。不要打开 react-notes-app 参考。
Starting from an empty directory, build a React + TypeScript + Vite project. Implement add, delete and edit in Notes Manager, and make all four tests below pass. Do not open react-notes-app to look.
- 页面上方是表单:Title 输入框、Content 文本域、一个提交按钮The form sits at the top of the page: a Title input, a Content textarea, and one submit button
- 页面下方是表格:表头 Title / Content / Edit / Delete,每条笔记一行The table sits below: the header is Title / Content / Edit / Delete, with one row per note
- 两个输入框都必须是受控的(value + onChange)Both inputs must be controlled (value + onChange)
- 标题或内容为空(含只有空格)时,提交按钮 disabledWhen the title or the content is empty (including only spaces), the submit button is disabled
- Task 1 Add:提交后新笔记出现在表格末尾,原有的都还在Task 1 Add: after submit the new note appears at the end of the table, and every existing note is still there
- Task 2 Delete:点某行的 Delete,该行按 id 被移除(同名笔记只删对的那条)Task 2 Delete: clicking Delete on a row removes that row by id (with notes of the same name, only the right one goes)
- Task 3 Edit:点某行的 Edit → 内容回填进表单、按钮文字变成 UpdateTask 3 Edit: clicking Edit on a row fills its content back into the form, and the button text becomes Update
- Task 3 提交后:该笔记在原位置被更新(顺序不变),然后退出编辑模式(表单清空、按钮回到 Add)Task 3 after submit: the note is updated in place (the order does not change), and edit mode ends (the form clears, the button goes back to Add)
- 必须带上这些 data-testid:note-manager / note-form / form-input / form-textarea / form-submit-button / notes-listThese data-testid values are required: note-manager / note-form / form-input / form-textarea / form-submit-button / notes-list
- 行内按钮的文字必须正好是 Edit 和 DeleteThe text on the row buttons must be exactly Edit and Delete
- Note 的类型是 { id: number; title: string; content: string }The type of Note is { id: number; title: string; content: string }
这一关的意义就在于「没有答案也能写出来」。请确认你已经在本机建好文件、跑过验证命令,再打开参考答案对照。The whole point of this level is writing it with no answer in front of you. Create the files on your machine and run the verification commands first, then come back and compare.
只给类型定义和三条要求。自己写出 runTasks, 并自己写一个验证台来证明它对。
You get only the type definitions and three requirements. Write runTasks yourself, and write your own check harness to show that it is right.
- runTasks(tasks, limit) 接收一个「函数数组」,每个函数被调用后返回 PromiserunTasks(tasks, limit) takes an array of functions, and each function returns a Promise when it is called
- 同一时刻最多 limit 个任务在运行;某个结束后立刻启动下一个At most limit tasks run at the same time; as soon as one finishes, start the next
- 任何任务失败都不能让 runTasks 抛错No failing task may make runTasks throw
- 返回数组顺序必须与 tasks 一致The order of the returned array must match tasks
- 成功写 { status: "fulfilled", value },失败写 { status: "rejected", reason }On success write { status: "fulfilled", value }; on failure write { status: "rejected", reason }
- 自己写一个 demo:6 个任务(其中至少 1 个 reject)、limit = 2,打印实时并发数与最终结果Write your own demo: 6 tasks (at least 1 of which rejects), limit = 2, printing how many run at each moment and the final results
这一关的意义就在于「没有答案也能写出来」。请确认你已经在本机建好文件、跑过验证命令,再打开参考答案对照。The whole point of this level is writing it with no answer in front of you. Create the files on your machine and run the verification commands first, then come back and compare.
换一道题也能用Works on other problems too
考试不会原题重考。真正能带走的是「看到这种信号 → 伸手去拿这个解法」。The exam will not reuse the same question. What you take away is the reflex: see this signal, reach for that solution.
- 识别能力和生成能力是两回事 —— 只有从空文件写过,才算真会。Recognising code and producing code are two different abilities. You only really know it once you have written it from empty files.
- 起手式:先让空架子跑起来(能看到 Hello),再写业务逻辑。Opening move: get the empty project running first, so you can see Hello on screen, then write the features.
- 有测试就先抄进来,它是你唯一客观的进度条。If there is a test, copy it in first. It is your only objective progress bar.
- 卡住 15 分钟再看提示,提示是四级递进的。Stay stuck for 15 minutes before opening a hint. The hints go in four levels, each one more specific.
- 最后一定要手动验证「按 id」「原位置」「退出编辑模式」这三条。At the end, check three things by hand: it edits by id, the note stays in place, and edit mode closes.