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

练习Exercises

筛出 148 个练习 · 第 13 / 13 页。Showing 148 · page 13 / 13.
来自From 历史与确认页:两个小而致命的细节The history and confirmation pages: two small details that decide pass or fail · Cab BookingCab Booking
L3写整块Write a block从零写出 RideHistoryWrite RideHistory from an empty file
从 Context 读历史,取最新三条、最新在最上, 空的时候显示空状态。检查器会挡住原地修改。Read the history out of the Context, take the three newest with the newest at the top, and show the empty state when there is nothing. The checker blocks changes made in place.
要求Requirements
  • 从 useCabContext() 里读 rideHistoryRead rideHistory out of useCabContext()
  • 取最新三条并让最新的排在最上面(slice(-3).reverse() 或等价写法)Take the three newest and put the newest at the top (slice(-3).reverse() or an equivalent)
  • 不许在 rideHistory 上直接调 reverse / sort —— 那会原地改 stateNever call reverse or sort on rideHistory directly — that changes the state in place
  • 每条记录一个 <li data-testid="history-cabs">,里面有车名和 $价格One <li data-testid="history-cabs"> per record, holding the cab name and the $price
  • 空历史显示 <p data-testid="no-ride-title">No ride history yet.</p>,且此时不渲染列表An empty history shows <p data-testid="no-ride-title">No ride history yet.</p> and renders no list
  • key 不能只用 ride.id —— 同一辆车可以被订两次The key cannot be ride.id alone — the same cab can be booked twice
JSXsrc/components/Home/RideHistory.jsx
This check is textual: it looks for the right constructs, it does not run your code
提示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.

看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。Before you open this, make sure you have written it yourself once. Following someone else's answer and producing your own are two different skills.

来自From 完整答案跑不起来 —— 一个扩展名的事The complete answer does not run — the cause is one file extension · Cab BookingCab Booking
L2Debug LabDebug LabDebug Lab:0 个测试跑起来Debug Lab: zero tests run
README 说「先运行完整答案熟悉流程」。npm install 成功,npx vitest run 却是下面这个输出。注意最后一行的「no tests」。The README says to run the finished answer first to get used to the flow. npm install succeeds, and npx vitest run gives the output below. Look at the “no tests” on the last line.
第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
RUN v2.1.8 /Users/you/cab-booking-context ❯ src/test/App.test.jsx (0 test) ⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯ FAIL src/test/App.test.jsx [ src/test/App.test.jsx ] Error: Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension. Plugin: vite:import-analysis File: /Users/you/cab-booking-context/src/context/CabContext.js:19:27 17 | > 18 | {children} 19 | </CabContext.Provider> | ^ 20 | ); 21 | }; ❯ TransformPluginContext._formatError node_modules/vite/dist/node/chunks/dep-CB_7IfJ-.js:49255:41 ❯ TransformPluginContext.error node_modules/vite/dist/node/chunks/dep-CB_7IfJ-.js:49250:16 Test Files 1 failed (1) Tests no tests
JSXsrc/context/CabContext.js ← 注意这个扩展名src/context/CabContext.js ← look at that extension源项目From source
1import { createContext, useContext, useState } from "react";
2
3const CabContext = createContext();
4
5const CabProvider = ({ children }) => {
6 const [bookedCabDetails, setBookedCabDetails] = useState(null);
7 const [rideHistory, setRideHistory] = useState([]);
8
9 const updateBookedCabDetails = (details) => {
10 setBookedCabDetails(details);
11 setRideHistory([...rideHistory, details]);
12 };
13
14 return (
15 <CabContext.Provider
16 value={{ bookedCabDetails, updateBookedCabDetails, rideHistory }}
17 >
18 {children}
19 </CabContext.Provider>
20 );
21};
22
23const useCabContext = () => {
24 const context = useContext(CabContext);
25
26 if (!context) {
27 throw new Error("useCabContext must be used within a CabProvider");
28 }
29
30 return context;
31};
32
33export { CabProvider, useCabContext };
Source: cab-booking-context/src/context/CabContext.js
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 完整答案跑不起来 —— 一个扩展名的事The complete answer does not run — the cause is one file extension · Cab BookingCab Booking
L1认出来Spot it下面哪些说法是对的?(多选)Which of these statements are correct? (more than one)
关于源项目那两处「能过但可以更好」的写法。About the two places in the source project that pass the tests but could be better.

这题是多选。More than one answer is correct.

先选一个选项Pick an option first
来自From 从零重写:空文件夹里做出来Rewrite it: build the whole app in an empty folder · Cab BookingCab Booking
L4从零重写Rebuild from scratch空文件夹里做出整个 Cab BookingBuild the whole of Cab Booking from an empty folder
只给需求和文件清单。不给任何代码。卡住了按四级提示走,答案在最后一道门后面。You get the requirements and the file list only. No code at all. If you get stuck, work through the four levels of hints. The answer sits behind the last door.
需求(不给代码,自己实现)Requirements — no code given, implement it yourself
  • 首页:一个大标题「Book a Safe Ride with HackerRide」、一个 data-testid="book-button" 的按钮,下面是行程历史区Home page: a big heading "Book a Safe Ride with HackerRide", a button with data-testid="book-button", and the ride history area below it
  • 行程历史:没有记录时显示 <p data-testid="no-ride-title">No ride history yet.</p>;有记录时每条一个 <li data-testid="history-cabs">,显示车名和 $价格Ride history: with no records show <p data-testid="no-ride-title">No ride history yet.</p>; with records show one <li data-testid="history-cabs"> per entry, holding the cab name and the $price
  • 行程历史只显示最新三条,最新的排最上面The ride history shows the three newest entries only, newest at the top
  • 点 book-button 进入选车页:容器 data-testid="all-cabs-section",按类型分三组,每组一个 <h3 data-testid="car-type-heading">,顺序必须是 Sedan / SUV / LuxuryPressing book-button opens the cab page: a container with data-testid="all-cabs-section", three groups by type, each with one <h3 data-testid="car-type-heading">, and the order has to be Sedan / SUV / Luxury
  • 每辆车一张卡,五个 testid:cab-card-img / cab-card-name / cab-card-type / cab-card-price / cab-card-select-button。类型显示 "Type: X",价格显示 "Fare: $N"One card per cab, with five testids: cab-card-img / cab-card-name / cab-card-type / cab-card-price / cab-card-select-button. The type reads "Type: X" and the price reads "Fare: $N"
  • 点某张卡的 Select:把这辆车记为当前预订、追加进历史,然后进入加载页 data-testid="loading"Pressing Select on a card: record that cab as the current booking, append it to the history, then go to the loading page with data-testid="loading"
  • 加载页 1000ms 后自动进入确认页;确认页 data-testid="confirm-message" 显示「<车名> is on the way and will arrive shortly.」The loading page moves to the confirmation page after 1000ms; the confirmation page shows data-testid="confirm-message" reading "<cab name> is on the way and will arrive shortly."
  • 确认页有 data-testid="confirm-button",点了回首页,此时历史里能看到刚才那辆车The confirmation page has data-testid="confirm-button"; pressing it returns to the home page, where the history now shows that cab
  • 状态必须放在 Context 里:createContext + Provider + 自定义 hook(hook 里带「不在 Provider 内就抛错」的守卫),Provider 包在 App 外面The state has to live in a Context: createContext + Provider + a custom hook (the hook carries a guard that throws when it is used outside the Provider), and the Provider wraps App
  • 数据用 data.json:三个类型各两辆车,Sedan 第一辆是 Ford Fusion / $20,SUV 两辆是 Toyota Highlander / Ford Explorer,Sedan 第二辆是 Honda AccordThe data comes from data.json: two cabs per type, the first Sedan is Ford Fusion / $20, the two SUVs are Toyota Highlander / Ford Explorer, and the second Sedan is Honda Accord
你需要自己建的文件Files you create yourself
文件清单File list
package.jsonvite + react + vitest + jsdom + @testing-library/react + @testing-library/jest-dom
vite.config.mjsplugins: [react()],test 段配 environment: "jsdom" / globals / setupFilesplugins: [react()], and a test section with environment: "jsdom" / globals / setupFiles
index.html一个 <div id="root">one <div id="root">
src/index.jsxcreateRoot,用 <CabProvider> 包住 <App />createRoot, with <CabProvider> wrapping <App />
src/App.jsxcurrentPage 状态机 + handleSelectCabthe currentPage state machine + handleSelectCab
src/context/CabContext.jsx三件套。注意扩展名 —— 里面有 JSXthe three parts. Watch the extension — this file holds JSX
src/components/AppHeader.jsx只显示标题,没有 testidshows the title only, no testid
src/components/Home/Home.jsxhero + book-button + <RideHistory />
src/components/Home/RideHistory.jsx空状态 / 最新三条倒序the empty state / the three newest in reverse
src/components/CabOptions/CabOptions.jsxObject.keys 分组grouping with Object.keys
src/components/CabOptions/CabCard.jsx五个 testidthe five testids
src/components/Loading/Loading.jsxsetTimeout 1000 + clearTimeout
src/components/CabConfirmation/CabConfirmation.jsx?.name + confirm-button
src/data/data.json三组六辆车,键顺序 Sedan → SUV → Luxurysix cabs in three groups, with the key order Sedan → SUV → Luxury
src/test/setup.jsimport "@testing-library/jest-dom"
src/test/App.test.jsx把源项目那四个测试原样放进来 —— 这是你的判分依据copy the source project's four tests in unchanged — this is what grades you
写完后在本机这样验证Verify it locally like this
npm install
装完无报错。React 18/19 都可以,测试用的 API 没差别It finishes with no errors. React 18 or 19 both work; the APIs the tests use are the same
npx vitest run
刚放进测试文件时应该是 4 failed / 4 total,报错都是 Unable to find an element by: [data-testid=...]。全部做完是 Test Files 1 passed / Tests 4 passed (4)Right after you drop the test file in it should be 4 failed / 4 total, all reporting Unable to find an element by: [data-testid=...]. When everything is done it is Test Files 1 passed / Tests 4 passed (4)
npx vitest run 2>&1 | grep -c 'no tests'
0。如果不是 0,说明你也踩了 .js 里写 JSX 那个坑 —— 把带 JSX 的文件改名成 .jsx0. Anything else means you hit the JSX-in-a-.js-file problem too — rename the files that hold JSX to .jsx
npm run dev
浏览器里手动走一遍:首页 → 选车 → 加载 1 秒 → 确认 → 回首页看到历史。连订四辆,历史应该只有三条且最新在最上Walk through it by hand in the browser: home page → pick a cab → 1 second of loading → confirm → back to the home page with the history there. Book four in a row and the history should hold three, newest at the top
提示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.