不要把 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
相关推荐
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·课程设计
Nicander4 小时前
我给 Excalidraw 做了一个本地工作区:DrawSpace
前端·开源
linux_cfan6 小时前
17 · 引擎适配器全景:HLS/DASH/Vimeo/Mux/Cast
前端·javascript·音视频
计算机魔术师6 小时前
烧掉2780亿美元还不够?OpenAI的资本豪赌让人头皮发麻
前端
IT_陈寒7 小时前
Java线程池用错参数,我的服务居然悄悄崩溃了
前端·人工智能·后端
卡布鲁7 小时前
React useMemo 与 useCallback 完全指南:原理、场景与避坑
前端·react.js
碳基修炼7 小时前
排查记:本地 devServer 是 http,浏览器却把 302 重定向升级成了 https
前端·http
步行cgn7 小时前
Spring p 命名空间注入详解
java·前端·spring
何何____8 小时前
Vue 生命周期详解
前端·javascript
江米小枣tonylua8 小时前
TypeScript 全面の拥抱 !Prisma 8 + Electron 升级实战
前端