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填空Fill the blanks
难度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

筛出 12 个练习(共 148 个)。Showing 12 of 148.
来自From props:数据往下流,事件往上报props: data flows down, events go back up · React 考试React exam
L2填空Fill the blanks补全 NoteItem 的两个按钮Fill in the two buttons of NoteItem

这是 NoteItem 真实的两个按钮。 一个要传整条笔记,一个只传 id —— 想清楚各自要传什么, 以及怎么才能「点击时才执行」。

These are the two real buttons of NoteItem. One passes the whole note, the other passes only the id. Decide what each one has to pass, and how to make it run only on the click.

TSXsrc/components/NoteItem/index.tsx2 个空2 blanks
1<td>
2 <button onClick={ onEdit(note)} className="outlined">
3 Edit
4 </button>
5</td>
6<td>
7 <button onClick={() => onDelete()} className="danger">
8 Delete
9 </button>
10</td>
把 2 个空都填上才能检查(还差 2 个)Fill all 2 blanks to check (2 to go)
来自From 受控输入:value + onChange 的闭环Controlled inputs: the loop between value and onChange · React 考试React exam
L2填空Fill the blanks补全受控输入的闭环Complete the loop of a controlled input

NoteForm 里 textarea 那一段补全。 三个空构成一个完整的闭环。

Fill in the textarea part of NoteForm. The three blanks together form one complete loop.

TSXsrc/components/NoteForm/index.tsx3 个空3 blanks
1const [content, setContent] = ("");
2
3<textarea
4 placeholder="Content"
5 value={}
6 onChange={(e) => setContent()}
7 data-testid="form-textarea"
8 className="form-textarea"
9/>
把 3 个空都填上才能检查(还差 3 个)Fill all 3 blanks to check (3 to go)
来自From useEffect:把 props 的变化同步进 stateuseEffect: copying a change in props into state · React 考试React exam
L2填空Fill the blanks补全编辑回填的 useEffectComplete the useEffect that prefills the form for editing

这是 NoteForm 里那个决定 Task 3 成败的 effect。 两个空:一个分支条件,一个依赖数组。

This is the effect in NoteForm that decides whether Task 3 passes. Two blanks: one branch condition, one dependency array.

TSXsrc/components/NoteForm/index.tsx2 个空2 blanks
1useEffect(() => {
2 if () {
3 setTitle(noteToEdit.title);
4 setContent(noteToEdit.content);
5 } else {
6 setTitle("");
7 setContent("");
8 }
9}, );
把 2 个空都填上才能检查(还差 2 个)Fill all 2 blanks to check (2 to go)
来自From Task 1 · Add:提交表单,新笔记进入表格Task 1 · Add: submit the form and the new note appears in the table · React 考试React exam
L2填空Fill the blanks补全新增逻辑Fill in the add logic

两个空。想清楚「旧的要不要留」和「用哪种更新形式」。

Two blanks. Decide whether the old notes have to stay, and which form of update to use.

TSXsrc/components/NoteManager/index.tsx2 个空2 blanks
1const handleSubmitNote = (submittedNote: Note) => {
2 if (noteToEdit) {
3 // Task 3
4 } else {
5 setNotes(() => [, submittedNote]);
6 }
7};
把 2 个空都填上才能检查(还差 2 个)Fill all 2 blanks to check (2 to go)
来自From Task 2 · Delete:点 Delete,该行按 id 被移除Task 2 · Delete: click Delete and that one row is removed by id · React 考试React exam
L2填空Fill the blanks补全删除逻辑Fill in the delete logic

三个空。第三个空是这道题唯一会绕人的地方。

Three blanks. The third one is the only part that trips people up.

TSXsrc/components/NoteManager/index.tsx3 个空3 blanks
1const handleDelete = (id: number) => {
2 setNotes((prev) => prev.((note) => note. id));
3};
把 3 个空都填上才能检查(还差 3 个)Fill all 3 blanks to check (3 to go)
来自From Task 3 · Edit:回填、改文字、就地更新、退出编辑Task 3 · Edit: refill the form, change the button text, update the row where it is, leave edit mode · React 考试React exam
L2填空Fill the blanks补全编辑逻辑的四个关键位置Fill in the four key spots of the edit logic

四个空横跨两个函数。第 4 个空是最容易漏的那一行 —— 漏了它测试照样能过,但行为明显不对。

Four blanks across two functions. The fourth is the line people forget most often — without it the tests still pass, but the behavior is clearly wrong.

TSXsrc/components/NoteManager/index.tsx4 个空4 blanks
1const handleSubmitNote = (submittedNote: Note) => {
2 if (noteToEdit) {
3 setNotes((prev) =>
4 prev.((note) =>
5 note.id submittedNote.id ? : note,
6 ),
7 );
8 ;
9 } else {
10 setNotes((prev) => [...prev, submittedNote]);
11 }
12};
13
14const handleEdit = (note: Note) => {
15 setNoteToEdit(note);
16};
把 4 个空都填上才能检查(还差 4 个)Fill all 4 blanks to check (4 to go)
来自From 实现:worker pool(工人池)Building it: a worker pool, meaning a fixed number of workers sharing one queue · React 考试React exam
L2填空Fill the blanks补全 worker pool 的五个关键位置Fill in the five key spots of the worker pool

五个空。第 2 个和第 4 个是最容易写错的 —— 一个关系到「顺序」,一个关系到「任务到底有没有被启动」。

Five blanks. Numbers 2 and 4 are the ones most often written wrong: one decides the order, the other decides whether the task was started at all.

TSq2/taskRunner.ts5 个空5 blanks
1export async function runTasks<T>(
2 tasks: Task<T>[],
3 limit: number,
4): Promise<SettledResult<T>[]> {
5 const results: SettledResult<T>[] = new Array(tasks.length);
6 let nextIndex = 0;
7
8 const worker = async () => {
9 while (nextIndex tasks.length) {
10 const i = nextIndex;
11 nextIndex++;
12
13 try {
14 const value = await ;
15 results[] = { status: "fulfilled", value };
16 } catch (reason) {
17 results[i] = { status: "", reason };
18 }
19 }
20 };
21
22 const workerCount = Math.(limit, tasks.length);
23 const workers: Promise<void>[] = [];
24 for (let w = 0; w < workerCount; w++) {
25 workers.push(worker());
26 }
27 await Promise.all(workers);
28 return results;
29}
把 5 个空都填上才能检查(还差 5 个)Fill all 5 blanks to check (5 to go)
来自From 变式一 · Todo ListVariation 1 · Todo List · React 考试React exam
L2填空Fill the blanks补全翻转与批量操作Fill in the toggle and the bulk actionDrillLab 自出Written by DrillLab

四个空。第 2 个是「只改一个字段」的写法,第 4 个考的是 「全选」和「反选」的区别。

Four blanks. The second is how you change one field only. The fourth is about the difference between select-all and invert-selection.

TSXsrc/components/TodoList/index.tsx4 个空4 blanks
1// 翻转一条:只改 done,其他字段照抄
2const toggle = (id: number) => {
3 setTodos((prev) =>
4 prev.((t) => (t.id === id ? { , done: !t.done } : t)),
5 );
6};
7
8// 剩余几项 —— 派生数据
9const remaining = todos.((t) => !t.done).length;
10
11// 全选 / 取消全选
12const toggleAll = () => {
13 const next = ;
14 setTodos((prev) => prev.map((t) => ({ ...t, done: next })));
15};
把 4 个空都填上才能检查(还差 4 个)Fill all 4 blanks to check (4 to go)
来自From 变式二 · 计时器:useEffect 的清理函数Variation 2 · a timer: the useEffect cleanup function · React 考试React exam
L2填空Fill the blanks补全计时器的 effectFill in the effect of the timerDrillLab 自出Written by DrillLab

三个空,全在这九行里。第 2 个空漏了会「越跳越快」, 第 3 个空写错会「卡在 1 不动」。

Three blanks, all within these nine lines. Miss the second and the clock speeds up with every start. Get the third wrong and the display freezes at 1.

TSXsrc/components/Timer/index.tsx3 个空3 blanks
1useEffect(() => {
2 if (!running) return;
3
4 const id = setInterval(() => {
5 setSeconds();
6 }, 1000);
7
8 return () => (id);
9}, []);
把 3 个空都填上才能检查(还差 3 个)Fill all 3 blanks to check (3 to go)
来自From 变式三 · fetch 取数:loading、error 与竞态Variation 3 · fetching data: loading, error, and the race between two requests · React 考试React exam
L2填空Fill the blanks补全取数 effect 的四个关键位置Fill in the four key spots of the fetching effectDrillLab 自出Written by DrillLab

四个空。第 1 和第 4 个合起来解决竞态,第 2 个是 fetch 的经典坑。

Four blanks. The first and the fourth together settle the race. The second is the classic fetch trap.

TSXsrc/components/UserCard/index.tsx4 个空4 blanks
1useEffect(() => {
2 let = false;
3
4 setLoading(true);
5 setError(null);
6 setUser(null);
7
8 (async () => {
9 try {
10 const res = await fetch(`/api/users/${userId}`);
11 if (!res.) throw new Error(`HTTP ${res.status}`);
12 const data: User = await res.json();
13 if (!ignore) setUser(data);
14 } catch (e) {
15 if (!ignore) setError((e as Error).message);
16 } finally {
17 if (!ignore) setLoading(false);
18 }
19 })();
20
21 return () => { ignore = ; };
22}, []);
把 4 个空都填上才能检查(还差 4 个)Fill all 4 blanks to check (4 to go)
来自From 变式四 · 递归读取评论的评论Variation 4 · reading replies to replies with recursion · React 考试React exam
L2填空Fill the blanks补全递归统计与递归渲染Fill in the recursive count and the recursive renderDrillLab 自出Written by DrillLab

四个空。第 2 个是递归调用本身,第 4 个是「往下一层」。

Four blanks. The second is the recursive call itself, and the fourth is one level further down.

TSXsrc/components/CommentTree/index.tsx4 个空4 blanks
1// 递归统计总条数
2export function countComments(nodes: Comment[]): number {
3 return nodes.reduce((sum, n) => sum + + (n.replies), 0);
4}
5
6// 递归渲染
7{comment.replies.length > 0 && (
8 <ul>
9 {comment.replies.map((child) => (
10 <
11 key={child.id}
12 comment={child}
13 depth={}
14 onReply={onReply}
15 />
16 ))}
17 </ul>
18)}
把 4 个空都填上才能检查(还差 4 个)Fill all 4 blanks to check (4 to go)
来自From 变式五 · 主题切换:Context 怎么用Variation 5 · theme switching: how to use Context · React 考试React exam
L2填空Fill the blanks补全 ThemeContext 的四个关键位置Fill in the four key spots of ThemeContextDrillLab 自出Written by DrillLab

四个空。第 3 个是最容易漏的那一步,第 4 个决定「忘了套 Provider」 时报错清不清楚。

Four blanks. The third is the step people miss most. The fourth decides how clear the error is when somebody forgets to wrap things in the Provider.

TSXsrc/context/ThemeContext.tsx4 个空4 blanks
1const ThemeContext = <ThemeContextValue | undefined>(undefined);
2
3export function ThemeProvider({ children }: { children: ReactNode }) {
4 const [theme, setTheme] = useState<Theme>("light");
5
6 const toggleTheme = useCallback(() => {
7 setTheme();
8 }, []);
9
10 const value = (() => ({ theme, toggleTheme }), [theme, toggleTheme]);
11
12 return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
13}
14
15export function useTheme(): ThemeContextValue {
16 const ctx = useContext(ThemeContext);
17 if () throw new Error("useTheme 必须在 <ThemeProvider> 里面用");
18 return ctx;
19}
把 4 个空都填上才能检查(还差 4 个)Fill all 4 blanks to check (4 to go)