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

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

练习Exercises

筛出 15 个练习(共 148 个) · 第 1 / 2 页。Showing 15 of 148 · page 1 / 2.
来自From 先读四个测试:它们到底要什么Read the four tests first: what exactly they ask for · Cab BookingCab Booking
L1认出来Spot it哪个断言决定了「分组顺序」不能自己定?Which assertion makes the group order fixed?
测试 2 里有一行让「Sedan / SUV / Luxury 的顺序」变成硬要求。是哪一行?One line in test 2 turns the order of Sedan / SUV / Luxury into a hard requirement. Which line?
先选一个选项Pick an option first
来自From 先读四个测试:它们到底要什么Read the four tests first: what exactly they ask for · Cab BookingCab Booking
L2填空Fill the blanks补齐 RideHistory 的两个 testid 和互斥逻辑Fill in the two testids of RideHistory and the either-or logic
空的时候只能出现 no-ride-title, 有记录的时候只能出现 history-cabs。把三个空填上。When it is empty only no-ride-title may appear; when there are records only history-cabs may appear. Fill in the three blanks.
JSXsrc/components/Home/RideHistory.jsx3 个空3 blanks
1const RideHistory = () => {
2 const { rideHistory } = useCabContext();
3 const latestRides = rideHistory.slice(-3).reverse();
4
5 return (
6 <section className="history-container">
7 <h3>Ride History</h3>
8
9 {latestRides.length > 0 ? (
10 <ul className="history-list">
11 {latestRides.map((ride, index) => (
12 <li key={`${ride.id}-${index}`} data-testid="">
13 <span>{ride.name}</span>
14 <strong>${ride.price}</strong>
15 </li>
16 ))}
17 </ul>
18 ) : (
19 <p data-testid="" className="empty-state">
20
21 </p>
22 )}
23 </section>
24 );
25};
把 3 个空都填上才能检查(还差 3 个)Fill all 3 blanks to check (3 to go)
来自From Context 放在哪一层 —— 这道题最容易死的地方Which level the Context goes on — the most common way to fail this task · Cab BookingCab Booking
L2填空Fill the blanks补齐 Context 三件套Fill in the three parts of the Context
四个空。第 4 个空是这道题的守卫,写错了就等于没有守卫。Four blanks. The fourth one is the guard, and getting it wrong is the same as having no guard at all.
JSXsrc/context/CabContext.jsx4 个空4 blanks
1import { createContext, useContext, useState } from "react";
2
3const CabContext = ();
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();
12 };
13
14 return (
15 <CabContext.
16 value={{ bookedCabDetails, updateBookedCabDetails, rideHistory }}
17 >
18 {children}
19 </CabContext.>
20 );
21};
22
23const useCabContext = () => {
24 const context = useContext(CabContext);
25
26 if () {
27 throw new Error("useCabContext must be used within a CabProvider");
28 }
29
30 return context;
31};
32
33export { CabProvider, useCabContext };
把 4 个空都填上才能检查(还差 4 个)Fill all 4 blanks to check (4 to go)
来自From Context 放在哪一层 —— 这道题最容易死的地方Which level the Context goes on — the most common way to fail this task · Cab BookingCab Booking
L3写整块Write a block从签名写出整个 CabContextWrite the whole CabContext from the signature
只给你 import 和导出。三件套自己写出来, 包括那个守卫。检查器会查守卫、查不可变更新、 以及不许用 pushYou only get the import and the export. Write the three parts yourself, including the guard. The checker looks for the guard, for an immutable update, and for no use of push.
要求Requirements
  • createContext() 不传默认值 —— 这样没套 Provider 时是 undefined,守卫才抓得住createContext() takes no default value — that way it is undefined with no Provider around, which is what the guard catches
  • 两个 state:bookedCabDetails 初始 null、rideHistory 初始 []Two states: bookedCabDetails starts null, rideHistory starts []
  • updateBookedCabDetails 同时改两个 state,历史用不可变更新(展开运算符),不许 pushupdateBookedCabDetails changes both states, and the history update is immutable (spread), never push
  • useCabContext 里有 if 守卫 + throw,错误信息要出现 CabProvideruseCabContext has an if guard plus a throw, and the message mentions CabProvider
  • 命名导出 CabProvider 和 useCabContextNamed exports for CabProvider and useCabContext
JSXsrc/context/CabContext.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 用一个 state 管四个页面Controlling four pages with one piece of state · Cab BookingCab Booking
L1排顺序Order it把一次完整预订的六步排好Put the six steps of one full booking in order
从点「Book a Cab」到历史里出现记录,中间发生了什么?按顺序排。What happens between pressing “Book a Cab” and the record appearing in the history? Put the steps in order.
1setCurrentPage("cab-options")
2updateBookedCabDetails(cab) —— 写 Context 的两个 stateupdateBookedCabDetails(cab) — writes both states in the Context
3setCurrentPage("loading")
4Loading 的 useEffect 里 setTimeout 1000ms 到期The setTimeout of 1000ms inside the useEffect of Loading fires
5onComplete() → setCurrentPage("cab-confirmation")
6onConfirm() → setCurrentPage("home"),首页读到新的 rideHistoryonConfirm() → setCurrentPage("home"), and the home page reads the new rideHistory
来自From 用一个 state 管四个页面Controlling four pages with one piece of state · Cab BookingCab Booking
L2填空Fill the blanks补齐 App 的状态机Fill in the state machine of App
五个空。注意第 4 个空是这道题最容易写反的地方。Five blanks. The fourth one is the easiest place in this exercise to get backwards.
JSXsrc/App.jsx5 个空5 blanks
1const App = () => {
2 const [currentPage, setCurrentPage] = useState();
3 const { } = useCabContext();
4
5 const handleSelectCab = (cab) => {
6 (cab);
7 setCurrentPage("loading");
8 };
9
10 return (
11 <div className="App">
12 <AppHeader title={title} />
13
14 {currentPage === "home" && (
15 <Home onBookClick={() => setCurrentPage("cab-options")} />
16 )}
17
18 {currentPage === "cab-options" && (
19 <CabOptions onSelectCab={} />
20 )}
21
22 {currentPage === "loading" && (
23 <Loading onComplete={() => setCurrentPage("cab-confirmation")} />
24 )}
25
26 {currentPage === "cab-confirmation" && (
27 <CabConfirmation onConfirm={() => } />
28 )}
29 </div>
30 );
31};
把 5 个空都填上才能检查(还差 5 个)Fill all 5 blanks to check (5 to go)
来自From 按类型分组渲染六张卡Rendering the six cards grouped by type · Cab BookingCab Booking
L1认出来Spot it哪个 key 在历史列表里会出问题?Which key goes wrong in the history list?
用户连订了两次同一辆 Ford Fusion(id: 1)。 历史列表用下面哪个 key 会报「重复 key」警告?The user books the same Ford Fusion (id: 1) twice in a row. Which of these keys makes the history list warn about duplicate keys?
先选一个选项Pick an option first
来自From 按类型分组渲染六张卡Rendering the six cards grouped by type · Cab BookingCab Booking
L3写整块Write a block从零写出 CabCardWrite CabCard from an empty file
只给你 props 签名。五个 testid 自己写出来, 别忘了 alttype="button"You only get the props signature. Write the five testids yourself, and do not forget alt and type="button".
要求Requirements
  • 五个 data-testid 一个不少:cab-card-img / -name / -type / -price / -select-buttonAll five data-testid values: cab-card-img / -name / -type / -price / -select-button
  • img 要有 alt(测试不查,但没有 alt 是真实的可访问性缺陷)The img needs an alt (the tests do not check it, but a missing alt is a real accessibility defect)
  • 按钮写 type="button" —— 不写的话在 <form> 里会变成提交按钮The button needs type="button" — without it, inside a <form> it becomes a submit button
  • onClick 里包一层箭头函数把 cab 传进去,不是直接传 onSelectCabWrap the onClick in an arrow function that passes cab in; do not pass onSelectCab directly
  • 价格前面要有 $ —— 历史列表的断言查的是 "$20"The price needs a $ in front — the history assertion looks for "$20"
JSXsrc/components/CabOptions/CabCard.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 Loading:一秒之后自己跳走Loading: it moves to the next page by itself after one second · Cab BookingCab Booking
L2填空Fill the blanks补齐 Loading 的四个空Fill in the four blanks of Loading
第 3 个空是这道题的送分点,第 4 个空是这道题的良心。Blank 3 is the one that earns the marks. Blank 4 is the one that is simply the right thing to do.
JSXsrc/components/Loading/Loading.jsx4 个空4 blanks
1import { useEffect } from "react";
2
3const Loading = ({ onComplete }) => {
4 (() => {
5 const timer = (() => {
6 if (onComplete) onComplete();
7 }, );
8
9 return () => ;
10 }, [onComplete]);
11
12 return (
13 <main data-testid="loading" className="loading-container">
14 <div className="spinner" aria-hidden="true" />
15 <h1>Loading...</h1>
16 <p>We are working on your cab booking. Thanks for your patience.</p>
17 </main>
18 );
19};
把 4 个空都填上才能检查(还差 4 个)Fill all 4 blanks to check (4 to go)
来自From Loading:一秒之后自己跳走Loading: it moves to the next page by itself after one second · Cab BookingCab Booking
L2Debug LabDebug LabDebug Lab:定时器永远不到期Debug Lab: the timer never fires
测试 3 报「找不到 confirm-message」, 而 DOM 快照显示页面还停在 loading。 先读报错,再看下面那个 Loading 组件 ——它和源项目差一个东西Test 3 reports that it cannot find confirm-message, and the DOM snapshot shows the page still sitting on loading. Read the error first, then look at the Loading component below — one thing in it differs from the source project.
第 1 步 · 先看现象和代码,别急着改Step 1 · read the symptom and the code before you touch anything
FAIL src/test/App.test.jsx > React: Cab Booking > completes a booking and adds it to ride history TestingLibraryElementError: Unable to find an element by: [data-testid="confirm-message"] Ignored nodes: comments, script, style <body> <div> <div class="App"> <header …>…</header> <main class="loading-container" data-testid="loading"> <div class="spinner" aria-hidden="true" /> <h1>Loading...</h1> </main> </div> </div> </body> ❯ src/test/App.test.jsx:52:17
JSXsrc/components/Loading/Loading.jsx(有问题的版本)src/components/Loading/Loading.jsx (the broken version)示意Illustrative
1const Loading = ({ onComplete }) => {
2 useEffect(() => {
3 const timer = setTimeout(() => {
4 if (onComplete) onComplete();
5 }, 1000);
6
7 return () => clearTimeout(timer);
8 }); // ← 依赖数组呢?
9
10 return (
11 <main data-testid="loading" className="loading-container">
12 <div className="spinner" aria-hidden="true" />
13 <h1>Loading...</h1>
14 </main>
15 );
16};
1const Loading = ({ onComplete }) => {
2 useEffect(() => {
3 const timer = setTimeout(() => {
4 if (onComplete) onComplete();
5 }, 1000);
6
7 return () => clearTimeout(timer);
8 }); // ← where is the dependency array?
9
10 return (
11 <main data-testid="loading" className="loading-container">
12 <div className="spinner" aria-hidden="true" />
13 <h1>Loading...</h1>
14 </main>
15 );
16};
第 2 步 · 这是什么类型的错误Step 2 · what kind of error is this
来自From 历史与确认页:两个小而致命的细节The history and confirmation pages: two small details that decide pass or fail · Cab BookingCab Booking
L1认出来Spot it哪些写法能让测试 4 全绿?(多选)Which versions make test 4 pass? (more than one)
历史是 [Fusion, Accord, Highlander, Explorer](最旧 → 最新)。 断言要求:3 条、顺序Explorer / Highlander / AccordFusion 不在 DOM 里。The history is [Fusion, Accord, Highlander, Explorer] (oldest → newest). The assertions want: 3 entries, in the order Explorer / Highlander / Accord, and Fusion not in the DOM.

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

先选一个选项Pick an option first
来自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.