可复用组件 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,方向键在两个页面都能用;
- 验证:两处分别切换选中项,确认受控/非受控两条路径行为一致,键盘操作在两处都正常。
动手任务
打开本课实验区:
- 给 FilterList 增加「多选」模式 multiple,思考 value 的类型契约如何变化(value 变数组)并实现。
- 不改 FilterList 内部,用组合的方式在课程页加一个「清空筛选」的自定义头部。
- 把日志页也改成受控模式(状态提升到页面),验证两种模式互不干扰。
常见错误
- 回调 props 泛滥:每加一个需求加一个回调;回到组合与默认值,砍掉可推导的 props。
- 受控/非受控只写在文档里:代码里没有
value === undefined的分支判断,契约就没有执行者。 - 扩展点全靠 props 枚举:renderA/renderB/renderC 收敛成 children 组合或单一 render prop。
- 无障碍属性甩给使用方:角色与键盘行为是组件的责任,不该每次复用重新实现。
检查与复盘
自查清单:
- 能写出该组件一页纸 API 文档:props 表、默认值、受控约定、无障碍分工
- 同一组件在两个场景复用成功,没有为第二场景加「特例 props」
- 受控与非受控两条路径行为一致,键盘操作验证过
参考资料
- React 官方文档:Thinking in React —— 组件边界与数据流的总体思路
- React 官方文档:向组件传递 props —— children 与组合
- React 官方文档:组件间共享状态 —— 受控模式的本质
读完这一课了?
标记完成后,工作台会安排接下来的学习。