R45第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

可复用组件 API

可复用组件 API · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

读完你能为一个可复用组件写出明确的 API 契约:受控与非受控两种用法、合理的默认行为、用组合留出的扩展点,以及无障碍职责的归属;并用「同一组件在两个不同页面复用」来检验设计。

问题场景

筛选栏组件 FilterBar 第一版:12 个 props,其中 7 个是回调,每个使用方都要传全;第二个页面想复用时,发现列表项渲染写死在组件里,改不了;键盘操作在第一页能用、第二页失灵——因为无障碍属性写死在内部,外层容器结构一变就断了。可复用不是「能 import」,而是契约清晰:谁控制状态、哪里能扩展、职责边界在哪。

核心解释

设计 API 有四个决策点。

**一、受控还是非受控?给两种都留门。**传了 value + onChange 就是受控(状态归使用方);只传 defaultValue 则组件自理。React 原生表单控件的 value/defaultValue 双轨就是标准答案:

function FilterList({ items, value, defaultValue, onChange }) {
  const [inner, setInner] = useState(defaultValue ?? null);
  const controlled = value !== undefined;
  const current = controlled ? value : inner; // 受控优先
  function select(id) {
    if (!controlled) setInner(id);            // 非受控才动内部状态
    if (onChange) onChange(id);
  }
}

value === undefined 的分支判断就是契约的执行者,不是文档里的一句话。两种用法站在使用方的视角:

// 受控:状态归使用方,选择历史可以进 URL、跨组件共享
<FilterList items={cats} value={cat} onChange={setCat} />
// 非受控:组件自理,onChange 只做埋点
<FilterList items={cats} defaultValue="all" onChange={track} />

**二、默认行为合理吗?**不传任何 props 时组件应「开箱可用」:默认全量列表、默认单选、占位文案齐全;特殊需求才通过 props 关闭默认。

**三、扩展点用组合,不用配置枚举。**与其加 renderItem、renderHeader、renderFooter…… 不如让结构用 children / 组合表达:FilterList 不认识「具体有什么筛选项」,只提供布局、选中态与键盘行为,筛选项由使用方组合进来。回调 props 泛滥时,先回到组合与默认值砍一砍。

**四、无障碍职责显式归属。**组件保证:角色(role)、键盘行为、焦点管理;使用方负责:可见文本标签、aria-label 的具体文案、页面级布局。这条分工写进组件文档,复用方才不会破坏它。

示例拆解

实验区用同一个 FilterList 组件渲染两个完全不同的场景:

  • 课程页:筛选分类,竖排布局,选项带计数徽标,受控用法(选择状态提升到页面);
  • 日志页:筛选级别,横排紧凑,非受控用法(defaultValue 起步,onChange 只做埋点);
  • 两处共享同一套 props 契约:items + value/defaultValue + onChange + layout;角色是 radiogroup/radio,方向键在两个页面都能用;
  • 验证:两处分别切换选中项,确认受控/非受控两条路径行为一致,键盘操作在两处都正常。

动手任务

打开本课实验区:

  1. 给 FilterList 增加「多选」模式 multiple,思考 value 的类型契约如何变化(value 变数组)并实现。
  2. 不改 FilterList 内部,用组合的方式在课程页加一个「清空筛选」的自定义头部。
  3. 把日志页也改成受控模式(状态提升到页面),验证两种模式互不干扰。

常见错误

  • 回调 props 泛滥:每加一个需求加一个回调;回到组合与默认值,砍掉可推导的 props。
  • 受控/非受控只写在文档里:代码里没有 value === undefined 的分支判断,契约就没有执行者。
  • 扩展点全靠 props 枚举:renderA/renderB/renderC 收敛成 children 组合或单一 render prop。
  • 无障碍属性甩给使用方:角色与键盘行为是组件的责任,不该每次复用重新实现。

检查与复盘

自查清单:

  • 能写出该组件一页纸 API 文档:props 表、默认值、受控约定、无障碍分工
  • 同一组件在两个场景复用成功,没有为第二场景加「特例 props」
  • 受控与非受控两条路径行为一致,键盘操作验证过

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选中等

设计一个 Badge 徽标组件,共有 5 种语义颜色 × 3 种尺寸的合法组合。下列 props 设计最好的是?

Q2多选中等(多选,全部选对才算正确)

复用 Modal 组件时,下列哪些设计让它更容易被不同页面使用?(多选)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...