想象你经营着餐饮集团“云膳”,旗下有三种店:高档餐厅“云顶”、连锁面馆“一碗香”、深夜食堂“云夜”。过去的管理方式是:总部规定每家门店必须用同一种盘子、同一种摆盘,连装修风格都统一。这就像传统组件库——样式与逻辑捆绑销售,换皮肤就得伤筋动骨。现在换一种方式:总部建一座中央厨房,把最难做的菜统一做成标准半成品包;门店拿到后,按自己的风格加热、摆盘、加装饰。火候、手法、安全标准由总部把关;摆盘、配色、氛围由门店决定。这个模式,正是 Headless UI 组件库的核心思想:把逻辑做成“半成品包”,把长相交给使用方。本文就用这座中央厨房当导游,带你从设计理念、核心原理到工程落地,把这套组件库设计方法完整吃透。

岗位关联

掌握 Headless UI 组件库的设计与封装,是把前端组件做成“多框架复用、无障碍内置、样式可换肤”的公共能力;对 AI 应用开发者而言,这也是让 AI 生成的 UI 代码自带可靠交互行为的技术底座。

目录
  1. 可视化演示
  2. 设计理念
  3. 核心原理
  4. 实现模式
  5. 无障碍交互
  6. 生态对比
  7. 工程化实践
  8. 封装策略
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Headless UI模式的逻辑视图分离架构、Props契约设计与WAI-ARIA无障碍支持,切换模式查看核心设计原则。

本节配有交互动效(进入视口后自动加载,约 3KB)。

逻辑与视图分离架构 UI层:纯展示组件,可任意替换Tailwind/CSS Hook层:状态管理与交互逻辑封装 状态层:useOpen/useActive等响应式数据 Component = useHook() + render() Headless模式实现逻辑复用与视图自由

设计理念

云膳集团把“后厨与门店分离”这个决定,经历了三个阶段。最早是夫妻店:一个大厨从前台收银到后厨颠锅全包,对应早期前端把 HTML、CSS、JS 混在一起写。接着是连锁时代:总部连装修带菜谱统一供给,门店省心但千篇一律,这对应 Bootstrap、Ant Design 这类样式组件库——上手快,想改品牌风格却很难。最后才是中央厨房时代:总部只产出标准化的半成品包,门店自主摆盘,这就是 Headless UI。

技术上的“半成品包”就是组件的逻辑内核:状态的开关、键盘的响应、读屏器的语义标注。它不包含任何颜色、圆角、间距等长相信息,也不直接产出视觉 DOM。消费方拿到这份半成品包后,自己决定渲染成什么样子,这就叫“无头”——有大脑,没有五官。

这样设计有三个直接收益:第一,逻辑可跨框架复用,同一套行为在 React、Vue、Svelte 里都能用;第二,复杂交互与无障碍在源头就保证,不用每个项目重写;第三,视觉完全自由,设计系统、主题换肤都变成门店摆盘的事。不过它也有边界:如果只是搭一个静态营销页,用 Headless 就像为炒一盘蛋炒饭建一座中央厨房,属于过度设计。

回到云膳:这套公式说明,“云顶”可以在半成品包上摆法餐的盘,“一碗香”浇上自己的辣油,“云夜”撒一把葱花取暖。菜还是那道菜,端上桌却各有各的活法——这就是逻辑与视觉分离的底层哲学。

Component=Headless⁡⏟状态⋅交互⋅无障碍+Styled⁡⏟布局⋅皮肤⋅动效\displaystyle \text{Component}=\underbrace{\operatorname{Headless}}_{\text{状态·交互·无障碍}}+\underbrace{\operatorname{Styled}}_{\text{布局·皮肤·动效}}
这个公式说明一个完整的 UI 组件 = 无头逻辑 + 有头皮肤,两部分自由组合。
  1. Headless(逻辑层)指状态、交互行为、无障碍语义这些与长相无关的部分,是整个公式的地基。
    Headless⁡={state, actions, a11y}\displaystyle \operatorname{Headless}=\{\text{state},\ \text{actions},\ \text{a11y}\}
  2. Styled(视觉层)只负责颜色、间距、排版、动效等观感部分,可以随时整体替换。
    Styled⁡={class, theme, layout}\displaystyle \operatorname{Styled}=\{\text{class},\ \text{theme},\ \text{layout}\}
  3. 组件合成两部分通过约定的接口拼装,同一个 Headless 可以配多个不同的 Styled,实现“一菜多吃”。
    Component=Headless⁡+Styled⁡\displaystyle \text{Component}=\operatorname{Headless}+\operatorname{Styled}

核心原理

中央厨房的秘诀在“流程卡”。一份红烧牛肉从选料到出锅,每一步都有标准作业流程:切块尺寸、焯水时间、收汁火候。门店不需要知道细节,拿到半成品包就能出菜。Headless 逻辑内核也一样,把“状态怎么流转、行为怎么触发”固化成流程卡,就构成了它的三个核心原理。

第一是状态上提。所有交互状态(对话框是否展开、菜单高亮到哪一项)都集中在逻辑层统一管理,而不是散落在消费方的各个组件里。逻辑层内部用“旧状态 + 动作 → 新状态”的方式流转,像中央厨房的流程卡一样每一步都有据可查。

第二是行为封装。键盘事件、点击外部关闭、Escape 退出这类常用行为被预先编排好,作为“行为包”随半成品包交付。第三是渲染代理——逻辑层不亲自下厨,它把“最终长什么样”的裁决权完全代理给调用方:调用方要按钮就渲染按钮,要链接就渲染链接。三个原理合起来,就是“状态归我管、行为我打包、长相你决定”,门店因此获得了最大的创作自由。

newState=reducer(prevState, action)\displaystyle \text{newState}=\text{reducer}(\text{prevState},\ \text{action})
这是 React 中 useReducer 的底层模型:把复杂的交互动作收敛为纯函数计算。
  1. 旧状态(prevState)逻辑层当前持有的数据快照,比如“面板已关闭、高亮第 0 项”。
    prevState={open=false, highlight=0}\displaystyle \text{prevState}=\{\text{open}=\text{false},\ \text{highlight}=0\}
  2. 动作(action)用户或代码发出的意图描述,如打开、关闭、下移高亮,它是一个“指令”而非结果。
    action∈{toggle, next, close}\displaystyle \text{action}\in\{\text{toggle},\ \text{next},\ \text{close}\}
  3. 纯函数计算reducer 根据旧状态与动作算出唯一的新状态,且绝不修改旧对象,保证流程可回放、可追踪。
    newState={prevStateaction=closemoveDown(prevState)action=next\displaystyle \text{newState}=\begin{cases}\text{prevState} & \text{action}=\text{close}\\\\ \text{moveDown}(\text{prevState}) & \text{action}=\text{next}\end{cases}

实现模式

中央厨房向门店开放三种合作方式:素料包、委托代工、组合套餐。对应到 Headless 组件,就是三种主流实现模式,设计组件库时按场景选用。

第一种是自定义 Hook(素料包)。逻辑层写一个 Hook,如 useDialog(),返回状态、行为函数和一组“接线属性”。消费方像门店拆开素料包一样,自行决定把属性绑到哪些元素上。这种模式自由度最高,适合团队已有成熟设计系统、只需要补行为的情况。

第二种是 Render Props(委托代工):组件接收一个 render 函数,把状态和行为作为参数交回去,由调用方决定这段区间渲染成什么。第三种是组合组件(组合套餐):Dialog.Root 负责状态,Dialog.Trigger 负责触发,Dialog.Content 负责面板,通过 Context 共享同一份逻辑,门店不必手动传参,抄起套餐就能开火。三种模式没有绝对优劣,实际组件库常常混用:复杂控件用组合组件,简单行为用自定义 Hook。

useDialog ⇒ { state, actions, getTriggerProps, getPanelProps }\displaystyle \text{useDialog}\ \Rightarrow\ \{\ \text{state},\ \text{actions},\ \text{getTriggerProps},\ \text{getPanelProps}\ \}
Hook 返回的不是一份固定模板,而是四样“半成品零件”:当前状态、操作函数、触发器接线说明、面板接线说明。
  1. 状态与动作open 等状态给消费方渲染依据,openDialog 等函数给消费方调用入口。
    state 与 actions: {open, openDialog, closeDialog}\displaystyle \text{state}\ \text{与}\ \text{actions}:\ \{\text{open},\ \text{openDialog},\ \text{closeDialog}\}
  2. 接线属性函数getTriggerProps 返回按钮需要的 onClick、aria-expanded 等属性,消费者直接展开绑定。
    getTriggerProps()→{onClick, aria-expanded}\displaystyle \text{getTriggerProps}()\to\{\text{onClick},\ \text{aria}\text{-}\text{expanded}\}
  3. 组装消费方把返回的属性与自己的 JSX 结构组装起来,逻辑与长相在调用处完成合体。
    View=bind⁡(props, JSX)\displaystyle \text{View}=\operatorname{bind}(\text{props},\ \text{JSX})

无障碍交互

门店可以随意摆盘,但有一条红线谁都不能碰:食品安全标准。云膳的中央厨房要求所有半成品包必须通过统一质检,对应到 Headless 组件,就是无障碍(Accessibility)绝不允许被视觉层“改良”掉。逻辑层必须内置对屏幕阅读器、键盘用户的完整支持,这是设计底线。

首先是 ARIA 标准。逻辑层自动给关键元素注入语义标注,告诉读屏软件“这是个对话框”、“现在处于展开状态”。视力正常的用户看到的是漂亮面板,盲人用户“听到”的则是清晰的结构。

其次是键盘导航:方向键移动高亮、Tab 进入/跳出、Escape 关闭、Home/End 跳转首尾,全部由逻辑层接管;最后是焦点管理:打开对话框时焦点移入面板并锁死(焦点陷阱),关闭时焦点归还给触发按钮(焦点恢复)。这些行为表面看不见,却是“食品安全”级别的硬指标——任何门店的摆盘都不能破坏它。

Semantics=role+aria-* ⇒ 辅助技术可理解\displaystyle \text{Semantics}=\text{role}+\text{aria}\text{-}\text{*}\ \Rightarrow\ \text{辅助技术可理解}
无障碍语义 = 角色声明 + 状态属性,二者齐备,屏幕阅读器才能正确“朗读”组件。
  1. role 角色声明告诉辅助技术这个元素的本质是什么,是语义的起点。
    role∈{dialog, menu, button, listbox}\displaystyle \text{role}\in\{\text{dialog},\ \text{menu},\ \text{button},\ \text{listbox}\}
  2. aria 状态属性补充动态信息,如是否展开、是否模态、当前选中项。
    aria-expanded, aria-modal, aria-selected\displaystyle \text{aria}\text{-}\text{expanded},\ \text{aria}\text{-}\text{modal},\ \text{aria}\text{-}\text{selected}
  3. 语义合成role 与 aria 属性组合,辅助技术才能还原出完整的交互意图。
    Semantics=role⁡+∑iaria⁡i\displaystyle \text{Semantics}=\operatorname{role}+\sum_{i}\operatorname{aria}_{i}

生态对比

市面上有几家成熟的“中央厨房”供货商,选谁相当于决定半成品包的标准化程度。云膳选供应商时会看三件事:配不配合自有门店体系、食品安全认证全不全、供货稳不稳。前端选 headless 库同理。

Radix UI:面向 React 生态的原语组件库,切到最细粒度,几乎不预设任何样式,适合用它搭建自有设计系统,如同给高端私厨定制的顶级食材包。Headless UI(Tailwind 出品):同时支持 React 与 Vue,与 Tailwind CSS 天然默契,主打“开箱即上菜”,适合快速交付的团队。React Aria:Adobe 开源,无障碍覆盖面最广,交互行为最严谨,适合对合规性要求极高的企业级产品。

选型没有标准答案,只有“匹配度”。就像云顶选顶级食材包、一碗香选快速半成品、云夜选全合规供应——把自家门店的定位想清楚,供货商的答案自然浮现。

Score=w1⋅Compat+w2⋅A11y+w3⋅Eco\displaystyle \text{Score}=w_{1}\cdot\text{Compat}+w_{2}\cdot\text{A11y}+w_{3}\cdot\text{Eco}
选型本质上是个加权打分题:框架兼容、无障碍完整度、生态热度按权重求和,分数高者胜出。
  1. 框架兼容(Compat)库是否覆盖团队正在用的 React/Vue/Svelte,权重通常最高,决定能否落地。
    Compat∈[0,1], w1=0.4\displaystyle \text{Compat}\in[0,1],\ w_{1}=0.4
  2. 无障碍完整度(A11y)ARIA 覆盖面、焦点策略、键盘导航是否开箱即用,决定改造成本。
    A11y∈[0,1], w2=0.35\displaystyle \text{A11y}\in[0,1],\ w_{2}=0.35
  3. 生态热度(Eco)社区活跃度、文档质量、周边工具链,影响长期维护的可持续性。
    Eco∈[0,1], w3=0.25\displaystyle \text{Eco}\in[0,1],\ w_{3}=0.25

工程化实践

中央厨房要按年度接受品控审查,靠的是一本品控手册:原料台账、出餐抽检、操作记录。Headless 组件库的工程化落地,同样需要三份“手册”:类型设计、测试策略、文档建设。

第一份手册是类型设计。用 TypeScript 泛型把组件 API 提炼成可推导的类型契约:用户传入一个选项数组,onSelect 的回调参数就能自动推导出对应类型。类型即文档,编译期就能拦截大部分误用,相当于品控手册在原料入库时就完成初检。

第二份手册是测试策略:逻辑层跑行为单测(打开、关闭、高亮移动),渲染层跑快照,再用 Playwright 模拟真实键盘路径做端到端测试,确保任何门店摆盘都不破坏键盘流程。第三份手册是文档建设:每个组件配备可交互范例(Storybook)、行为说明和键盘操作表。三份手册齐了,这座中央厨房才敢对外大规模供货。

ComponentProps[T]=BaseProps∪CustomProps[T]\displaystyle \text{ComponentProps}[T]=\text{BaseProps}\cup\text{CustomProps}[T]
泛型让组件的属性类型跟随业务数据自动变化,使用端获得完整智能提示。
  1. 基础属性(BaseProps)所有场景通用的公共 props,如 id、disabled、className。
    BaseProps={id, disabled, className}\displaystyle \text{BaseProps}=\{\text{id},\ \text{disabled},\ \text{className}\}
  2. 泛型定制(CustomProps[T])用泛型参数把选项类型与回调签名注入组件,如 Listbox<string>。
    CustomProps[T]={options:T[], onChange:(v:T)→void}\displaystyle \text{CustomProps}[T]=\{\text{options}:T[],\ \text{onChange}:(v:T)\to\text{void}\}
  3. 并集推导类型检查器自动合并两组属性,使用端无需手动标注即可获得精确提示。
    ComponentProps[T]=BaseProps⁡∪CustomProps⁡[T]\displaystyle \text{ComponentProps}[T]=\operatorname{BaseProps}\cup\operatorname{CustomProps}[T]

封装策略

中央厨房把半成品包交给门店后,门店要写一本“摆盘手册”:怎么装饰、怎么配色、怎么贴品牌标识。在 Headless 组件库的消费端,这份手册就是封装策略——把无头逻辑包装成自家有头组件。

最直接的封装是样式注入:无头组件提供 className 入口,消费方注入自己的样式类;多个来源的类名要按规则合并,不能互相覆盖。这就像半成品包上的“建议摆盘”标签:允许门店替换、增删、组合。

第二招是语义化插槽(如 Radix 的 asChild 模式):允许“把行为穿到任意标签上”,让 div、a、button 都能继承触发逻辑,彻底打破标签限制。第三招是主题定制:用 CSS 变量(设计令牌)定义颜色、间距、圆角,门店只需改一套变量值就能完成全店换肤。三招配合,门店既能保住自家风格,又不破坏中央厨房的标准化内核。

Classfinal=merge⁡(base, user, theme)\displaystyle \text{Class}_{\text{final}}=\operatorname{merge}(\text{base},\ \text{user},\ \text{theme})
最终类名是合并函数对基础类、用户类、主题类三路输入去重和解冲突后的结果。
  1. 基础类(base)headless 库保留的结构性 class,确保布局锚点和行为钩子存在。
    base={h-dialog h-is-open}\displaystyle \text{base}=\{\text{h-dialog}\ \text{h-is-open}\}
  2. 用户类(user)调用方通过 className 传入的自定义样式类,优先级最高。
    user={my-panel dark rounded-xl}\displaystyle \text{user}=\{\text{my-panel}\ \text{dark}\ \text{rounded-xl}\}
  3. 主题类(theme)来自主题系统的设计令牌类,如 bg-brand-500,与用户类按规则共存。
    Classfinal=uniq⁡(base∪user∪theme)\displaystyle \text{Class}_{\text{final}}=\operatorname{uniq}(\text{base}\cup\text{user}\cup\text{theme})

AI 应用场景

AI 生成 UI 代码时的“行为兜底”

阶段:代码生成与渲染层

解决的问题:传统方式下,AI 编码助手生成的弹窗、下拉框常常“有长相没灵魂”——视觉漂亮但键盘用不了、读屏语义残缺。接入 Headless 组件库后,AI 只需专注摆盘(写样式与结构),状态、键盘、无障碍这套最复杂的行为逻辑由“半成品包”兜底,从源头杜绝“好看但难用”的代码。

具体案例:某前端团队把 Radix UI 原语封装成内部组件,并在 AI 编码助手的知识库中登记用法。开发者让 Copilot“生成一个带确认删除的弹窗”,AI 直接输出 <Dialog.Root> 系列调用,自动获得焦点陷阱与 Escape 关闭,不再从零堆 div 和事件。

多品牌出海设计系统

阶段:跨端渲染与主题适配

解决的问题:一个产品面向多个海外品牌时,传统组件库每换一个品牌就要复制一套组件;Headless 模式用一套逻辑内核 + 多套主题令牌(门店摆盘),换肤成本从“重写组件”降为“换 CSS 变量”。

具体案例:某 SaaS 公司用 Headless UI 封装核心控件,针对日韩、欧美市场分别发布两套主题包,同一套 Listbox 逻辑在两地呈现完全不同的视觉语言,营销页迭代效率提升 60%。

组件行为回归测试

阶段:质量保障与自动化测试

解决的问题:视觉回归测试只能发现“看起来不对”,无法发现“键盘路径断了”。Headless 组件把行为集中在逻辑层,天然适合用 AI 驱动的测试代理模拟真实用户键盘操作,做行为回归。

具体案例:某团队用 Playwright + AI 断言框架,对封装后的 headless 组件跑“Tab 进入 → 方向键选 → Enter 确认 → Escape 退出”全链路测试,每次发布前自动验证无障碍行为没有回退。

Python 演示

python57 行
"""无头列表选择器:逻辑内核与视觉皮肤彻底分离的演示"""class HeadlessListbox:    """逻辑内核:只管理状态与行为,不关心任何样式"""     def __init__(self, options):        self.options = options        self.highlight = 0        self.expanded = False     def dispatch(self, action):        """Reducer:中央厨房的标准操作流程"""        if action == 'toggle':            self.expanded = not self.expanded        elif action == 'next' and self.expanded:            self.highlight = (self.highlight + 1) % len(self.options)        elif action == 'prev' and self.expanded:            self.highlight = (self.highlight - 1) % len(self.options)        elif action == 'close':            self.expanded = False        return self.get_state()     def get_state(self):        """半成品包:只暴露消费者需要的状态"""        return {            'expanded': self.expanded,            'active': self.options[self.highlight],            'total': len(self.options),        }  def render_plain(state):    """门店A:极简纯文字摆盘"""    mark = 'v' if state['expanded'] else '>'    return f" {state['active']} {mark} (共{state['total']}项)"  def render_fancy(state):    """门店B:带装饰符号的活泼摆盘"""    head = f"* {state['active']} *" if state['expanded'] else f"  {state['active']}  "    return head + (' [展开中]' if state['expanded'] else ' [已收起]')  def main():    box = HeadlessListbox(['红烧牛肉', '卤蛋', '时蔬', '例汤'])    print('=== 同一逻辑内核,两种摆盘 ===')    print('默认  :', render_plain(box.get_state()))    box.dispatch('toggle')    for _ in range(3):        box.dispatch('next')    print('门店A :', render_plain(box.get_state()))    print('门店B :', render_fancy(box.get_state()))    box.dispatch('close')    print('收起  :', render_plain(box.get_state()))  if __name__ == '__main__':    main()
目的:用 Python 模拟 Headless 模式——HeadlessListbox 只负责状态与行为逻辑,render_plain 与 render_fancy 是两家“门店”,同一份半成品包被摆盘成两种风格。
  1. class HeadlessListbox逻辑内核类,对应中央厨房:不包含任何样式信息。
  2. dispatch(self, action)Reducer 入口,状态流转都从这里走,action 只传意图不传结果。
  3. self.expanded = not self.expanded切开关闭状态的纯逻辑操作,不带任何 UI 依赖。
  4. get_state()返回“半成品包”,消费者只拿到这一个入口,内部细节完全隐藏。
  5. render_plain(state)` / `render_fancy(state)两家门店的摆盘函数,接收同一份 state 却输出完全不同风格。
  6. box.dispatch('toggle')模拟用户点击触发,逻辑层响应后状态更新,门店随之重摆。

常见误区

误区以为 Headless 只是把视觉去掉,剩下一个光秃秃的 div 壳子,所有交互都要从零手写,等于买了一袋空气。

正解恰恰相反,Headless 保留了最复杂的部分:状态管理、键盘导航、焦点陷阱、ARIA 属性全部内置。去掉的只是“长相”。就像中央厨房把最难做的红烧肉做成半成品包,门店只需加热摆盘,而不是从养牛开始。

为何常见:“无头”字面给人“什么都没有”的错觉,其实“无头”只针对视觉呈现,逻辑层恰恰是全套的。

误区看到 Radix、React Aria 都是 React 生态,就认定这套模式是 React 专属,用 Vue 或 Svelte 就与它无缘。

正解模式与框架无关。React 生态最为繁荣,但 Vue 有对应的 headless 组件思路,Svelte 社区也有无头实践,Headless UI 库本身也同时支持 React 与 Vue。无头是设计范式,不是框架专利。

为何常见:主流开源项目和文章多来自 React 社区,造成“只见 React”的幸存者偏差。

误区把 headless(无头)和 stateless(无状态)混为一谈,以为去掉视觉后组件也就不管状态了,性能必然更好。

正解完全相反。Headless 组件是“强状态”组件——只是把状态从视觉层上提到逻辑层集中管理。“无头”去掉的是视觉 DOM 与样式,状态一个不少,甚至管理得更精细(reducer、状态机)。

为何常见:两个英文词发音与拼写接近,初学者极易混淆概念边界。

误区既然最终都要自己写样式,多引入一个库反而增加依赖,干脆所有组件都从零手写最干净。

正解自己写很容易漏掉隐藏工程量:焦点陷阱、Escape 关闭、滚动锁、读屏语义、边界竞态。这些才是“半成品包”的核心价值,复刻成本极高。样式只是冰山一角,行为与无障碍才是水面下的主体。

为何常见:新手只看到样式工作量,看不到行为与无障碍的复杂度,低估了无头库的真实价值。

面试题

Q1. Headless UI 组件的核心设计思想是什么?

单选题 · easy

  • A.无头服务器渲染模式
  • B.逻辑与视觉相分离← 正确答案
  • C.事件处理全部去掉
  • D.只支持无状态组件

答案:B。Headless 的核心是“中央厨房”式分离——状态、交互、无障碍等逻辑集中管理,视觉呈现交给消费方。A 是服务端渲染概念,C、D 是对“无头”的误解:行为事件恰恰被完整封装,状态也更精细。

Q2. “半成品包”在 Headless 模式中最贴近下面哪个概念?

单选题 · easy

  • A.逻辑层输出的状态行为← 正确答案
  • B.打包后的静态资源包
  • C.可视化编辑器配置项
  • D.页面模板的静态存档

答案:A。半成品包指 Hook 或组件对外暴露的状态、行为函数与属性接线说明,如 useDialog() 的返回值。静态资源、可视化配置、静态模板都不含可交互的行为逻辑。

Q3. useDialog 这类自定义 Hook 一般不负责以下哪项?

单选题 · mid

  • A.管理开合状态机
  • B.绑定 Escape 关窗
  • C.计算入场过渡动画← 正确答案
  • D.返回触发器属性

答案:C。入场动画属于视觉层(Styled)职责,是门店摆盘的事。状态机、Escape 行为、触发器属性都是逻辑层(Headless)的活,对应中央厨房的食材处理与安全标准。

Q4. Render Props 模式的核心特征是什么?

单选题 · mid

  • A.渲染交给调用方← 正确答案
  • B.在内部写死模板
  • C.依赖 CSS 框架换肤
  • D.用服务端组件渲染

答案:A。Render Props 把 state 与 actions 作为参数传给调用方提供的渲染函数,由调用方决定画成什么,即“委托代工”。B 是传统组件做法,C、D 与渲染代理理念无关。

Q5. 逻辑层内置的无障碍能力通常包括哪些?

多选题 · mid

  • A.焦点陷阱与恢复← 正确答案
  • B.ARIA 属性自动注入← 正确答案
  • C.自动生成设计令牌
  • D.键盘方向键导航← 正确答案

答案:ABD。焦点管理、ARIA 语义、键盘导航是 Headless 的“食品安全标准”,全部内置。设计令牌属于主题定制,是封装策略而非无障碍能力,归属门店摆盘环节。

Q6. 关于 Radix UI 与 Headless UI 的区别,正确的是?

单选题 · mid

  • A.前者仅适配 React← 正确答案
  • B.后者必须是 Vue 组件
  • C.前者强制使用样式表
  • D.后者放弃无障碍支持

答案:A。Radix 是 React 专属原语库;Headless UI(Tailwind 出品)同时支持 React 与 Vue。两者都不强制样式表,也都内置无障碍,B、C、D 均不符合事实。

Q7. “状态上提”在 Headless 模式中的正确含义是?

单选题 · mid

  • A.状态放于每个子组件内
  • B.状态写入全局 DOM 节点
  • C.状态由逻辑层集中持有← 正确答案
  • D.状态由主题文件生成

答案:C。状态上提指把所有交互状态集中到逻辑层的 Hook 或 Root 组件中,通过 Context 或返回值分发。A 是状态分散,B 污染 DOM,D 混淆了样式与状态。

Q8. 自建 Headless 组件库时,工程化“品控三件套”包括?

多选题 · hard

  • A.类型导出完整可推导← 正确答案
  • B.键盘模拟自动化测试← 正确答案
  • C.强制依赖具体 CSS 库
  • D.行为文档与范例演示← 正确答案

答案:ABD。泛型类型契约、键盘路径端到端测试、交互文档构成品控三件套。C 恰恰相反——Headless 组件不应强制绑定任何 CSS 库,样式应由消费方自由选择。

Q9. useReducer 相比 useState 在无头组件中的主要优势是?

单选题 · hard

  • A.复杂动作流程集中处理← 正确答案
  • B.自动生成主题样式变量
  • C.直接提升更新性能上限
  • D.免去状态结构设计环节

答案:A。reducer 把 toggle、next、close 等动作收敛为纯函数流转,行为可追踪、可回放,正是“流程卡”思想。它不负责主题、不必然提升性能,也仍需要精心设计状态结构。

Q10. 以下哪些场景适合采用 Headless 模式?

多选题 · hard

  • A.大型设计系统多主题← 正确答案
  • B.极简静态营销落地页
  • C.多框架复用的组件层← 正确答案
  • D.强无障碍的复杂控件← 正确答案

答案:ACD。多主题换肤、跨框架复用、强无障碍复杂控件都是无头模式的优势场景。静态营销页用无头组件如“为炒一盘蛋炒饭建中央厨房”,属于过度设计,B 不适用。