不要把 JSBridge 写成 if else:Dispatcher、Registry 和 Biz/Imp 分层
项目地址:github.com/lichenyang5...
本文是 MiniAppRuntime-Harmony 系列的第四篇,重点讲 action 分发和能力分层。
1. 最容易写坏的地方
当 H5 调用 ArkTS 以后,最容易写出这样的代码:
ts
if (request.action === 'ui.showToast') {
// showToast
} else if (request.action === 'system.clipboard.writeText') {
// clipboard
} else if (request.action === 'network.request') {
// request
}
一开始没问题。
但是 action 一多,这个文件就会变成灾难。
所以从第二个阶段开始,我就没有继续在 BridgeController 里写业务逻辑,而是拆出了:
text
BridgeDispatcher
HandlerRegistry
RuntimeBootstrap
ToastBiz
ToastImp
2. BridgeController 只做通信边界
BridgeController 的职责应该很轻:
text
接收 raw message
→ 解析 BridgeRequest
→ 调用 Dispatcher
→ 调用 CallbackExecutor 回调 H5
它不应该知道 ui.showToast 怎么实现。
也不应该知道 promptAction.showToast 怎么调用。
否则 Controller 会变得越来越重。
3. BridgeDispatcher 负责分发
Dispatcher 做的事情很简单:
ts
const handler = registry.get(request.action)
if (!handler) {
return unknownActionResponse
}
try {
return await handler(request)
} catch (err) {
return internalErrorResponse
}
它解决两个问题:
- 已注册 action,交给对应 handler;
- 未注册 action,统一返回 UNKNOWN_ACTION。
这样所有 action 都走同一个入口。
4. HandlerRegistry 负责注册
Registry 只负责三件事:
text
register(action, handler)
get(action)
has(action)
它不做业务逻辑,不解析 request,不回调 H5。
这点很关键。
一个好的 Registry 应该像电话簿:
只告诉你某个 action 对应哪个 handler,不负责 handler 具体做什么。
5. RuntimeBootstrap 负责启动注册
RuntimeBootstrap 在运行时初始化时注册内置能力。
例如:
ts
registry.register(ACTION_UI_SHOW_TOAST, async (request) => {
return await toastBiz.handle(request)
})
这让能力注册集中在一个地方。
以后新增 API 时,只需要:
- 新增 ActionName;
- 新增 Biz;
- 新增 Imp;
- 在 RuntimeBootstrap 注册 handler。
6. 为什么还要 Biz / Imp
ui.showToast 看起来很简单:
ts
promptAction.showToast({ message })
那为什么还要拆成 ToastBiz 和 ToastImp?
因为它们职责不同。
ToastBiz 负责业务语义:
text
message 是否存在?
message 是否是字符串?
message 是否为空?
错误码怎么返回?
成功响应怎么组装?
ToastImp 负责系统调用:
text
调用 promptAction.showToast
这叫做把"业务判断"和"平台能力"分开。
现在只有 Toast,看不出价值。
等后面有 clipboard、storage、network、device 等 API 时,这种分层就非常有用了。
7. ui.showToast 的完整链路
当前 ui.showToast 的完整链路如下:
text
H5
→ window.myascf.send("ui.showToast", { message })
→ JavaScriptProxy.postMessage
→ BridgeController.handleMessage
→ BridgeDispatcher.dispatch
→ HandlerRegistry.get("ui.showToast")
→ RuntimeBootstrap 注册的 handler
→ ToastBiz.handle
→ ToastImp.showToast
→ promptAction.showToast
→ BridgeResponse
→ BridgeCallbackExecutor.sendResponse
→ runJavaScript
→ H5 Promise resolve
这条链路虽然比直接调用长,但它更像一个框架。
每一层都很薄,每一层都好测试,每一层都可以替换。
8. 错误处理
目前已经有几类错误:
text
UNKNOWN_ACTION action 未注册
PARAM_ERROR 参数错误
INTERNAL_ERROR ArkTS 内部异常
TIMEOUT H5 等待超时
CALLBACK_LOST H5 callback 已不存在
例如参数错误:
js
window.myascf.send("ui.showToast", {})
不会调用 ToastImp,而是直接返回 PARAM_ERROR。
这说明参数校验在 Biz 层生效了。
9. 这一阶段的意义
到这里,项目已经不是一个简单 Demo。
它具备了运行时框架的基本结构:
text
通信边界
能力分发
能力注册
业务校验
平台调用
统一回调
统一错误
后续要新增第二个 API,就不需要改主链路,只需要按模式添加能力即可。
项目地址:
text
https://github.com/lichenyang5/MiniAppRuntime-Harmony