DrillLab
练习Practice

动手做Get your hands on it

练习跟着课文走 —— 每节课尾都有本课的练习。这一页是全部练习的总库,想集中刷题的时候来。 每个练习都写清了它来自哪一节,卡住了就回去看那一节。Practice follows the lessons — every lesson ends with the exercises for that lesson. This page is the whole library, for when you want to drill in one sitting. Each exercise names the lesson it came from, so you can go back when you stall.

0 / 148个做对过you got right
筛一下Filter theseReact 考试React exam从零重写Rebuild from scratch
难度LevelL1 → L4 和上面四档同一个意思:给你的东西越来越少L1 → L4: the same idea as the four tiers above — less is handed to you全部难度All levelsL1L1L2L2L3L3L4L4

已筛到你正在学的《React 考试》。想看全部就点上面的「全部」。Filtered to React exam — the course you are on. Use “All” above to see everything.

练习Exercises

筛出 2 个练习(共 148 个)。Showing 2 of 148.
来自From 从零重写:空文件夹到 4 个测试全过Write it again yourself: from an empty folder to 4 passing tests · React 考试React exam
L4从零重写Rebuild from scratch从零重建 Q1 · Notes ManagerRebuild Q1 · Notes Manager

空目录开始,建出一个 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.

需求(不给代码,自己实现)Requirements — no code given, implement it yourself
  • 页面上方是表单: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 }
你需要自己建的文件Files you create yourself
文件清单File list
package.json自己写 scripts 与依赖(react / react-dom / vite / @vitejs/plugin-react / typescript / vitest / jsdom / @testing-library/*)You write the scripts and dependencies (react / react-dom / vite / @vitejs/plugin-react / typescript / vitest / jsdom / @testing-library/*)
index.html一个 <div id="root"> 加一行 module scriptOne <div id="root"> plus one module script line
tsconfig.jsonstrict、jsx: react-jsx、moduleResolution: bundlerstrict, jsx: react-jsx, moduleResolution: bundler
vite.config.tsReact 插件 + 内联 vitest 配置(environment: jsdom、globals、setupFiles)The React plugin plus an inline vitest config (environment: jsdom, globals, setupFiles)
vitest.setup.tsimport "@testing-library/jest-dom"
src/main.tsxcreateRoot().render(<App />)
src/App.tsx渲染顶层组件Renders the top-level component
src/types/Note.tsNote 类型The Note type
src/components/NoteManager/index.tsx★ 状态所有者:notes + noteToEdit + 三个 handler★ The state owner: notes + noteToEdit + three handlers
src/components/NoteForm/index.tsx★ 受控表单、编辑回填、Add/Update 切换、提交时 id 的取舍★ The controlled form, filling values back for an edit, switching Add and Update, and choosing the id on submit
src/components/NoteTable/index.tsx表格骨架 + map + notes-list 的 testidThe table skeleton, the map, and the notes-list data-testid
src/components/NoteItem/index.tsx单行 + Edit / Delete 按钮One row plus the Edit and Delete buttons
src/NoteManager.test.tsx把四个测试抄进来当判卷器(见下方参考答案区)Copy the four tests in and let them grade you (see the reference answer area below)
写完后在本机这样验证Verify it locally like this
npm install
装完依赖,node_modules 与 package-lock.json 出现The dependencies install, and node_modules and package-lock.json appear
npm run dev
打开提示的 localhost 地址,能看到表单和空表格Open the localhost address it prints, and you see the form and an empty table
npx vitest run
Test Files 1 passed (1) / Tests 4 passed (4)
npm run dev
手动验证三件事:① 加三条同名笔记,删中间那条,只消失一条 ② 编辑中间那条,它还在第二行 ③ 更新完按钮回到 Add、表单清空Check three things by hand: ① add three notes with the same name, delete the middle one, and only one disappears ② edit the middle one and it is still on the second row ③ after the update the button goes back to Add and the form clears
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

这一关的意义就在于「没有答案也能写出来」。请确认你已经在本机建好文件、跑过验证命令,再打开参考答案对照。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.

Tick this once you have written it locally and the verification commands give the expected output.
来自From 从零重写:空文件夹到 4 个测试全过Write it again yourself: from an empty folder to 4 passing tests · React 考试React exam
L4从零重写Rebuild from scratch从零重建 Q2 · 并发任务调度器Rebuild Q2 · the concurrent task runner

只给类型定义和三条要求。自己写出 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.

需求(不给代码,自己实现)Requirements — no code given, implement it yourself
  • 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
你需要自己建的文件Files you create yourself
文件清单File list
package.json装 tsx 和 typescript,加一条跑 demo 的 scriptInstall tsx and typescript, and add one script that runs the demo
tsconfig.jsonstrict: true 就够了strict: true is enough
q2/taskRunner.ts★ Task / SettledResult 类型 + runTasks 实现★ The Task / SettledResult types plus the runTasks implementation
q2/demo.ts★ 自己写验证台:一个 running 计数器 + 6 个任务 + 打印★ Write the check harness yourself: one running counter, 6 tasks, and the printing
写完后在本机这样验证Verify it locally like this
npm install
装好 tsx 和 typescripttsx and typescript are installed
npm run q2
输出里 running now 从不超过 2;最终 6 条结果顺序与输入一致;reject 的那条是 { status: 'rejected', reason: Error }running now never goes above 2 in the output; the final 6 results are in the same order as the input; the rejected one is { status: 'rejected', reason: Error }
npx tsc --noEmit
没有类型错误No type errors
提示Hints共 4 级,已看 0 级4 levels, 0 opened
先自己想两分钟。想不出来再点右上角 —— 提示是一级一级放的,不会一次给完。Think for two minutes first. Then use the button above — hints come one level at a time, never all at once.

这一关的意义就在于「没有答案也能写出来」。请确认你已经在本机建好文件、跑过验证命令,再打开参考答案对照。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.

Tick this once you have written it locally and the verification commands give the expected output.