表单路由与生态

共 26 题
📑 题目列表 26 题
#
★★★

1. React 测试(Testing Library、Vitest Browser Mode)

React 测试如何用 Testing Library 与 Vitest Browser Mode 落地?两者的分工与边界是什么?

  • Testing Library 的测试哲学(用户视角)
  • Vitest Browser Mode 的真实浏览器执行
  • 与 jsdom 环境的取舍

Testing Library(RTL + jest-dom + user-event)以"用户视角"测试:通过可访问性角色查询元素(getByRole)、模拟真实交互(user-event 的完整事件序列)、断言可访问状态,避免测试实现细节(不测组件内部 state 与 DOM 结构);搭配 Vitest 作为测试运行器(快、TS 原生、E2E 集成)。Vitest Browser Mode 让测试在真实浏览器(Playwright/WebdriverIO 驱动)中运行:完整 DOM、真实布局与事件系统(jsdom 缺失的 offsetWidth、focus 行为、滚动、剪贴板等),原生 ESM、真实 fetch;与 jsdom 环境的差异是"保真度"——浏览器模式能测"jsdom 模拟不了"的行为(测量、动画、键盘焦点序列、浏览器 API)。

分工与边界:单元/组件测试默认 jsdom(快、稳定、适合逻辑与交互断言);涉及真实布局、浏览器 API、无法 jsdom 模拟的行为用 Browser Mode;两者共享同一套 Testing Library API(测试代码可移植);Browser Mode 比 jsdom 慢、比 Playwright E2E 快(无完整应用启动),定位是"真实浏览器中的组件/集成测试";CI 中 jsdom 层跑全量、Browser Mode 跑精选(交互复杂、布局敏感)测试;注意 Browser Mode 下并发(worker 数)、资源与浏览器版本管理。工程建议:分层测试(jsdom 单测 → browser 组件测 → E2E),按"行为保真需求"选择环境。

答题先讲 Testing Library 的用户视角哲学,再讲 Browser Mode 的保真度优势与 jsdom 的边界,最后给分层测试策略与环境选型标准。

#
★★★

2. TanStack Router 类型安全路由的能力边界

TanStack Router 的类型安全路由能力是什么?它的能力边界与适用场景是什么?

  • 全链路类型安全的实现(searchParams、path params)
  • 基于文件路由的类型推导
  • 与 React Router 的边界对比

TanStack Router 的核心能力是"端到端类型安全":基于文件系统路由(routeTree.gen.ts 自动生成),用 TypeScript 字面量类型推导出完整路由表——路径参数(path params)经 parseParams 校验后类型精确(如 $postId: string)、search params 由 search schema(Zod/Valibot)定义并推导(search 的读写全部类型化)、route 的 loader 数据、上下文、beforeLoad 钩子的返回类型都贯通到组件(useLoaderData/useSearch 零断言)。类型安全贯穿链接(Link to 的路径必须合法、search 必须匹配)、导航(router.navigate 参数校验)与数据加载(loader 与组件数据契约),编译期即可拦截"路径拼错、参数写错"。

能力边界:类型安全建立在"代码生成"之上(维护 routeTree.gen.ts 与 dev server),复杂动态路由(运行时拼接、非 TS 项目)收益受限;search schema 的运行时校验与类型推导需同时维护(Zod 与类型的双写成本);非类型化场景(第三方 URL 进入、旧代码、any 泛滥)安全被稀释;性能与体积:类型生成与校验有编译开销。适用场景:中型以上 TS 应用、对 URL 状态强依赖(表格筛选、分页在 URL)的产品;对比 React Router:React Router v7+ 也引入 typegen(类型安全 loader/search 的现代化路线),TanStack 更彻底(全量推导 + schema 校验),React Router 生态更大、迁移兼容更好——选型看"类型安全优先级"与"团队栈"。

答题先讲 TanStack 的全链路类型安全(路径、search、loader 数据贯通),再讲代码生成依赖、schema 双写与动态场景的边界,最后与 React Router 对比定位。

#
★★★

3. View Transitions API 在 SPA 路由切换的过渡编排与 prefers-reduced-motion 可访问性的工程实践

View Transitions API 在 SPA 路由切换中如何编排过渡?prefers-reduced-motion 的可访问性实践是什么?

  • SPA 路由的过渡编排(命名视图、共享元素)
  • 过渡回退与异步更新
  • reduced-motion 与可访问性

SPA 路由过渡编排:把"路由内容变化"包进 document.startViewTransition(或 React 19 的 ),旧页面与新页面截取快照后做过渡;编排核心是命名视图——给"跨路由共享的元素"(Logo、页头、列表项)设置 view-transition-name,新旧快照同名视图间做"共享元素过渡"(如列表项放大成详情),不同名视图默认交叉淡入;多路由切换(嵌套路由)分别包裹或统一在布局层包裹,规划动画层级(::view-transition-group 的 z-index);配合路由库在导航完成/取消时控制过渡范围(过度过渡导致"页面跳变"可禁用指定路由的过渡);异步更新:startViewTransition 的回调返回 promise(数据加载完成后 resolve),确保快照截取在"新内容就绪"后。

可访问性:prefers-reduced-motion: reduce 时跳过或缩短动画(媒体查询内不启动 transition 或设置 0 时长),尊重用户"减少动效"偏好;过渡期间内容双快照可能导致屏幕阅读器异常朗读——减少 reduce 动效用户的外;过渡完成后焦点管理照常(路由切换焦点移至标题);内容语义优先于动画(过渡不改变 DOM 可访问性树);测试:reduced-motion 模式下验证功能完整(动画禁用不破坏导航);渐进增强:无 API 支持的浏览器直接跳转。工程实践把过渡封装在路由层组件(统一命名、媒体查询、回退),避免散落各处。

答题先讲过渡编排(startViewTransition + 命名视图共享元素 + 异步 resolve),再讲 reduced-motion 的可访问性处理(跳过动画、焦点管理、无 API 回退),体现动画与可访问性的平衡。

#
★★★

4. React Router v8 在 SPA/数据路由(loader/action)

React Router v8 的数据路由(loader/action)是什么?在 SPA 中如何使用?

  • data router 的 loader/action 模式
  • 路由级数据获取与提交
  • SPA 模式下的行为差异

React Router v7/v8 的"数据路由"(data router)把数据获取与提交移入路由层:每个路由可定义 loader(进入路由前获取数据,返回的数据经 useLoaderData 读取)与 action(表单/请求提交,处理写操作并返回结果),路由导航时由路由器并行执行 loader、处理加载/错误状态(useNavigation 的 state)、提交后自动 revalidate(重新执行 loader 刷新数据)。SPA 模式下同样生效:loader 在客户端执行(fetch 数据),导航不依赖服务端;配合

(受控路由表单)提交到 action,实现"声明式数据流"。

工程实践:loader 集中数据获取(组件内不再 useEffect + state 三件套),导航状态(loading/error)由路由器统一管理;loader 返回的 deferred promise 配合 Suspense 实现流式加载;action 处理提交并 revalidate,形成"变更 → 刷新"闭环;缓存策略由框架/自建(loader 的 fetch 缓存、SWR 模式);SPA 边界:loader 的错误处理(errorElement)、未匹配路由(catch-all)、嵌套路由的数据聚合(父 loader 数据子路由共享);与状态管理分工:loader 管"路由级数据",跨路由共享状态仍用 context/状态库;SSR 时 loader 在服务端执行(同构),SPA 时仅客户端——编写 loader 需两端安全(不依赖浏览器专属 API,除非仅在 SPA 用)。

答题先讲 data router 的 loader/action 模式与导航状态管理,再讲 SPA 下的执行位置与 revalidate 闭环,最后给错误处理、deferred 与状态管理分工的实践边界。

#
★★★

5. React Hook Form(RHF,uncontrolled + ref)

React Hook Form(RHF)的工作原理是什么?uncontrolled + ref 模式的价值与边界是什么?

  • 非受控注册(ref)与表单 store 分离
  • 字段级订阅减少重渲染
  • 与 Controller、校验库的协作

RHF 默认采用"非受控 + ref"模式:useForm 创建独立的表单 store(表单值放在 store 中,非 React state),register(name) 通过 ref 挂到输入框——输入时值写入 store 而不触发 React 渲染(DOM 自身管理 value),提交/校验时从 store 读取值。这带来核心收益:输入框的每次按键不引发 React 重渲染(无受控模式的 value→state→render 回路),表单字段越多收益越明显;字段级订阅(useWatch/watch)让只有被订阅的组件才随对应字段变化重渲染。

价值与边界:与校验库(Zod/yup/Valibot)集成——通过 resolver 在提交/变更时校验,错误以"字段级"写入 store,仅错误字段重渲染;需要受控的场景(自定义组件、联动字段)用 Controller 桥接(render prop 拿到 value/onChange 同步到 store);RHF 的 store 不参与 React 渲染周期,SSR 时初始值序列化需显式处理;与 React 19 的 form action 协作:RHF 可 handleSubmit 到 action 或读取 FormData(非受控天然兼容);边界:非受控模式依赖 ref 挂载(动态字段需 Controller 或 ref 回调重挂载)、复杂跨字段校验用 resolver + context 或 watch;不适合"每个按键都要驱动其它 UI"的场景(用 watch/useWatch 显式订阅)。选型:字段多、性能敏感、与校验库深度集成的表单用 RHF;React 19 简单表单可用原生 form action。

答题先讲"非受控 + ref + 独立 store"的架构与零重渲染收益,再讲 Controller 桥接受控场景与校验库集成,最后给动态字段与 React 19 form action 的边界。

#
★★★

6. React Router v8 的 URL 状态(searchParams)在表单状态恢复与刷新重入的工程取舍

React Router v8 的 URL 状态(searchParams)在表单状态恢复与刷新重入中的工程取舍是什么?

  • URL 作为可分享/可恢复的状态载体
  • useSearchParams 的读写与序列化
  • 与本地状态的分工与边界

把"筛选、分页、搜索词、Tab"等表单状态放进 URL searchParams 的价值:可分享(复制链接他人看到相同状态)、可恢复(刷新/重入自动还原)、可回溯(浏览器前进后退保留状态)、可服务端感知(SSR/分析)。React Router v8 中 useSearchParams 返回 [searchParams, setSearchParams],setSearchParams 更新 URL(可配 replace 避免堆栈膨胀),导航层把 searchParams 传给 loader(读取条件加载数据),实现"URL 即状态源"。

工程取舍:URL 状态适合"低敏感、可序列化、语义公开"的状态(筛选条件、页码、排序);不适合——高频变化(每次按键更新 URL 有历史记录与性能问题,需防抖/局部更新策略)、敏感数据(token、个人信息会留在历史与链接中)、大型/复杂对象(URL 长度与可读性)。实践:筛选表单提交(而非每键)才写 URL;与表单状态分离——"编辑中未提交"的草稿存组件状态,"已提交/已应用"的筛选写 URL;setSearchParams 的读写需序列化(数组、嵌套对象用 URLSearchParams 约定或第三方序列化);刷新重入时 loader 从 searchParams 恢复查询,保证"刷新后筛选不丢";与服务端 SSR 协作:searchParams 在服务端可读,避免重复获取。边界:URL 是"全局共享状态",多表单多模块用"命名空间"(?filter=...&view=...)避免冲突。

答题先讲 URL 状态的价值(分享/恢复/回溯/SSR 感知),再讲 useSearchParams 的实践(提交时写入、序列化、loader 恢复),最后给"草稿与已应用分离、敏感数据不进 URL"的取舍边界。

#
★★★

7. React Router v8(Remix 演进)的 loader/action 模式与嵌套路由

React Router v8 的 loader/action 模式与嵌套路由如何工作?与 Remix 的演进关系是什么?

  • 嵌套路由的布局与数据归属
  • loader/action 在嵌套路由的并行与冒泡
  • Remix 演进(框架模式)的路线

React Router v7/v8 吸收 Remix 的"框架模式":嵌套路由(路由配置成树,父路由渲染 Outlet 嵌子路由)与数据路由(每层路由可有 loader/action)组合——导航时路由器"并行"执行所有命中路径的 loader(父与子的 loader 并发,而非串行等待),每层路由的 loader 数据由该层的 useLoaderData 读取(数据与布局层级对应);action 提交后 revalidate 会重跑"提交路由及以上层级"的 loader(数据层级刷新);嵌套路由让"布局的持久性"(父布局在子路由切换时不卸载)与"数据按层加载"(各层独立 loading/error)成为默认行为。

与 Remix 的演进:React Router v6→v7 把 Remix 的 data 能力(loader/action/Form/revalidate)合并进 React Router(Remix 成为 React Router 之上的全栈框架),v8 延续该路线并强化文件路由(file-based routing)、类型生成(typegen)与框架模式(framework mode:route module + loader + action 的约定式开发);SPA 与 SSR 共享同一套 loader/action 心智(SSR 时 loader 在服务端执行)。实践要点:嵌套数据避免重复获取(父 loader 数据经 useLoaderData 传递或复用)、错误边界按层(errorElement 各层独立)、加载状态按层呈现(useNavigation 全局 + 层内 Suspense)。边界:过度嵌套导致数据请求层级化延迟(并行虽好仍受最慢层约束)、action 冒泡的 revalidate 范围需理解(避免不必要的重取)。

答题先讲嵌套路由与 loader/action 的组合(并行 loader、分层数据、revalidate 范围),再讲 Remix 演进路线(v7 合并、v8 框架模式),最后给分层错误与重取范围的实践边界。

#
★★★

8. Conform(Progressive enhancement first、原生 FormData API)

Conform 表单方案的核心设计(Progressive enhancement first、原生 FormData API)是什么?它的工程价值与边界是什么?

  • 渐进增强优先的架构
  • 基于原生 FormData 的提交
  • 与服务端校验/Server Actions 的协作

Conform 是"渐进增强优先"的表单方案:表单状态(值、错误、校验)基于原生 HTML 表单语义(FormData、name 属性、required/pattern)构建——不使用 JS 时表单也能通过浏览器原生校验与提交(action 指向服务端端点);启用 JS 后 Conform 增强交互(实时校验、错误就地展示、提交状态),核心数据模型仍是 FormData(值从 FormData 读取、提交走 FormData),而非"受控 state + 序列化对象"。设计动机:与 React 19 的 form action/Server Actions 同源(

),服务端校验函数直接消费 FormData 并返回字段错误映射,客户端渲染错误——"一套表单逻辑两端复用"。

工程价值:无 JS 可用性(SEO/极端环境/降级)、与服务端校验单一来源(错误结构一致)、减少客户端状态与渲染(非受控 + FormData 读取)、与 React 19 生态(useActionState/useFormStatus)天然协作。边界:渐进增强要求"表单能脱离 JS 工作"的约束(复杂联动校验需服务端同样实现或接受降级);FormData 是字符串世界(数字/布尔/嵌套需约定序列化,Conform 提供字段路径约定);客户端实时体验(防抖校验、异步校验)仍需 JS 增强层;受控组件(自定义输入)需桥接(Conform 支持);与 RHF 对比:RHF 偏"客户端性能与校验生态",Conform 偏"表单的服务端一体化与渐进增强",选型取决于是否深度使用 Server Actions。

答题先讲"FormData 为数据核心 + 原生表单语义 + 无 JS 可用"的渐进增强架构,再讲与服务端校验/Server Actions 的单一来源协作,最后对比 RHF 给选型边界。

#
★★★

9. Formik 受控模式与现代取舍

Formik 的受控模式是什么?在现代 React 19 时代的取舍是什么?

  • Formik 的受控状态模型
  • 与现代方案(RHF、Conform、form action)的对比
  • 迁移与选型建议

Formik 是"受控模式"表单库:整个表单状态(values、errors、touched、isSubmitting)存放在组件 state 中,字段值经 onChange/onBlur 处理器同步进 state,每次输入都触发 React 渲染(values 变化 → 组件重渲染);提供 useFormik/formik 的 handleChange/handleSubmit 等 API,校验函数 validate/validationSchema 在提交与变更时运行。受控模式的优点:状态可见、可预测、易于调试与联动(任意字段变化驱动其它 UI);缺点:大型表单每次按键全表单重渲染(性能)、状态样板代码多。

现代取舍(React 19 时代):性能敏感的大型表单优先非受控方案——RHF(uncontrolled + ref + 字段级订阅)或 Conform(FormData 核心);简单表单可用原生 form action/useActionState(无需库);Formik 仍适合:中小型表单、团队熟悉、需要强受控联动、与 Yup 深度集成的存量项目;React 19 的 useOptimistic/Server Actions 生态与 Formik 受控模型叠加较繁琐(Formik 的状态需手动同步到 action 的 FormData);迁移建议:性能瓶颈明显(大表单卡顿)时迁 RHF(模式迁移:受控 → register/Controller),渐进式(先替换提交层再替换字段);选型原则:"字段多、性能敏感"用非受控/FormData 系;"交互联动复杂、状态可预测性优先"Formik 仍可用;新项目默认 RHF 或原生 form action。

答题先讲 Formik 的受控状态模型(values 进 state、每次输入重渲染)与优缺点,再对比 RHF/Conform/原生 form action 给现代选型,最后给迁移路径建议。

#
★★★

10. React 19 RCE 漏洞(CVE-2025-55182 等,19.0.1/19.1.2/19.2.1 修复)

React 19 的 RCE 漏洞(CVE-2025-55182 等)是什么?如何防护与升级?

  • 漏洞成因(dangerouslySetInnerHTML/序列化攻击面)
  • 受影响版本与修复版本
  • 升级与防护实践

React 19 在 2025 年修复了一系列远程代码执行(RCE)类安全漏洞(如 CVE-2025-55182,涉及 React DOM 服务端/客户端渲染路径中 HTML 序列化与 dangerouslySetInnerHTML 的边界处理不当,攻击者可构造恶意输入在渲染/水合时注入可执行内容),官方在 19.0.1、19.1.2、19.2.1 等补丁版本中修复;受影响的是对应旧版 19.0.x/19.1.x 及早期 19.2 版本,同时期 React 18.x 的等价攻击面也有相关修复(需查具体公告)。这类漏洞的共性:把"用户可控数据"经不受信路径进入 HTML 输出(服务端渲染字符串拼接、客户端 innerHTML 类 API)时,转义/序列化缺陷导致注入。

防护实践:立即升级到修复版本(>=19.0.1/19.1.2/19.2.1 对应线),并纳入依赖巡检(npm audit/SCA 工具)常态化;业务侧原则——用户内容默认转义(React 的 JSX 文本节点自动转义),避免把用户数据拼进 dangerouslySetInnerHTML 或 renderToStaticMarkup 的原始字符串(确需富文本用消毒库:DOMPurify 等白名单过滤);SSR 输出到客户端的水合数据(script 中的 JSON)用安全序列化(防 注入);对"渲染用户 HTML"的入口(评论区、富文本)做输入输出双端校验;安全公告订阅(React 官方 blog/security advisories)与依赖锁定(package-lock + CI 漏洞扫描)。整体:补丁升级是治标,输入消毒与最小权限是治本。

答题先讲漏洞性质(渲染/序列化路径的注入面)与版本修复对应,再讲升级巡检与"默认转义、富文本消毒、SSR 数据安全序列化"的防护实践。

#
★★★

11. React 19 RSC + next/form 在渐进增强(PWA/Slow Network)

React 19 的 RSC + next/form 如何支持渐进增强?在 PWA 与慢网络下的价值是什么?

  • next/form 的无 JS 提交语义
  • RSC 与 Server Actions 的渐进增强
  • 慢网络/PWA 的体验价值

next/form 与 React 19 的 Server Actions 组合实现"JS 可用则增强、无 JS 则原生提交"的渐进增强:

在客户端 JS 加载后由 React 接管(FormData 经 Flight 序列化调用服务端函数、流式返回结果、自动管理 pending 与重置);无 JS 或 JS 加载失败时,表单退化为原生 HTML 提交(POST 到 Server Action 端点,服务端渲染返回完整页面),功能不丢失。RSC 在其中提供"服务端渲染的表单初始状态 + 服务端校验":初始值/错误由服务端渲染注入(无客户端状态依赖),提交后的结果由服务端渲染返回或流式更新。

PWA/慢网络价值:弱网/离线环境下 JS bundle 加载慢或失败,原生表单提交仍可用(HTML 表单不依赖 JS);离线可用性配合 Service Worker 缓存 HTML;慢网络下表单首交可用(原生 POST 比"下载 JS → 水合 → 交互"更快到达服务端);流式返回让提交结果渐进呈现;减少客户端状态(表单值在 DOM/FormData,水合负担小);降级路径一致(同一 action 端点)。边界:渐进增强要求表单语义完整(name 属性齐全、服务端校验兜底——客户端校验仅增强);复杂交互(联动、即时校验)在无 JS 下退化为服务端往返校验(体验降级但功能可用);PWA 离线提交需队列与同步策略(workbox 后台同步)补充。工程上"先服务端可用、再客户端增强"的顺序保证鲁棒性。

答题先讲"JS 增强、无 JS 原生提交"的双轨机制(form action + Flight + 服务端渲染返回),再讲慢网络/PWA 的价值(无 JS 可用、首交快、水合轻),最后给语义完整性与离线同步的边界。

#
★★★

12. React Hook Form 在非受控 + Controller 模式与 React 19 受控提交的取舍

React Hook Form 的非受控 + Controller 模式与 React 19 受控提交之间如何取舍?

  • RHF 的非受控核心与 Controller 桥接
  • React 19 受控提交(form action + state)的语义
  • 混合使用的工程决策

RHF 的核心是非受控:register + ref 让值驻留 DOM/表单 store,输入不触发 React 渲染;Controller 把"必须受控"的组件(自定义输入、日期选择器、富文本)桥接进 store(value/onChange 与 store 同步)。React 19 的"受控提交"是另一条路线:

由 React 管理提交(action 状态、pending、自动重置),字段值从 FormData 读取(非受控字段)或从受控 state 手动合并——它把"提交"作为一等语义(useActionState/useFormStatus 配套),而 RHF 把"输入性能与校验"作为一等语义。

取舍:使用 RHF 时,提交层可以接入 React 19——handleSubmit 回调里调用 server action(或构建 FormData 传 action),保留 RHF 的字段性能与校验;不使用库时,原生 form action + 受控/非受控混合(简单表单足够);决策矩阵——字段多、校验复杂、联动多:RHF(非受控默认 + Controller 补充);表单简单、要与 Server Actions 深度绑定、渐进增强优先:原生 form action + FormData;两者组合(RHF 管字段、action 管提交)在大型表单中常见,注意"受控字段值要合并进 FormData"(getValues + 手动 append)与"RHF 的 handleSubmit 与 action 的提交时序"(先本地校验再调用 action);React 19 受控提交的边界:字段级性能仍需 RHF 类方案补足(form action 不解决输入渲染问题)。

答题先讲 RHF 的"非受控默认 + Controller 桥接"与 React 19 的"提交语义"两条路线定位,再给决策矩阵(按表单复杂度与 Server Actions 绑定度),最后讲组合模式与字段合并的注意点。

#
★★

13. React Router v8 与 Remix v3 在 file-based 路由、loader/action 与 SSR 的合并路线取舍

React Router v8 与 Remix v3 在 file-based 路由、loader/action 与 SSR 上的合并路线与取舍是什么?

  • 框架模式与数据路由的统一
  • file-based 路由的路线
  • SSR/SPA 的一体化取舍

Remix v3 与 React Router v7/v8 的合并路线是"一套数据路由内核,两种运行形态":Remix 成为 React Router 之上的全栈框架(SSR/部署/服务端数据),React Router 保持库形态(SPA 优先、可自选 SSR);v7 起两者共享同一 loader/action/Form/revalidate 数据模型(Remix 的 data 能力并入 React Router),v8 强化 file-based 路由(约定式路由文件 → 类型生成 typegen)——文件路由在 Remix(服务端约定)与 React Router(v8 的 framework mode)中统一,减少"库用法与框架用法"的心智分裂。取舍要点:选择 Remix v3 意味着接受"框架约定"(文件路由、部署目标、SSR 默认);选择 React Router 库形态意味着自由组合(任意部署、SPA/SSR 自管)但需自建约定。

SSR 取舍:Remix v3 提供开箱的 SSR 数据加载(loader 服务端执行、流式、部署适配);React Router v8 的框架模式也可 SSR(自配服务器渲染管线),SPA 下 loader 客户端执行——同一套路由代码两端可用是合并的最大收益(写一次、SPA/SSR 按部署切换);工程决策:需要全栈框架(服务端数据、部署管线、约定式)选 Remix;已有 SSR 基建或纯 SPA、需控制栈的选 React Router v8 库 + 自组装;迁移路径:React Router 数据路由代码可平滑迁到 Remix(API 同源);注意 v8 仍演进中(typegen/框架模式版本差异),锁版本做决策。

答题先讲合并路线的本质(共享数据路由内核 + file-based 路由 + 两种运行形态),再讲 SSR 取舍(Remix 全栈开箱 vs Router 自组),最后给选型与迁移建议。

#
★★

14. React Router v8 的数据加载 API(loader/action)与 TanStack Router 的 type-safe loader 在工程边界

React Router v8 的 loader/action 与 TanStack Router 的 type-safe loader 在工程边界上有什么差异?

  • 两套 loader 的数据契约差异
  • 类型安全的深度与生成机制
  • 工程适用边界

React Router v8 的 loader/action:loader 返回任意数据(组件内 useLoaderData 泛型需开发者标注),数据契约靠"约定 + 手动类型";action 返回数据经 useActionData 读取;loader 与组件之间类型关联是"软"的(类型错误不阻断编译,靠开发者维护同步)。TanStack Router 的 type-safe loader:loader 的返回类型经 routeTree 代码生成(routeTree.gen.ts)贯通到 useLoaderData/useMatch——路径、search、loader 数据全部类型推导,loader 与组件的数据契约编译期强制(改 loader 返回类型,消费端立即报错);search params 经 schema(Zod/Valibot)校验并推导类型(运行时校验 + 类型一致)。

工程边界差异:React Router——生态大、API 简单、SSR 部署一体化、迁移成本低,适合"类型要求宽松、快速迭代、与框架全栈集成";类型安全可通过手动泛型获得但需纪律维护;TanStack——类型安全是核心竞争力,适合"URL 状态复杂、团队强类型文化、希望编译期拦截路由错误"的项目;代价是代码生成工具链依赖、schema 双写、动态路由/非 TS 场景收益下降;两者在"数据获取"层面都支持 loader/deferred/revalidate,差异集中在类型契约的强制程度;实践中,React Router 可结合 zod 手动强化(loader 返回校验 + 类型推导),弥补软类型;选型看"类型安全的优先级"与"工具链成本"的平衡。

答题先对比两套 loader 的数据契约(软类型 vs 代码生成强制类型),再讲 typegen/schema 双写的机制差异,最后给工程适用边界(生态 vs 类型文化)与 React Router 手动强化路径。

#
★★

15. Next.js 15 的 App Router(app/)的 RSC、Server Actions 与 'use client' 边界与现代组件分层

Next.js 15 的 App Router 中 RSC、Server Actions 与 'use client' 边界如何组织?现代组件分层是什么?

  • app/ 目录的组件默认服务端
  • Server Actions 的声明与调用
  • 组件分层模式(叶子交互)

Next.js App Router(app/)中组件默认是 Server Components:可直接 async 获取数据(服务端执行)、渲染静态内容;交互需求用 'use client' 声明客户端组件(有 state/effect/事件);Server Actions 用 'use server' 声明(文件或函数级),在客户端通过 form action/事件调用,服务端执行写操作。'use client' 边界的组织原则:边界"下移"到交互叶子——页面/布局保持服务端(数据获取、SEO、静态渲染),只有真正交互的组件(表单、按钮、列表项操作)标记客户端;客户端组件可通过 children 插槽接收服务端渲染的内容(不必全屏客户端化);跨边界 props 必须可序列化。

现代组件分层模式:"服务端外壳 + 客户端叶子"——布局(服务端,含导航静态部分)、页面(服务端,async 取数)、交互模块(客户端叶子,自包含状态与 action 调用)、服务端动作层(Server Actions 统一写操作与校验);数据流:服务端组件取数 → props/children 传递 → 客户端叶子通过 action 变更 → 服务端 revalidate/重渲染返回新数据;注意点:'use client' 文件中的所有导出都是客户端模块(公共工具需拆分);客户端组件 import 服务端组件被禁止(用 children 组合);Server Actions 的序列化边界(参数/返回值可序列化);Next.js 15 中 caching(fetch 缓存、router cache)与 RSC 分层配合(服务端数据缓存、客户端导航缓存);错误边界与 loading(error.tsx/loading.tsx)按路由层组织。这套分层把"数据、渲染、交互、变更"按能力边界清晰切分,是 App Router 的推荐架构。

答题先讲默认服务端 + 'use client' 边界 + Server Actions 的机制,再讲"服务端外壳 + 客户端叶子"的分层模式与 children 插槽组合,最后给模块拆分与缓存协作的注意点。

#
★★

16. react-hook-form 7 与 TanStack Form 在 uncontrolled input、订阅与 Zod schema 校验的现代取舍

react-hook-form 7 与 TanStack Form 在 uncontrolled input、订阅与 Zod schema 校验上的现代取舍是什么?

  • 两者的非受控实现差异
  • 字段级订阅模型对比
  • 校验与生态的取舍

两者都主打"非受控/低重渲染":RHF7 用 register + ref 让值驻留 DOM(store 独立于 React),输入不触发 React 渲染;TanStack Form 用 field 的 getValue 获取值(值也存于独立 store 并缓存 DOM 更新),字段级订阅(useField/field.subscribe)只重渲染订阅的组件,两家的性能模型接近,差异在 API 形态:RHF 的 register 是"声明式挂载",TanStack 的 field API 更显式( 渲染模式,支持派生状态、字段元数据,团队投票数据表明其可组合性更强)。订阅模型:RHF 的 watch/useWatch 按字段订阅,TanStack 默认字段级 + 派生状态(computed values)更体系化。

Zod schema 校验:RHF 通过 resolver(@hookform/resolvers 支持 zod)在提交/变更时校验,错误映射到字段;TanStack 内置 standards(zod 标准校验器)在 value 层校验并把错误绑定字段;差异在"校验时机与错误传播"的默认行为(RHF 需配置 mode,TanStack 更内建)。现代取舍:RHF 生态成熟(社区大、教程多、与 RHF 配套的 UI 库适配广)、迁移文档多;TanStack Form 类型安全更强(表单值全泛型、与 Zod 的推断贯通)、派生状态更先进,但生态较新;选型:团队生态与招聘池优先 RHF;强类型文化、复杂派生表单优先 TanStack;两者都支持"uncontrolled 默认 + Controller/useField 桥接受控"与 SSR;性能对比在多数表单上差异可忽略,决策更多基于 API 偏好与生态。

答题先对比非受控实现与订阅模型(register vs field API),再对比 Zod 校验集成(resolver vs standards),最后给生态成熟度与类型文化的选型建议。

#
★★

17. Valibot 与 Zod 4 在 bundle size(tree-shake 友好)

Valibot 与 Zod 4 在 bundle size 与 tree-shake 友好性上的差异是什么?工程上如何选型?

  • Zod 4 的模块化改造与体积
  • Valibot 的 tree-shake 设计
  • 按体积与生态的选型

Zod 4 针对体积做了模块化重构:引入"新模块化架构"(根导出 + 子路径导出、按需导入 schema 类型),核心库比 Zod 3 显著缩小(常见场景 bundle 减少约 50%+),且新增了 mini 级 API(如 zod/mini 的独立校验器,几乎零运行时)与标准校验器(standards);但 Zod 4 的整体设计仍是"一个库多个入口",tree-shake 依赖使用者按子路径导入,默认根导入仍会带入较多代码。Valibot 从设计上就是 tree-shake 优先:每个校验函数独立导出(如 import { email } from 'valibot')、无默认根导出聚合、死代码消除彻底——只导入用到的校验器,bundle 增量可按 KB 级计算(常用于"库作者/表单库"场景,RHF/TanStack Form 等的体积敏感集成)。

工程选型:体积敏感(组件库、SDK、页面性能预算紧张)优先 Valibot(或 Zod 4 的按需子路径 + standards);生态与体验优先 Zod(类型推断文档、RHF resolver 兼容、更成熟的社区与第三方适配);两者 API 相近(Zod 4 的 standards 与 Valibot 的 schema 风格趋同),迁移成本可控;实际工程:先按"校验逻辑的复杂度"(简单字段校验用 Valibot/standards 轻量层,复杂 schema 用 Zod)分层,再按构建产物对比(webpack-bundle-analyzer 对比同一 schema 的实现体积);注意 tree-shake 的生效条件(ESM + sideEffects: false 配置正确),否则体积优势不体现;运行时与类型推断的双写问题两者都需管理(Valibot 的类型推断相对弱些,Zod 4 更强)。

答题先讲 Zod 4 的模块化与体积改进、Valibot 的 tree-shake 优先设计,再给体积敏感 vs 生态优先的选型标准与分层实践,最后提 tree-shake 生效条件。

#
★★

18. React 19 的 form Actions 与 Progressive Enhancement 在没启用 JS 时的表单提交工程价值

React 19 的 form Actions 与渐进增强在"没启用 JS"时的表单提交有什么工程价值?如何实现?

  • 无 JS 时的原生表单提交路径
  • 服务端渲染的响应与状态恢复
  • 渐进增强的实现要求

React 19 的 form action(

)的渐进增强价值:启用 JS 时 React 接管提交(FormData 经 Flight 调用 Server Action、pending 管理、流式返回结果);未启用 JS(或 JS 加载失败/被禁用)时,表单按原生 HTML 语义提交——浏览器把表单字段(name 属性)编码为 POST 请求发往 action 对应的服务端端点,服务端执行相同逻辑并返回完整 HTML 页面(RSC 渲染的服务端响应)。核心价值是"功能可用性不依赖 JS":慢网络(JS bundle 未下载完)下用户仍能提交;极简/降级环境下(无 JS 的嵌入、爬虫、辅助技术环境)功能不缺失;同一条 Server Action 代码两端复用,无重复实现。

实现要求:表单必须有完整语义——字段 name 属性齐全(FormData 键)、提交按钮 type="submit"、action 端点可被原生 POST 到达(框架生成,如 Next.js 的 action ID 端点);服务端渲染返回页(无 JS 时浏览器整页导航);客户端状态(useActionState 的初始值、错误)需由服务端渲染携带(SSR 序列化初始状态),刷新/无 JS 时一致性;客户端校验只是增强(服务端校验兜底,无 JS 时靠服务端错误响应);表单重置与状态恢复在无 JS 时由浏览器/服务端渲染完成。工程上先保证"无 JS 路径可工作"(渐进增强的底线),再叠加客户端增强。

答题先讲双轨提交机制(JS 增强 vs 原生 POST 到同一端点),再讲无 JS 场景的价值(慢网络可用、环境降级),最后给语义完整、服务端校验兜底与初始状态序列化的实现要求。

#
★★

19. Next.js 15 的 useFormStatus/useFormState 与 react-hook-form 在大型表单的边界

Next.js 15 的 useFormStatus/useFormState 与 react-hook-form 在大型表单中如何分工?边界在哪里?

  • 原生 form action 体系的能力范围
  • RHF 的字段级能力
  • 大型表单的组合模式

useFormStatus/useFormState(useActionState)属于 React/Next.js 的原生 form action 体系:管理"提交过程"(pending、进度)与"提交结果"(服务端返回的 state),适合"提交层"语义;它们不提供字段级能力(字段值管理、校验、联动、脏值检测、数组字段)——这些是 react-hook-form 的主场(非受控注册、resolver 校验、useFieldArray、字段级订阅)。大型表单的分工边界:RHF 管"字段层"(值、校验、错误、联动),form action 体系管"提交层"(pending、服务端结果、渐进增强);组合模式——RHF 的 handleSubmit 中调用 Server Action(或构造 FormData 传给 action),useActionState 管理 action 返回值与 pending,useFormStatus 在按钮层显示状态。

边界细节:不要混用"两套状态"——RHF 的 errors 与 action 返回的字段错误需统一(action 返回结构化错误后映射进 RHF 的 setError,或服务端校验并入 RHF resolver);RHF 的 isSubmitting 与 useFormStatus 的 pending 语义不同(前者客户端 submit 流程、后者 form action 过程),选其一驱动 UI;大型表单的"草稿/恢复"(值存 URL/localStorage)由 RHF 层负责(form action 不提供);SSR 初始值:RHF 的 defaultValues 与 action 的初始 state 各司其职(表单初始值用 RHF、提交状态用 action);性能:字段级输入性能是 RHF 的收益,form action 不介入输入路径。选型:表单复杂选 RHF + 提交接 action;表单简单直接 form action。

答题先划分"字段层(RHF)vs 提交层(form action 体系)"的能力边界,再讲组合模式(handleSubmit 调 action、错误映射、pending 语义区分),最后给草稿恢复与初始值分工。

#
★★

20. TanStack Form 在多实例表单、字段级订阅与跨组件状态共享的现代工程价值

TanStack Form 在多实例表单、字段级订阅与跨组件状态共享上有什么现代工程价值?

  • 多实例表单的状态隔离
  • 字段级订阅的渲染控制
  • 跨组件共享(form store 传递)模式

TanStack Form 的现代工程价值:多实例——每个 useForm 创建独立实例(状态隔离),同一表单组件可被多次渲染(表格行表单、列表编辑)而互不串扰;实例可通过 useFormContext 在子树内共享(Provider 模式),支持"表单状态跨组件访问"(页头提交按钮读表单状态、侧栏展示校验摘要)而无需 props 层层传递;字段级订阅——field 的 subscribe/useField 让"只有订阅了某字段的组件"随该字段变化重渲染(值、错误、元数据各自可订阅),大型表单中"状态摘要、错误面板"等派生 UI 精确响应,避免整表单重渲染。

组合价值:多实例 + 字段订阅解决"重复结构表单的性能与隔离"(如购物车行、动态字段列表用字段数组 + 每行独立订阅);跨组件共享配合"派生状态"(computed values)可构建"提交按钮禁用逻辑、未保存提示、进度统计"等跨字段 UI;类型安全(表单值泛型贯通)让多实例与共享场景的代码更稳;与 React 19 协作:提交层可接 form action(handleSubmit 调 Server Action),字段订阅与并发渲染兼容(订阅是响应式的);边界:跨组件共享靠 context 意味着"组件树内"共享(跨路由需外部 store);过度字段订阅反而增加订阅管理成本(按需订阅原则);多实例的表单数据同步(联动)需显式设计。工程上"实例隔离 + 字段订阅 + context 共享"覆盖了大型复杂表单的绝大多数架构需求。

答题先讲多实例隔离与 context 共享(Provider + 跨组件访问),再讲字段级订阅的渲染精确性,最后给组合场景(行表单、派生 UI)与订阅成本的边界。

#
★★

21. React Router v8 的 revalidate 机制(afterAction 自动 refetch loader)

React Router v8 的 revalidate 机制(afterAction 自动 refetch loader)是什么?工程应用与边界是什么?

  • action 提交后自动 revalidate 的时机
  • 数据新鲜度的保障模型
  • 细粒度控制(不重取/条件重取)的边界

React Router v8 的数据路由中,action 成功完成后会自动触发 revalidate:重跑"当前路由及其上层"的 loader,用最新数据刷新 useLoaderData——形成"提交 → 数据刷新"的闭环(类似 SWR 的 mutate 与 RSC 的 revalidatePath 的心智)。机制要点:revalidate 的默认范围是"提交所在路由 + 祖先路由"(子路由提交不刷新兄弟路由的数据);action 返回的数据经 useActionData 读取(供即时反馈),loader 重取用于权威数据;revalidate 是自动的(无需手动调用),导航与提交共用 loader 执行管线。

工程应用:CRUD 表单提交后列表自动刷新(action 更新数据库 → loader 重取列表);乐观更新 + revalidate 组合(先展示乐观结果,revalidate 后收敛为真实数据);配合 deferred 实现"先刷新快的、再补慢的"。边界与细粒度控制:默认 revalidate 可能重取不必要的数据(父路由大列表)——可用 shouldRevalidate(自定义跳过逻辑,比较提交目标与 loader 依赖)或把 loader 拆细(按层级缓存);action 失败不触发 revalidate(错误留在 action 层处理);导航型 revalidate(进出路由)与提交型 revalidate 的触发条件不同(shouldRevalidate 可区分);频繁提交(如每行编辑)需控制 revalidate 频率(并发去重由路由器合并);SSR 下 revalidate 在服务端执行(注意服务端缓存与 revalidate 的交互)。工程上"自动 revalidate 保新鲜 + shouldRevalidate 控范围"是标准组合。

答题先讲"action 成功后自动重跑 loader"的闭环机制与范围(本路由 + 祖先),再讲 CRUD/乐观更新应用,最后给 shouldRevalidate 细粒度控制与失败不重取的边界。

#
★★

22. 现代 React 生态(Codemod、ESLint React Hooks 插件、react-compiler)

现代 React 生态中的 Codemod、ESLint React Hooks 插件与 react-compiler 各扮演什么角色?如何协同?

  • Codemod 的自动化迁移
  • ESLint 插件的规则治理
  • Compiler 的自动优化

三者的角色:Codemod(react-codemod 等)是"自动化重构工具"——用脚本批量改写代码(如旧生命周期迁移、React.createElement → JSX 新转换、Class → 函数组件辅助、移除 forwardRef 等 React 19 变更),解决跨版本迁移的重复劳动,风险是改写后的行为差异需测试覆盖;ESLint React Hooks 插件(eslint-plugin-react-hooks)是"规则治理层"——rules-of-hooks 检查 Hook 调用规则、exhaustive-deps 检查依赖数组完整性,在开发期拦截模式错误,React 19 时代与 Compiler 对齐(建议移除冗余依赖);react-compiler(babel-plugin-react-compiler + eslint-plugin-react-compiler)是"优化层"——编译期自动记忆化(替代手工 memo/useMemo),并诊断无法优化的代码。

协同方式:版本升级流程——先用 eslint 插件扫出存量违规(规则治理)→ 用 Codemod 批量完成机械性迁移 → 启用 Compiler 并处理 bailout 报告(编译诊断)→ 回归测试 + 性能门禁验证;日常开发——lint 插件保证"代码符合规则"(新代码零违规),Compiler 自动优化(无需手工记忆化),Codemod 在依赖升级(React 19、Router v7/v8、RHF8 等)时按官方脚本批量迁移;三者共同构成"自动化优先"的工程文化:规则自动检查、迁移自动执行、优化自动生成,人工聚焦架构决策。边界:Codemod 不保证行为等价(需 review 与测试)、lint 不保证运行时正确、Compiler 不覆盖 bailout 代码——三层都需人工兜底与验证。

答题按"迁移(Codemod)、规则(ESLint)、优化(Compiler)"三角色展开,再讲升级流程与日常开发的协同链路,最后给三层各自的行为等价与覆盖边界。

#
★★

23. Final Form 与 React Hook Form 在大型表单性能、订阅与可恢复性的取舍(现代 React 19 时代)

Final Form 与 React Hook Form 在大型表单的性能、订阅与可恢复性上如何取舍?React 19 时代如何选择?

  • 两者的订阅模型与渲染控制
  • 表单状态的持久化与恢复
  • React 19 时代的选型

性能与订阅:Final Form 是"订阅制"表单库(redux-form 继任者):字段用 Field 组件订阅(render props),表单状态存于外部 store(final-form 内核),值变化只重渲染订阅的字段(细粒度订阅、与 UI 解耦、可在 React 外使用内核);RHF7 用 ref 注册 + 独立 store,输入不触发 React 渲染,watch/useWatch 字段级订阅——两者的"低重渲染"目标一致,实现不同(Final 靠字段组件订阅、RHF 靠 ref + 显式订阅),性能上大型表单都远优于受控方案;可恢复性(表单状态持久化与重入恢复):Final Form 的 store 与 React 解耦,状态序列化(values/formState)与恢复(restore 方法)更直接(redux 生态友好);RHF 的 defaultValues 可从持久化数据恢复、值驻留 DOM 使"刷新重入"(配合 URL/存储)恢复成本低;两者都支持"中途离开保存草稿"(序列化 values + 恢复)。

React 19 时代取舍:Final Form 维护趋缓(官方进入维护模式、无新特性,与现代并发特性/Server Actions 生态的集成需自建),适合存量大型项目;RHF 活跃(紧跟 React 19:resolver 生态、与 form action/Server Actions 的组合模式成熟、useFieldArray 与动态表单),新项目优先 RHF;订阅语义的团队熟悉度也是决策因素(Final 的 Field 组件模式 vs RHF 的 register 模式);可恢复性需求强的场景(草稿恢复、多步骤表单持久化)两者都能实现,RHF + URL/存储的组合在现代实践更常见。结论:新项目选 RHF(生态与 React 19 对齐),存量 Final Form 项目评估迁移收益(大型表单迁移成本高,可保留)。

答题先对比订阅模型(Field 订阅 vs ref + watch)与性能,再讲可恢复性(store 解耦 vs defaultValues 恢复),最后给维护状态与 React 19 生态的选型建议。

#

24. React Router v8 的 data router 在加载阶段如何配合 Suspense 边界做 streaming——加载器抛出的 deferred promise 何时被路由器消费,何时由组件层 fallback 接管

React Router v8 的 data router 加载阶段如何配合 Suspense 边界做 streaming?deferred promise 何时被路由器消费、何时由组件层 fallback 接管?

  • loader 的 deferred promise 机制
  • 路由器消费与组件消费的分工
  • 流式加载的边界设计

React Router v8 的 loader 可以返回 deferred 对象(defer({ critical, slow: slowPromise })):路由器加载时立即解析"关键数据"(critical),而慢数据(slow promise)不阻塞导航——首帧先渲染"关键数据已就绪"的 UI;组件侧用 (或 useAsyncValue)消费 promise:Await 内部是 Suspense 边界,promise 未 resolve 时显示 fallback(组件层接管),resolve 后渲染数据内容(组件层继续)。分工:路由器消费"promise 的外层"(决定导航何时完成、首帧何时可渲染——只在关键数据就绪时完成导航),组件层消费"promise 的值"(决定具体内容何时显示——慢数据到达时局部流式填充),两端通过"deferred 包装"衔接。

流式加载的工程边界:deferred 适合"关键路径数据(快)与次要数据(慢)分离"的场景(页面主内容 + 评论区/推荐列表);路由器在关键数据未就绪时导航处于 pending(useNavigation state 可显示全局加载态),关键数据就绪后即使慢数据未到也完成导航(URL 更新、页面渲染)——这就是"streaming 的导航层语义";组件层 Await 的 fallback 粒度决定"局部 loading 的呈现"(骨架屏 vs 整块);错误处理:slow promise reject 时 Await 附近用 errorElement 或 ErrorBoundary 兜底;并发:多个 deferred 并行加载(loader 内 Promise.all 控制);SSR 时 deferred 支持流式补发(服务端先发关键 HTML、慢数据到达后流式注入)。边界:deferred 不适用"首屏强依赖"的数据(关键数据应同步返回);滥用 deferred(全部数据都包)会让首屏碎片化,需按"数据重要性"划分。

答题先讲 loader deferred 与 Await/Suspense 的分工(路由器管导航完成时机、组件层管内容呈现时机),再讲流式填充的边界设计(关键/次要数据分离、pending 语义、错误兜底),最后给滥用风险。

#

25. React Router v8 的 framework 模式(route module + action + loader)

React Router v8 的 framework 模式(route module + action + loader)是什么?与 library 模式的区别是什么?

  • route module 的约定式结构
  • framework 模式的运行形态
  • 与 library 模式的取舍

framework 模式是 React Router v8 的约定式开发模式:路由文件(route module)按约定导出——default(组件)、loader(数据获取)、action(提交)、ErrorBoundary、handle 等;文件路由系统(基于文件目录生成路由树 + 类型生成 typegen)让"每个路由的 UI + 数据 + 提交 + 错误"集中在一个模块内;运行时由框架(或自配置的 framework 服务器)执行 loader/action(SSR 时服务端、SPA 时客户端),配合 useLoaderData/useActionData/useNavigation 消费。library 模式则是 v6 风格的"代码定义路由 + 手动组件组织":开发者自建路由配置、自管数据获取(useEffect 或任意方案),React Router 只提供导航与匹配。

区别与取舍:framework 模式——约定化(结构统一、路由模块自包含)、数据流内置(loader/action/revalidate/错误边界开箱)、类型生成(typegen 编译期安全)、适合新项目与团队规范化;代价是学习约定、代码生成依赖、路由结构与部署形态绑定(framework server);library 模式——灵活(任意数据方案、任意结构)、迁移平滑(存量 v6 代码)、但数据与错误管理需自建(样板代码多)。工程决策:新项目/需要统一架构选 framework;存量 SPA/混合架构选 library(或渐进引入 framework 路由);两者可在同一应用过渡(v8 支持逐步采用)。现代 React Router 的路线是 framework 优先(官方推荐),Remix 全栈能力在其上构建。

答题先讲 route module 的约定式导出与文件路由、类型生成,再对比 library 模式的灵活与自建成本,最后给新老项目的选型建议与过渡路径。

#

26. React Hook Form 8(beta)与 TanStack Query 在受控/非受控、字段级订阅与异步校验的协作模式上有哪些演进——表单状态与远端缓存的去重与冲突解决应如何在客户端 + Server Action 组合下设计

React Hook Form 8(beta)与 TanStack Query 在受控/非受控、字段级订阅与异步校验上有哪些演进?表单状态与远端缓存的去重与冲突如何设计?

  • RHF8 的 API 演进(字段级订阅等)
  • 表单状态与 Query 缓存的去重
  • Server Action 组合下的冲突解决

RHF8(beta)的演进方向:更强的字段级订阅与派生状态(字段状态订阅粒度细化、订阅模式现代化)、更好的类型安全(表单值泛型、与 schema 推断贯通)、对受控组件的桥接改进(Controller 现代化)与异步校验的原生编排(校验队列、防抖/取消);与 TanStack Query 的协作模式:分工——RHF 管"表单输入状态"(值、校验、脏值),Query 管"远端数据缓存"(数据获取、缓存、失效);去重设计——表单初始值(defaultValues)从 Query 缓存读取(useQuery 的 data 作为初始值,避免重复请求);提交时"乐观更新":先更新 Query 缓存(queryClient.setQueryData)或 useMutation 的 onMutate 乐观回滚,再提交服务端;校验与查询分离:异步校验(唯一性检查、可用性查询)用 useQuery 的 enabled(依赖字段值触发查询),把"校验型请求"纳入缓存与去重(同参数查询命中缓存不重复发);字段级订阅让"校验中/错误"只重渲染对应字段。

Server Action 组合下的冲突解决:提交路径——RHF handleSubmit → Server Action(服务端校验与写库)→ 成功后使 Query 缓存失效(invalidateQueries/revalidatePath 按 tag)或直接 setQueryData 用 action 返回值更新(避免整列表重取);冲突场景——乐观更新与服务端结果不一致:用 useOptimistic(表单层乐观值)配合 Query 的乐观回滚(onError 恢复),服务端返回权威数据后以 action 结果覆盖缓存;多端并发编辑:以"服务端版本号/updatedAt"做冲突检测(提交前比对,冲突提示),缓存层以"最后写入 + 版本号"收敛;去重原则:单一事实来源——"表单值"以 RHF 为准、"远端数据"以 Query 缓存为准,中间经"初始化(Query→defaultValues)与提交(RHF→Action→Query)"两条显式桥接,避免双写同一数据源。

答题先讲 RHF8 演进(订阅粒度、类型、异步校验编排),再讲与 Query 的分工与去重(初始值来自缓存、校验型请求入缓存、提交后失效或直接更新),最后给乐观更新与服务端冲突的解决设计。