UniApp 与 H5 双向通信完整教程

UniApp 容器页 、到 H5 index.html 桥接初始化 、再到 H5 业务页收发消息,完整流程一次讲清楚。


1. 先说结论:到底怎么通信?

uni-app<web-view> 场景里,建议用这套模型:

  • UniApp -> H5 :通过子 webview 的 evalJS 主动注入消息
  • H5 -> UniApp :通过 uni.postMessage({ data }) 发消息给宿主页
  • H5 端调用 uni 能力前 :等待 UniAppJSBridgeReady

UniAppJSBridgeReady 的意思:
uni-app 注入到网页里的 JS Bridge 已经准备好了

在这个事件之前调用 uni 相关 API,容易出现 uni 未定义 或调用无效。

官方文档参考:


2. 通信协议先统一

先统一消息结构,避免后面越写越乱:

ts 复制代码
type BridgeMessage = {
  type: string
  timestamp: number
  requestId?: string
  token?: string
  payload?: Record<string, any>
}

建议至少有:

  • type:消息类型(必填)
  • timestamp:时间戳(排查时序问题)
  • requestId:链路追踪(可选但推荐)
  • payload:业务数据

3. UniApp 容器页代码

这个页面负责加载 H5、处理双向通信、回传 token。

vue 复制代码
<template>
  <view class="webview-container">
    <web-view
      v-if="webUrl"
      :src="webUrl"
      ref="webviewRef"
      :update-title="false"
      @message="handleMessage"
      @error="handleError"
    />
  </view>
</template>

<script setup>
import { ref, getCurrentInstance } from 'vue'
import { GRAPH_H5_BASE_URL } from '@/utils/config'
import { onLoad, onReady } from '@dcloudio/uni-app'

const webUrl = ref('')
const token = ref('')
const webviewRef = ref(null)
const childWebview = ref(null)

// 读取 token(可按项目实际改造)
const getTokenFromStorage = () => {
  try {
    const tokenData = uni.getStorageSync('auth_token')
    if (tokenData && typeof tokenData === 'object' && tokenData.access_token) {
      return tokenData.access_token
    }
    if (typeof tokenData === 'string') return tokenData
    return ''
  } catch (e) {
    console.error('获取 token 失败:', e)
    return ''
  }
}

const setNavigationTitle = (title) => {
  if (!title) return
  try {
    uni.setNavigationBarTitle({ title })
  } catch (e) {
    console.error('设置导航栏标题失败:', e)
  }
}

// UniApp -> H5
const sendMessageToH5 = (payload = {}) => {
  // #ifdef APP-PLUS
  const target = childWebview.value
  if (!target) {
    console.warn('子 webview 未就绪,消息发送失败:', payload)
    return
  }

  const safePayload = JSON.stringify(payload)
    .replace(/\\/g, '\\\\')
    .replace(/'/g, "\\'")

  const jsCode = `
    (function () {
      var payload = JSON.parse('${safePayload}');
      if (typeof window.receiveData === 'function') {
        window.receiveData(payload);
      }
      window.dispatchEvent(new CustomEvent('uniapp-message', { detail: payload }));
    })();
  `
  target.evalJS(jsCode)
  // #endif
}

// 页面参数解析:url + title
onLoad((options) => {
  if (!options?.url) return

  let h5Url = ''
  try {
    h5Url = decodeURIComponent(options.url)
  } catch (e) {
    h5Url = options.url
  }

  if (!h5Url.startsWith('http://') && !h5Url.startsWith('https://')) {
    h5Url = `${GRAPH_H5_BASE_URL}${h5Url.startsWith('/') ? '' : '/'}${h5Url}`
  }

  if (options?.title) {
    let title = ''
    try {
      title = decodeURIComponent(options.title)
    } catch (e) {
      title = options.title
    }
    setNavigationTitle(title)
  }

  webUrl.value = h5Url
})

// 获取子 webview,初始化下发 token
onReady(() => {
  if (!token.value) token.value = getTokenFromStorage()

  // #ifdef APP-PLUS
  const instance = getCurrentInstance()
  const currentWebview = instance?.proxy?.$scope?.$getAppWebview?.()
  if (!currentWebview) return

  let checkCount = 0
  const maxChecks = 30
  const checkInterval = 100

  const waitWebviewReady = () => {
    checkCount++
    if (checkCount > maxChecks) {
      console.warn('子 webview 等待超时')
      return
    }

    try {
      const children = currentWebview.children()
      if (!children || !children.length || !children[0]) {
        setTimeout(waitWebviewReady, checkInterval)
        return
      }

      childWebview.value = children[0]

      sendMessageToH5({
        type: 'UNIAPP_INIT',
        token: token.value || '',
        timestamp: Date.now()
      })
    } catch (error) {
      console.error('等待子 webview 失败:', error)
      setTimeout(waitWebviewReady, checkInterval)
    }
  }

  waitWebviewReady()
  // #endif
})

// H5 -> UniApp
const handleMessage = (e) => {
  const rawData = e.detail?.data
  const message = Array.isArray(rawData) ? rawData[0] : rawData
  if (!message || typeof message !== 'object') return

  if (message.type === 'GET_TOKEN') {
    sendMessageToH5({
      type: 'TOKEN_RESULT',
      token: token.value || '',
      timestamp: Date.now()
    })
  }
}

const handleError = (e) => {
  console.error('WebView 加载错误:', e)
  uni.showToast({ title: '页面加载失败', icon: 'none' })
}
</script>

<style scoped>
.webview-container {
  width: 100%;
  min-height: 100svh;
}
</style>

4. H5 的 index.html

先加载 uni.webview.1.5.8.js,再等 UniAppJSBridgeReady

建议下载后放到自己项目的静态资源目录 防止官方的网络波动

html 复制代码
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0,user-scalable=no,maximum-scale=1.0, minimum-scale=1.0"
    />
    <meta name="referrer" content="never" />
    <meta http-equiv="Content-Security-Policy" content="img-src 'self' http: https: data:;" />
    <title></title>
  </head>
  <body>
    <div id="app"></div>

    <!-- uni SDK:建议下载后部署到自己静态资源目录 -->
    <script type="text/javascript" src="/uniapp_webview/uni.webview.1.5.8.js"></script>
    <script>
      document.addEventListener('UniAppJSBridgeReady', function () {
        console.log('UniAppJSBridgeReady')

        // 环境探测(可用于埋点或兼容逻辑)
        if (window.uni?.webView?.getEnv) {
          window.uni.webView.getEnv(function (res) {
            console.log('当前环境:' + JSON.stringify(res))
          })
        }
      })
    </script>

    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

5. H5 业务侧通信代码

ts 复制代码
// 1) 接收 UniApp 主动消息
window.receiveData = function (payload: any) {
  if (!payload || typeof payload !== 'object') return

  switch (payload.type) {
    case 'UNIAPP_INIT':
      localStorage.setItem('app_token', payload.token || '')
      break
    case 'TOKEN_RESULT':
      localStorage.setItem('app_token', payload.token || '')
      break
    default:
      break
  }
}

// 2) 监听自定义事件(与 receiveData 可并存)
window.addEventListener('uniapp-message', (e: any) => {
  const payload = e.detail || {}
  console.log('uniapp-message:', payload)
})

// 3) 主动请求 token(H5 -> UniApp)
export function requestTokenFromUniApp() {
  if (window.uni && typeof window.uni.postMessage === 'function') {
    window.uni.postMessage({
      data: {
        type: 'GET_TOKEN',
        timestamp: Date.now()
      }
    })
  } else {
    console.warn('uni.postMessage 不可用,当前可能不是 web-view 场景')
  }
}

6. 完整流程

  1. UniApp 打开 <web-view> 加载 H5 地址
  2. H5 加载 uni.webview.1.5.8.js
  3. 触发 UniAppJSBridgeReady,桥接就绪
  4. UniApp onReady 获取子 webview,发送 UNIAPP_INIT(含 token)
  5. H5 收到后缓存 token
  6. 业务中若 token 过期或缺失,H5 发 GET_TOKEN
  7. UniApp 收到后回 TOKEN_RESULT
  8. H5 更新 token,继续请求接口

相关推荐
2501_9160074715 小时前
HTTPS 抓包的流程,代理抓包、设备数据线直连抓包、TCP 数据分析
网络协议·tcp/ip·ios·小程序·https·uni-app·iphone
游戏开发爱好者817 小时前
React Native iOS 代码如何加密,JS 打包 和 IPA 混淆
android·javascript·react native·ios·小程序·uni-app·iphone
2501_9159184118 小时前
iOS mobileprovision 描述文件管理,新建、下载和内容查看
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张18 小时前
iOS 应用程序使用历史记录和耗能记录怎么查?
android·ios·小程序·https·uni-app·iphone·webview
学亮编程手记19 小时前
Mars-Admin 基于Spring Boot 3 + Vue 3 + UniApp的企业级管理系统
vue.js·spring boot·uni-app
万物得其道者成1 天前
uni-app CLI:APP 多环境打包(测试/正式)最简配置 + `import.meta.env` 为 `undefined` 的解决
uni-app
毕设源码-邱学长1 天前
【开题答辩全过程】以 基于 uni-app Node.js 的音乐系统设计与实现为例,包含答辩的问题和答案
uni-app
qq_316837751 天前
华为obs 私有桶 音频 使用uniapp 安卓端播放-99的问题
uni-app·音视频
凉辰2 天前
uniapp实现生成海报功能 (开箱即用)
javascript·vue.js·小程序·uni-app