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

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

练习Exercises

筛出 21 个练习(共 148 个) · 第 2 / 2 页。Showing 21 of 148 · page 2 / 2.
来自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 变式二 · 计时器:useEffect 的清理函数Variation 2 · a timer: the useEffect cleanup function · React 考试React exam
L2Debug LabDebug LabDebug Lab · 计时器越跑越快Debug Lab · the timer keeps getting fasterDrillLab 自出Written by DrillLab

点了几次 Start / Pause 之后,秒数开始一次跳好几秒。 下面是真实的测试输出。

After a few clicks of Start and Pause, the seconds start jumping several at a time. Below is the real test output.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
$ npx vitest run src/Timer.test.tsx ✕ pause stops the clock and keeps the value Expected element to have text content: 00:02 Received: 00:07 ✕ start/pause many times does not speed up Expected element to have text content: 00:04 Received: 00:10 ✕ reset stops and zeroes Expected element to have text content: 00:00 Received: 00:03 ✕ unmount clears the interval AssertionError: expected 1 to be +0 // Object.is equality Tests 4 failed | 4 passed (8) # 现象:start / pause 来回点四次,每次只走 1 秒, # 显示却是 00:10 —— 正好是 1+2+3+4。 # 而且 Reset 之后秒数还在自己往上涨。$ npx vitest run src/Timer.test.tsx ✕ pause stops the clock and keeps the value Expected element to have text content: 00:02 Received: 00:07 ✕ start/pause many times does not speed up Expected element to have text content: 00:04 Received: 00:10 ✕ reset stops and zeroes Expected element to have text content: 00:00 Received: 00:03 ✕ unmount clears the interval AssertionError: expected 1 to be +0 // Object.is equality Tests 4 failed | 4 passed (8) # Symptom: click start / pause four times, one second of running each time, # and the display reads 00:10 — exactly 1+2+3+4. # After Reset the seconds also keep climbing on their own.
TSXsrc/components/Timer/index.tsx示意Illustrative
1useEffect(() => {
2 if (!running) return;
3
4 const id = setInterval(() => {
5 setSeconds((s) => s + 1);
6 }, 1000);
7}, [running]);
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自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)
来自From 变式五 · 主题切换:Context 怎么用Variation 5 · theme switching: how to use Context · React 考试React exam
L2Debug LabDebug LabDebug Lab · Cannot destructure property 'theme'DrillLab 自出Written by DrillLab

按钮好好的,卡片一渲染就整页白屏。这是真实报错。

The button is fine, and the moment the card renders the whole page goes blank. This is the real error.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
$ npx vitest run src/Theme.test.tsx TypeError: Cannot destructure property 'theme' of '(0 , __vite_ssr_import_1__.useTheme)(...)' as it is undefined. at ThemedCard (src/components/ThemedCard/index.tsx:6:11) at ThemeApp ✕ 默认是 light,按钮说 Switch to Dark ✕ 点一下变 dark:按钮文字和卡片底色一起变 ✕ 再点一下切回 light ✕ 没套 Provider 就用 useTheme(),必须立刻报错 ✕ toggleTheme 是稳定引用:theme 变了它也不变 Tests 5 failed | 3 passed (8) # 浏览器里的报错略有不同,意思一样: # Cannot destructure property 'theme' of 'useTheme(...)' as it is undefined.$ npx vitest run src/Theme.test.tsx TypeError: Cannot destructure property 'theme' of '(0 , __vite_ssr_import_1__.useTheme)(...)' as it is undefined. at ThemedCard (src/components/ThemedCard/index.tsx:6:11) at ThemeApp ✕ 默认是 light,按钮说 Switch to Dark ✕ 点一下变 dark:按钮文字和卡片底色一起变 ✕ 再点一下切回 light ✕ 没套 Provider 就用 useTheme(),必须立刻报错 ✕ toggleTheme 是稳定引用:theme 变了它也不变 Tests 5 failed | 3 passed (8) # The browser wording is slightly different but means the same thing: # Cannot destructure property 'theme' of 'useTheme(...)' as it is undefined.
TSXsrc/components/ThemeApp/index.tsx示意Illustrative
1// ThemeContext.tsx
2const ThemeContext = createContext<ThemeContextValue>(undefined as never);
3
4export function useTheme() {
5 return useContext(ThemeContext); // 没有守卫
6}
7
8// ThemeApp/index.tsx
9const ThemeApp: React.FC = () => (
10 <>
11 <ThemeProvider>
12 <ThemeToggleButton />
13 </ThemeProvider>
14 <ThemedCard />
15 </>
16);
1// ThemeContext.tsx
2const ThemeContext = createContext<ThemeContextValue>(undefined as never);
3
4export function useTheme() {
5 return useContext(ThemeContext); // no guard
6}
7
8// ThemeApp/index.tsx
9const ThemeApp: React.FC = () => (
10 <>
11 <ThemeProvider>
12 <ThemeToggleButton />
13 </ThemeProvider>
14 <ThemedCard />
15 </>
16);
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From Debug Lab · React 十种典型故障Debug Lab · ten typical React failures · React 考试React exam
L2Debug LabDebug Lab故障 1 · 路径大小写Fault 1 · upper and lower case in a path

新建了组件之后启动开发服务器,Vite 直接报错,页面白屏。

You add a new component, start the dev server, and Vite reports an error right away. The page is blank.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
[plugin:vite:import-analysis] Failed to resolve import "./components/notemanager" from "src/App.tsx". Does the file exist? /Users/me/react-notes-app/src/App.tsx:1:24 1 | import NoteManager from "./components/notemanager"; | ^
TSXsrc/App.tsx示意Illustrative
1import NoteManager from "./components/notemanager";
2
3function App() {
4 return <NoteManager />;
5}
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From Debug Lab · React 十种典型故障Debug Lab · ten typical React failures · React 考试React exam
L2Debug LabDebug Lab故障 2 · props 名字对不上Fault 2 · the prop names do not match

重构时把父组件传的 prop 名改了,子组件忘了跟着改。 页面能显示,但点 Delete 直接崩。

During a refactor the prop name passed by the parent was changed, and the child component was not changed to match. The page still renders, but clicking Delete crashes it.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
Uncaught TypeError: onDelete is not a function at onClick (NoteItem/index.tsx:18:29) at HTMLUnknownElement.callCallback # 另外 TypeScript 那边也在报: src/components/NoteTable/index.tsx(20,7): error TS2322: Type '{ key: number; note: Note; onRemove: (id: number) => void; onEdit: ... }' is not assignable to type 'IntrinsicAttributes & NoteItemProps'. Property 'onDelete' is missing in type ... but required in type 'NoteItemProps'.Uncaught TypeError: onDelete is not a function at onClick (NoteItem/index.tsx:18:29) at HTMLUnknownElement.callCallback # TypeScript is reporting something too: src/components/NoteTable/index.tsx(20,7): error TS2322: Type '{ key: number; note: Note; onRemove: (id: number) => void; onEdit: ... }' is not assignable to type 'IntrinsicAttributes & NoteItemProps'. Property 'onDelete' is missing in type ... but required in type 'NoteItemProps'.
TSX两处不一致The two places that disagree示意Illustrative
1// NoteTable 里传下去的名字:
2<NoteItem
3 key={note.id}
4 note={note}
5 onRemove={onDelete} // ← 传的是 onRemove
6 onEdit={onEdit}
7/>
8
9// NoteItem 的 props 接口和解构:
10export interface NoteItemProps {
11 note: Note;
12 onDelete: (id: number) => void; // ← 期望的是 onDelete
13 onEdit: (note: Note) => void;
14}
15const NoteItem: React.FC<NoteItemProps> = ({ note, onDelete, onEdit }) => {
1// The name NoteTable passes down:
2<NoteItem
3 key={note.id}
4 note={note}
5 onRemove={onDelete} // ← it passes onRemove
6 onEdit={onEdit}
7/>
8
9// The props interface of NoteItem, and how it destructures them:
10export interface NoteItemProps {
11 note: Note;
12 onDelete: (id: number) => void; // ← it expects onDelete
13 onEdit: (note: Note) => void;
14}
15const NoteItem: React.FC<NoteItemProps> = ({ note, onDelete, onEdit }) => {
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From Debug Lab · React 十种典型故障Debug Lab · ten typical React failures · React 考试React exam
L2Debug LabDebug Lab故障 3 · 测试找不到元素Fault 3 · the test cannot find the element

代码看起来完全正确,手动点也没问题,但两个测试挂了。

The code looks entirely correct and clicking through it by hand works, but two tests fail.

第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
FAIL src/NoteManager.test.tsx > adds a note TestingLibraryElementError: Unable to find an element by: [data-testid="form-input"] Ignored nodes: comments, script, style <body> <div> <div class="layout-column ..." data-testid="note-manager"> <div class="card ..."> <form data-testid="note-form"> <section class="layout-row ..."> <label class="form-title-label">Title:</label> <input type="text" placeholder="Title" data-testid="title-input" ... /> ...
TSXsrc/components/NoteForm/index.tsx示意Illustrative
1<input
2 type="text"
3 placeholder="Title"
4 value={title}
5 onChange={(e) => setTitle(e.target.value)}
6 data-testid="title-input"
7 className="form-input"
8/>
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this