组件通用约定

所有组件继承 BaseComponent 的统一属性、Value / ValueChanged 双向绑定,以及 UIService + Container 的全局消息机制。

最后更新

读任意一个组件文档前,先掌握这几条通用约定,后面就能举一反三。Known 的可视化组件(Button、Table、Input、Modal……)并非各写各的,而是共享同一套"骨架":统一继承 BaseComponent、统一的绑定写法、统一由全局服务推送浮层。本页把这套约定讲透,之后翻组件库时你只需要看它"多了哪些专有参数",而不用重学通用部分。

一切从 BaseComponent 开始

框架里几乎所有交互组件都继承 Known.Components.BaseComponent。这个基类做两件事:塞进两个随取随用的服务,和声明一组"每个组件都有"的公共参数。

两个服务通过 [Inject] 自动注入,组件内部直接用,页面里无需手动传:

  • UI(UIService)——全局浮层入口,@UI.Success("已保存") 一句话就能弹提示,详见下文。
  • Local(LocalService)——本地化与偏好入口,@Local["查询"] 取当前语言译文、@Local.Info.Theme 读当前主题。

公共参数则对应每个组件都认的这几个 attribute:

参数 类型 默认 作用
Class string 无 追加到根元素 CSS 类,用来叠你自己的样式
Style string 无 根元素内联样式
Disabled bool false 禁用态,组件自动置灰并拦截交互
Visible bool true 控制是否渲染;false 时组件整体不出现
VisibleChanged EventCallback<bool> — 与 Visible 配对,支持组件内部反向改可见性
UserAttributes Dictionary<string,object> 无 CaptureUnmatchedValues,把你写的任意未知 attribute 原样透传到根元素

这意味着 <Button Class="danger" Style="width:120px" Disabled="true" data-id="@row.Id" /> 里,前三个是基类参数,data-id 会被 UserAttributes 接住渲染到 DOM 上——你不需要每个组件都额外声明 data-*。

一个小坑:源码里 Visible 的 XML 注释写着"默认为 false",但实际字段初始化是 = true。以代码为准——组件默认可见,你想隐藏它才显式写 Visible="false"。

BaseComponent 还在 OnInitializedAsync 里自动订阅了 Local.Changed,并在 Dispose 里退订。所以当你切换语言 / 主题 / 密度时,页面上所有内置组件会自动重渲染、刷新各自取到的译文——你写自定义组件若也继承 BaseComponent,这份自动刷新是白送的;但你要是自己又写了 Dispose(),务必记得调用 base.Dispose() 把这份退订保留下来,否则会泄漏回调。

Value / ValueChanged:统一的双向绑定写法

可输入类组件(Input、Select、Switch、DatePicker、分页器等)都遵循 Blazor 的标准双向绑定约定:一个 Value 参数负责"当前值",一个 ValueChanged 回调负责"值变了告诉外面"。两者配对后,你就可以用 @bind 一行搞定:

<Input @bind-Value="keyword" Placeholder="搜索名称" />

<Switch @bind-Value="enabled" />

下面这两行完全等价,理解了这个就理解了 @bind 的糖衣:

<Value="keyword" ValueChanged="v => keyword = v" />
@bind-Value="keyword"

需要"值一变就跑异步逻辑"(比如即时搜索)时,用带 Async 的写法,框架还会顺带把顶部加载条亮起来:

<Input Value="keyword" ValueChanged:async="OnKeywordChanged" />

约定统一的好处是:你只要会一个可输入组件的绑定,就会了全部——不用记"这个控件是 Text 那个是 Model"之类的方言。具体某个组件的 Value 是什么类型(字符串、布尔、日期、还是对象),看它自己的文档页顶部参数表。

注意:Select 的候选项 Value 目前一律按字符串处理,绑定属性是数值类型时留意做一次转换,这是组件库层面的已知约定,不属于通用绑定机制的问题。

全局浮层:UIService 推送,Container 渲染

Toast 轻提示、Notification 卡片通知、顶部 LoadingBar 进度条、以及 Modal / Drawer——这类"盖在页面之上、通常全局只出现一处"的浮层,Known 不要求你在每个页面里摆一组组件。做法是:由布局里的 Container 组件在整个应用生命周期里放置一次这些浮层的宿主,业务代码只管调 UIService 的方法把内容推进去。

所以你在任意继承 BaseComponent 的组件或注入了 UIService 的地方,都能直接:

UI.Success("保存成功");
UI.Error("网络异常,请重试");
UI.Exception(ex);              // 开发模式弹完整堆栈,发布模式只弹消息

UI.Notify("构建完成", "耗时 32 秒,产物已就绪");   // 右上角卡片通知

UI.ShowLoading();              // 顶部不确定进度条
UI.SetProgress(60);            // 切到确定进度
await UI.HideLoadingAsync();   // 补满到 100 再淡出

UI.ShowModal(model);           // 打开模态框,由全局 ModalHost 渲染
UI.ShowDrawer(model);          // 打开抽屉,由全局 DrawerHost 渲染

UIService 上这些可调参数(都能在建应用时改一次,全站生效):

分组 属性 默认 含义
Toast Duration 2500 自动关闭毫秒数,0 不自动关;Error 类型会强制至少 5000
Toast ToastPlacement "top" 停靠位置,水平居中,可选 top / bottom
Toast ToastClosable true 是否带手动关闭按钮
Toast ToastMax 5 最多同屏条数,超出挤掉最早的
Notification NotificationDuration 4500 卡片通知自动关闭毫秒数
Notification NotificationPlacement "topRight" 出场的角落
Notification NotificationMax 5 同屏最多条数
LoadingBar LoadingColor "primary" 进度条主题色:primary/success/warning/danger/info

任何状态变化都会触发 UIService.Changed 事件,Container 里的宿主订阅它重渲染——这就是"你只调方法、不碰组件"能成立的底层连线。要主动收尾也现成:UI.Close(msg) / UI.Clear() 关轻提示,UI.CloseAllModals() 一把关掉所有模态框。

与本地化、主题的关系

BaseComponent 已经注入了 Local,所以你在组件里直接写 @Local["删除"] 就能拿到当前语言译文;缺译时它安全回退成中文原文本身,不会渲染成空白。语言包怎么按程序集分散维护、en.json 怎么写、和数据库 SysLanguage 怎么合并覆盖,是国际化那一篇的内容,这里只需记住:用户可见文本一律走 Local,别硬编码。

小结与下一步

把这三条内化,组件文档就从"上百页说明书"塌缩成"每页只需看你没见过的专有参数":见到 Class/Style/Disabled/Visible 知道是基类白送的;见到可输入组件知道 @bind-Value 通用;见到"弹个提示 / 开个框"知道是 UI.Xxx() 而非在页面里摆组件。

微信公众号二维码 扫码关注公众号
QQ 交流群二维码 扫码加入 QQ 群