不要把 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
相关推荐
雪芽蓝域zzs8 小时前
(六)打包优化 + Nginx 部署完整配置 + 项目收尾
前端·vue.js
研☆香8 小时前
聊一聊前端的常见字 关键字
开发语言·前端·javascript
东风破_8 小时前
JWT 1:从一个登录请求开始,理解 React 项目里的 API 层与 Mock
前端·后端
东风破_9 小时前
JWT 3:为什么 Token 要放进 Authorization?Axios 拦截器到底解决了什么?
前端·后端
东风破_9 小时前
JWT 5:路由守卫是什么?把整个 JWT 登录鉴权流程串起来
前端·后端
东风破_9 小时前
JWT 2:HTTP 是无状态的,为什么登录成功后还要给 Token?
前端·后端
东风破_9 小时前
JWT 4:Zustand 到底解决了什么?为什么登录状态要放进 Store?
前端·后端
IT_陈寒9 小时前
Vite动态导入差点让我秃头,原来问题出在这
前端·人工智能·后端
白泽_hunter9 小时前
项目里最常见的 5 个 this 指向坑:从规则到实战彻底讲透
前端
宿67410 小时前
vue3-pinia
前端·vue.js