羽球联盟 HarmonyOS NEXT 实战系列 (07/20):NavBar、SearchBar、SegmentTab与卡片组件复用
文章导读常见页面骨架被抽成 NavBar、SearchBar、SegmentTab、Card 等组件。组件传入数据和回调不直接耦合业务数据仓库。卡片组件负责展示一致性页面负责列表、筛选和跳转。页面效果首页、搜索页、赛事页和个人中心会反复出现导航、搜索、分段筛选和卡片。如果每个页面都各写一套顶部结构、筛选条和列表卡片视觉会逐渐跑偏交互细节也会开始不一致。羽球联盟把这些高频界面骨架沉到common层后页面本身只需要关心“当前展示什么数据、点击后去哪里、空态文案怎么写”。从首页进入搜索、赛事和收藏时可以明显感觉到这些页面属于同一个产品标题栏高度一致搜索输入框的留白和圆角一致分段筛选的选中反馈一致卡片的圆角、阴影和底色也一致。组件复用在这里解决的不是“少写几行代码”而是把跨页面的一致性变成一条可以持续维护的工程约束。实战拆解组件复用真正难的地方不是把样式抄出来而是把边界切对。这个项目里NavBar、SearchBar、SegmentTab负责稳定的页面骨架ArticleCard、EquipmentCard、PlayerCard、EventCard负责不同内容实体的统一展示。它们都遵守同一条规则组件接收已经整理好的数据和少量回调不直接知道数据仓库、接口抓取和路由之外的业务细节。这一点在搜索页最明显。搜索页一边要把本地资讯、联网资讯、本地装备、联网装备合并起来做筛选一边又要保持上方搜索框和中间分段 Tab 的交互一致。如果把搜索、筛选和卡片渲染全部写死在一个页面里短期内能跑通后面要复用就会很痛苦。现在的做法是页面负责组装结果集再把结果交给通用组件展示所以搜索页、收藏页、历史页虽然数据来源不同界面结构却可以保持稳定。另一个值得注意的点是“通用组件不越层”。例如ArticleCard不去主动读取PrefStore、不自己发起跳转、也不决定收藏状态如何持久化它只暴露onTap。页面层收到点击后再决定是记录历史、进入详情还是加上一层埋点。这样组件的生命周期和职责都更干净后续迁移到别的列表场景时也不会把旧业务逻辑一起拖过去。关键代码build() { Column() { NavBar({ title: 搜索, showBack: true }) Row() { SearchBar({ value: $keyword, placeholder: 搜索资讯、赛事、装备, onSubmit: (t: string): void { this.onSubmitSearch(t); } }) } .padding({ left: AppSizes.s16, right: AppSizes.s16, top: AppSizes.s8, bottom: AppSizes.s8 }) SegmentTab({ tabs: this.tabs, selectedIndex: $selected }) List({ space: AppSizes.s12 }) { ForEach(this.matchedArticles(), (a: Article) { ListItem() { ArticleCard({ article: a, onTap: (article: Article): void { PrefStore.getInstance().addHistory(article.id); Nav.push(Routes.ARTICLE_DETAIL, new RouteParams(article.id)); } }) } }, (a: Article) s_a_ a.id) } } }这段代码把复用链路展现得很完整。NavBar负责顶栏SearchBar只维护输入与提交SegmentTab只同步当前选中的索引ArticleCard只消费文章模型并把点击回调还给页面。真正和业务强绑定的动作比如写入浏览历史、进入详情页、选择当前展示哪一种列表全部留在页面层处理。对应的卡片组件本身也保持了这个边界。例如ArticleCard会根据coverUrl是否存在决定优先加载远程图还是本地资源兜底会自己处理标题、标签、阅读数这些视觉细节但不会擅自去决定点击后发生什么。这种拆法让“同一张卡片出现在首页推荐、搜索结果、收藏页、历史页”变得可行而且每个页面都还能保留自己的行为差异。取舍分析这类复用最容易走向两个极端。一个极端是页面里什么都自己写短期看最直观但每多一个页面就要多维护一份顶栏、卡片和筛选条另一个极端是把组件做得过度聪明内部直接读取仓库、自己决定路由最后通用组件反而绑死在某个业务上。羽球联盟选择的是中间路线把视觉结构和基础交互抽出去把数据准备和业务决策留在页面里。这样的取舍会让common层多出一些显式参数比如SegmentTab需要tabs和selectedIndexSearchBar需要Link value和onSubmit卡片需要模型对象和点击回调。参数确实多了一点但好处是组件行为可预测不会出现“改一个页面另一个页面跟着被影响”的连锁反应。另一个收益来自样式统一。AppColors、AppSizes和卡片/顶栏组件一起使用后圆角、字号、边距、背景色都不需要页面重新发明。后续新增页面时开发重点就会从“把样式抄得像不像”转移到“这页的数据筛选规则和交互反馈是否正确”维护成本明显更低。设计落点组件只负责展示和回调不直接读取业务仓库。页面负责合并本地/远程数据、过滤结果和处理跳转组件只承接最终状态。NavBar、SearchBar、SegmentTab先统一页面骨架再让不同业务页挂接自己的数据流。卡片组件统一封面、标题、标签、时间和底色等视觉规则避免列表体验碎片化。分段筛选、空态和卡片样式统一后页面新增成本会明显下降。易踩坑不要让通用组件直接 import features 数据否则 common 会变成业务层。不要把页面私有状态塞进组件内部选中项、搜索关键字这类状态通常由页面持有更清晰。不要在卡片组件里顺手写持久化或路由逻辑否则复用范围一扩大就会反噬。只抽样式、不抽边界也会出问题。视觉一样但数据模型和回调契约不稳定组件最后还是难以长期复用。验证方式在多个页面观察搜索框、卡片和分段 Tab 是否一致。空列表时确认 EmptyState 能覆盖不同业务场景。点击卡片后确认回调交给页面处理历史记录与页面跳转都正常生效。在搜索页切换“资讯 / 装备”后确认SegmentTab只切换视图不污染搜索关键字本身。参考资料[ArkUI 自定义组件官方文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-create-custom-components-V5)[ArkUI 状态管理 State / Link官方文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-state-management-overview-V5)小结第 07 篇最核心的结论是组件复用不是把页面切成更小的块而是先确定哪些东西应该长期稳定。顶栏、搜索、筛选和卡片属于稳定骨架就应该沉到common数据合并、状态切换、点击后的业务动作属于场景差异就应该留在页面层。这样做以后新增页面既能保持同一套产品观感也不会把业务逻辑意外耦合进通用组件。

相关新闻