在移动端 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: string
  params?: T
}

type BridgeHandler<T = any> = (params: T) => void

interface BridgeResponse<T = any> {
  success: boolean
  data?: T
  message?: string
}

// ===== 定义事件映射 =====
export interface BridgeEventMap {
  getLocation: { type: 'wgs84' | 'gcj02' }
  openBluetoothAdapter: void
  startBeaconDiscovery: { uuids: string[] }
  getBeacons: void
  stopBeaconDiscovery: void
  closeBluetoothAdapter: void
  devicePermissionChanged: { location: boolean; bluetooth: boolean }
  logout: { redirectUrl: string }
  getMotionDate: { yaw: string }
  getSafeTop: void
  getUserInfo: void
}

export type BridgeEventName = keyof BridgeEventMap

class JSBridge {
  private bridge: any | null = null
  private 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 {
    // iOS
    if (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)

    // Android
    if (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.ready
    if (!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.ready
    return bridge.invoke('getLocation', params)
  }

  /** 获取用户信息 */
  async getUserInfo() {
    await this.ready
    return 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 —— 一劳永逸地解决前端与原生通信的复杂性!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐