不要把 JSBridge 写成 if else:Dispatcher、Registry 和 Biz/Imp 分层

不要把 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
}

它解决两个问题:

  1. 已注册 action,交给对应 handler;
  2. 未注册 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 时,只需要:

  1. 新增 ActionName;
  2. 新增 Biz;
  3. 新增 Imp;
  4. 在 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
相关推荐
前端甜糖1 小时前
前端如何实现在线预览office常见文件功能?
前端·vue.js
北鸟南游1 小时前
学习-claude code 工程化实战的笔记
前端·ai编程·claude
Kyrie6781 小时前
TypeScript 7.0 正式发布
前端
lichenyang4531 小时前
JSBridge 不是发消息就完了:runJavaScript、Timeout 和 Callback Lost
前端
xiaofeichaichai1 小时前
Vite配置实战
前端
zhanghaofaowhrql1 小时前
为CSDN博客编辑器安装自定义CSS主题,打造个性化写作界面
前端·css·编辑器
懒得不想起名字2 小时前
flutter 使用go_router 和 page_transition定义页面动画
前端
多加点辣也没关系2 小时前
JavaScript|第6章:流程控制语句
开发语言·前端·javascript
csj502 小时前
前端基础之《React(11)—state声明式》
前端·react.js