[特殊字符] Vue3 + WebView 双端通信桥:用 TypeScript 构建高可维护的 JSBridge 与 JSSDK
在移动端 WebView 场景中(例如 iOS 的 WKWebView、Android 的 WebView),
前端页面(Web) 与 原生容器(Native) 之间需要频繁通信:
-
Web 请求位置信息、蓝牙状态;
-
Native 主动推送系统事件、设备权限变更;
-
双方需要统一协议、可靠回调和强类型约束。
本文带你构建一个完整的 TypeScript + Promise 化 + 双端兼容 的 JSBridge,
让你的 H5 页面拥有原生 App 级的通信能力。
🧩 一、核心设计思路
我们拆分为两层:
| 层级 | 说明 |
|---|---|
| JSBridge | 与原生交互的底层通信封装(注册、触发、回调) |
| JSSDK | 对上层业务提供类型化、Promise 化的接口封装 |
架构图如下:
[Web业务层] ⇄ [JSSDK接口层] ⇄ [JSBridge通信层] ⇄ [Native]
⚙️ 二、JSBridge 优化实现
// bridge.ts
interface BridgeMessage<T = any> {eventName: stringparams?: T
}type BridgeHandler<T = any> = (params: T) => voidinterface BridgeResponse<T = any> {success: booleandata?: Tmessage?: string
}// ===== 定义事件映射 =====
export interface BridgeEventMap {getLocation: { type: 'wgs84' | 'gcj02' }openBluetoothAdapter: voidstartBeaconDiscovery: { uuids: string[] }getBeacons: voidstopBeaconDiscovery: voidcloseBluetoothAdapter: voiddevicePermissionChanged: { location: boolean; bluetooth: boolean }logout: { redirectUrl: string }getMotionDate: { yaw: string }getSafeTop: voidgetUserInfo: void
}export type BridgeEventName = keyof BridgeEventMapclass JSBridge {private bridge: any | null = nullprivate eventHandlers = new Map<string, Set<BridgeHandler>>()public readonly ready: Promise<void>constructor() {this.ready = new Promise((resolve) => {this.setup((bridge) => {this.bridge = bridge// 注册全局事件接收器bridge.registerHandler?.('globalHandler', (msg: BridgeMessage) => {const handlers = this.eventHandlers.get(msg.eventName)if (handlers) handlers.forEach(fn => fn(msg.params))})resolve()})})}/** 初始化 iOS / Android 桥接环境 */private setup(callback: (bridge: any) => void): void {// iOSif (window.WKWebViewJavascriptBridge) return callback(window.WKWebViewJavascriptBridge)if (window.WKWVJBCallbacks) return window.WKWVJBCallbacks.push(callback)window.WKWVJBCallbacks = [callback]window.webkit?.messageHandlers?.iOS_Native_InjectJavascript?.postMessage(null)// Androidif (window.WebViewJavascriptBridge) return callback(window.WebViewJavascriptBridge)if (window.WVJBCallbacks) return window.WVJBCallbacks.push(callback)window.WVJBCallbacks = [callback]document.addEventListener('WebViewJavascriptBridgeReady', () => {callback(window.WebViewJavascriptBridge)})}/** ✅ Web 调用原生方法(Promise 异步) */async invoke<K extends BridgeEventName, R = BridgeResponse<BridgeEventMap[K]>>(eventName: K,params?: BridgeEventMap[K],): Promise<R> {await this.readyif (!this.bridge) throw new Error('Bridge 未初始化')return new Promise((resolve, reject) => {const msg: BridgeMessage = { eventName, params }try {this.bridge.callHandler('globalHandler', msg, (response: BridgeResponse) => {if (response?.success) resolve(response.data as R)else reject(new Error(response?.message || `Bridge 调用失败: ${eventName}`))})} catch (err) {reject(err)}})}/** ✅ 原生调用 Web 事件(支持多订阅) */on<K extends BridgeEventName>(eventName: K, handler: BridgeHandler<BridgeEventMap[K]>) {if (!this.eventHandlers.has(eventName))this.eventHandlers.set(eventName, new Set())this.eventHandlers.get(eventName)!.add(handler)}/** ✅ 一次性监听 */once<K extends BridgeEventName>(eventName: K, handler: BridgeHandler<BridgeEventMap[K]>) {const wrapper = (params: BridgeEventMap[K]) => {handler(params)this.off(eventName, wrapper)}this.on(eventName, wrapper)}/** ✅ 取消监听 */off<K extends BridgeEventName>(eventName: K, handler: BridgeHandler<BridgeEventMap[K]>) {this.eventHandlers.get(eventName)?.delete(handler)}
}const bridge = new JSBridge()
export default bridge
📦 三、JSSDK 封装层
我们为每个原生能力封装 Promise 化的接口:
// jssdk.ts
import bridge, { type BridgeEventMap } from '@/utils/bridge'class JSSDK {private ready: Promise<void>constructor() {this.ready = bridge.ready}/** 获取定位信息 */async getLocation(params: BridgeEventMap['getLocation']) {await this.readyreturn bridge.invoke('getLocation', params)}/** 获取用户信息 */async getUserInfo() {await this.readyreturn bridge.invoke('getUserInfo')}/** 监听设备权限变更 */onPermissionChanged(handler: (state: BridgeEventMap['devicePermissionChanged']) => void) {bridge.on('devicePermissionChanged', handler)}
}export const jsSDK = new JSSDK()
🚀 四、实际使用
在你的 Vue 组件中:
<script setup lang="ts">
import { jsSDK } from '@/utils/jssdk'onMounted(async () => {const location = await jsSDK.getLocation({ type: 'wgs84' })console.log('当前位置:', location)jsSDK.onPermissionChanged(({ location, bluetooth }) => {console.log('设备权限变化:', { location, bluetooth })})
})
</script>
🧠 五、优化亮点总结
| 优化点 | 说明 |
|---|---|
| 强类型映射 | 通过 BridgeEventMap 保证调用与回调参数类型一致 |
| Promise 化接口 | 异步调用结构更清晰,易用性更强 |
| iOS/Android 自动检测 | 自动区分 WebView 环境初始化 |
| 多订阅 + 一次性事件 | 同时支持 on / once 监听 |
| 安全防护 | 调用前 await bridge.ready,防止未初始化调用 |
| 单例模式 | 保证全局唯一的通信实例,避免重复注册 |
🧭 六、适用场景
-
iOS / Android WebView 双端混合开发;
-
小程序 WebView 嵌入网页;
-
Cordova / Capacitor / Flutter Web 容器通信;
-
H5 与原生系统能力(定位、蓝牙、设备信息等)交互。
🪄 结语
这套桥接方案结合了 TypeScript 类型安全 + Promise 异步接口 + Vue3 生命周期管理,
能够在保持灵活性的同时,确保跨端通信的稳定与可维护性。
让 H5 页面与原生交互,变得如同调用普通 API 一样简单优雅 👇
await jsSDK.getLocation({ type: 'gcj02' })
📚 如果你正开发一款需要与原生端交互的 Web 应用,
不妨尝试封装自己的 JSBridge —— 一劳永逸地解决前端与原生通信的复杂性!
