动手做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.
练习Exercises
筛出 30 个练习(共 148 个) · 第 2 / 3 页。Showing 30 of 148 · page 2 / 3.按 TODO 的三条要求写完整实现。检查器会核对方法名、兜底、 错误处理和 correlation id。
Write the full implementation against the three requirements in the TODO. The checker looks at the method name, the fallback, the error handling and the correlation id.
- 用 user.id 去取该用户的订单Use user.id to fetch that user's orders
- 调用数据源上真实存在的方法Call a method that really exists on the data source
- 绝不返回 null 或 undefined(schema 是 [Order!]!)Never return null or undefined (the schema says [Order!]!)
- 用 try/catch 包住,失败时抛 GraphQLErrorWrap it in try/catch and throw a GraphQLError on failure
- 错误的 extensions 里带 code 和 correlationIdPut code and correlationId in the error's extensions
- 已经是 GraphQLError 的错误要原样往上抛,不要重新包装Rethrow an error that is already a GraphQLError untouched, without rewrapping it
- 日志里带上 correlationIdInclude correlationId in the log line
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
两个函数一起写。注意它们的数据来源、兜底策略、 context 解构都不一样。
Write both functions together. They differ in where they read from, what they fall back to, and what they destructure out of context.
- Query.order 用 orderLoader 取数据Query.order reads through orderLoader
- Query.order 找不到时抛带 ORDER_NOT_FOUND code 的 GraphQLErrorWhen Query.order finds nothing, it throws a GraphQLError carrying the ORDER_NOT_FOUND code
- Query.orders 用 orderDataSource.getOrdersByUserId 取数据Query.orders reads through orderDataSource.getOrdersByUserId
- Query.orders 校验 userId,非法时抛 INVALID_INPUTQuery.orders validates userId and throws INVALID_INPUT when it is not valid
- Query.orders 绝不返回 null(schema 是 [Order!]!)Query.orders never returns null (the schema says [Order!]!)
- 两个都用 try/catch,catch 里先放行已有的 GraphQLErrorBoth use try/catch, and the catch lets an existing GraphQLError through first
- 两个都在日志里带上 correlationIdBoth include correlationId in their log line
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
六个端点一起写。业务逻辑全部调 orderService, 你负责选对状态码、处理可选参数、转 enum、打日志。
Write all six endpoints. Every piece of business logic goes through orderService; your job is picking the right status codes, handling the optional parameter, converting the enum, and logging.
- GET /api/orders:?userId= 传了就按用户过滤,没传返回全部;200GET /api/orders: filter by user when ?userId= is given, otherwise return everything; 200
- GET /api/orders/{id}:200;不要 try/catch,让 404 由全局处理器给出GET /api/orders/{id}: 200; no try/catch, let the global handler produce the 404
- GET /api/orders/user/{userId}:200GET /api/orders/user/{userId}: 200
- POST /api/orders:201 CreatedPOST /api/orders: 201 Created
- PATCH /api/orders/{id}/status:把 body 里的字符串转成 OrderStatus;缺失或非法值返回 400;成功 200PATCH /api/orders/{id}/status: convert the string in the body into an OrderStatus; return 400 when it is missing or invalid; 200 on success
- DELETE /api/orders/{id}:204 No ContentDELETE /api/orders/{id}: 204 No Content
- 六个端点都用 logger.info 打日志,并带上 MDC 里的 correlationIdAll six endpoints log with logger.info and include the correlationId from the MDC
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
针对 management.endpoints.web.exposure.include=*, 写出修正后的配置。至少要做到:白名单、管理端口分离、 health 不泄漏细节、支持 k8s 探针。
Write the corrected configuration for management.endpoints.web.exposure.include=*. At a minimum: an allow list, management on its own port, a health endpoint that leaks no detail, and support for Kubernetes probes.
- 用白名单列出需要的端点,不用 *List the endpoints you need in an allow list; do not use *
- management.server.port 设成与业务端口不同的值Set management.server.port to something other than the business port
- health 端点不显示详情The health endpoint shows no details
- 开启 health probes(liveness / readiness)Turn on the health probes (liveness and readiness)
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
hover 预览 + 点击选中 + 再点清零。 检查器会查 ??、onMouseLeave 的位置和无障碍。
Hover to preview, click to pick, click the same star again to reset. The checker looks at ??, where onMouseLeave sits, and accessibility.
- hover 到第 n 颗时前 n 颗显示为选中样式(预览)Hovering star n shows the first n stars in the filled style (a preview)
- 鼠标移出整个组件后回到已选值Moving the mouse out of the whole component goes back to the picked value
- 点第 n 颗设为 n 分;再点同一颗清零Clicking star n sets the score to n; clicking the same star again resets to zero
- 每颗星是 button,带 aria-label,键盘可用Every star is a button with an aria-label, and works from the keyboard
- 显示值必须是派生的,不许再开第三个 stateThe shown value has to be derived; a third piece of state is not allowed
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
四个考点全都会被检查:惰性初始化、try/catch 兜底、 依赖带 key、as const。
All four points get checked: lazy initialisation, a try/catch fallback, key in the dependency list, and as const.
- 读 localStorage 只在首次渲染发生一次(惰性初始化)Reading localStorage happens once, on the first render (lazy initialisation)
- 读和写都要有 try/catchBoth the read and the write need a try/catch
- JSON 序列化 / 反序列化JSON serialising and deserialising
- effect 的依赖里要有 key 和 valueThe effect's dependency list holds key and value
- 返回元组,用 as constReturn a tuple, using as const
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
一次操作同时改两个数组,而且不许碰原 board。 检查器会查两个边界和「未动的列复用引用」。
One action changes two arrays, and the board you were handed must not be touched. The checker looks at the two edge cases and at whether untouched columns keep their original reference.
- from === to 时返回同一个引用,不造新对象When from === to, return the same reference and build no new object
- 找不到卡时返回同一个引用When the card is not found, return the same reference
- 源列用 filter 去掉,目标列用展开追加Drop it from the source column with filter, and append to the target column with spread
- 只改这两列,其余列复用原数组Only those two columns change; the rest keep their original arrays
- 不许 push / splice / 直接赋值No push, no splice, no direct assignment
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
cancel() 能取消挂着的那次。Turn this half-finished version, which calls through immediately every time, into a real debounce: a burst of calls runs only once, delay ms after the last one, and cancel() drops the pending run.- 调用 debounced() 不许立刻执行 fnCalling debounced() must not run fn right away
- 一串连续调用只在停手 delay 毫秒后执行一次,参数用最后那次的A burst of calls runs once, delay ms after the last call, with the arguments of that last call
- 两串隔开的调用各自触发一次Two bursts separated by a pause each fire once
- cancel() 取消还没发生的那次调用cancel() drops the call that has not happened yet
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
- 第一次调用立刻执行(leading)The first call runs immediately (leading)
- 窗口内的后续调用不执行Later calls inside the same window do not run
- 窗口结束时用窗口内最后一次的参数补执行(trailing)When the window closes, run once more with the arguments of the last call in it (trailing)
- 窗口过了之后再调用,又立刻执行A call after the window has passed runs immediately again
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
- 原始值和 null 原样返回Primitives and null come back unchanged
- 嵌套对象 / 数组逐层克隆,每一层都是新引用Nested objects and arrays are cloned level by level, and every level is a new reference
- Date 克隆成新 Date;Map / Set 深克隆A Date becomes a new Date; Map and Set are deep-cloned
- 循环引用不爆栈(WeakMap 登记「原对象 → 克隆」)A circular reference does not recurse forever (a WeakMap records original to clone)
- 不许用 JSON.parse(JSON.stringify(x))JSON.parse(JSON.stringify(x)) is not allowed
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
.flat()。Grow this shallow-copy version into a real flatten: one level by default, depth decides how many levels, Infinity flattens everything, and the input is never changed. Calling the built-in .flat() is not allowed.- 默认 depth 为 1,与 Array.prototype.flat 一致depth is 1 by default, the same as Array.prototype.flat
- depth 控制展开层数,Infinity 全压平depth decides how many levels are opened up; Infinity flattens everything
- depth 0 返回浅拷贝,不是原数组引用depth 0 returns a shallow copy, not a reference to the input array
- 不改输入数组;不许调用原生 .flat()The input array is never changed, and the built-in .flat() is not allowed
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.
fn.length 就执行,可任意分组, 部分应用复用互不污染。Grow this return-fn-directly version into a real curry: run as soon as fn.length arguments have arrived, accept them in any grouping, and let a partial application be reused without one call affecting another.- 攒够 fn.length 个参数就执行Runs as soon as fn.length arguments have arrived
- 参数可以任意分组:c(1)(2)(3) / c(1, 2)(3) / c(1)(2, 3)Arguments may come in any grouping: c(1)(2)(3), c(1, 2)(3), c(1)(2, 3)
- 部分应用可复用:const add1 = c(1) 之后多次调用互不污染A partial application is reusable: after const add1 = c(1), calling add1 many times gives independent results
看答案之前,先确认你已经自己动手写过一遍。看懂别人的答案和自己写出来,是两种能力。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.