Vue H5嵌入鸿蒙原生应用,Web组件双向通信踩坑全解
文章目录
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看, 传送门https://blog.csdn.net/qq_34419312
前言
先交代个背景:你手上有一套 Vue 写的 H5,跑了挺久,页面也熟、接口也稳,领导一拍脑袋说,给它塞进鸿蒙原生应用里去。
这活儿听着简单,不就是嵌个网页吗?干过 iframe 的都知道,这事一旦换个宿主,味道全变了。今天就把这套东西捋一遍。
1. 先搞清楚你手里是哪个容器
鸿蒙里嵌网页,不是只有一种玩法。应用内用 ArkUI 的 Web,元服务里用 ASCF 的 web-view。
俩东西名字长得像,都叫 WebView,但桥接机制完全是两个世界。你要是把这边的代码抄到那边去,就像拿着外卖地址去取快递——快递员看你的眼神都不对。
1.1 一张表看清两边的区别
| 对比项 | 应用里的 Web | 元服务 web-view |
|---|---|---|
| 写在哪 | ArkUI 的 build() 里,跟 Column 平级 | ASCF 页面,写法接近小程序 |
| 心智模型 | 一个你能完全控制的 iframe | 微信小程序里那个 web-view |
| H5 调原生 | 容器往 window 上挂对象,直接调 | 走 has.ascfweb.postMessage |
| 原生调 H5 | controller.runJavaScript(…) | createWebViewContext().postMessage(…) |
| 实时性 | onPageEnd 之后立刻能聊 | 消息经常要等页面退了才回调,主打一个异步 |
| 判断跑哪了 | 看 window 上有没有注入的对象 | UA 里带 ASCF/ |
选型就看你住哪栋楼。住应用楼,就用左边这套;住元服务楼,别往左边搬家具。下面的内容全部只讲左边的 Web,元服务那套 postMessage 体系今天不展开。
2. Web 组件到底怎么放进去
先说个认知纠偏:Web 就是页面上的一块矩形区域,它不是 Ability。Ability 只负责把窗口打开,就像酒店前台给你房卡,进了房间之后干嘛是你自己的事。
你现在的应用主框架是 Tab,每个 Tab 底下有一条导航栈。H5 就是这条栈里压着的一页,页面内部再嵌一个 Web。退栈、切 Tab 走的还是 NavPathStack,网页自己的 vue-router 只管这块矩形里的事,出了这个框它说了不算。
一个 Web 配一个控制器。控制器就放在这个页面上,别在子组件里再 new 一个——控制器这东西跟牙一样,掉了不好配。
2.1 最小可运行代码
直接上代码,边看边聊:
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct H5Page {
private controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'https://example.com/h5/', controller: this.controller })
.javaScriptAccess(true)
.domStorageAccess(true)
.onPageEnd(() => {
this.notifyH5Ready();
})
.onErrorReceive(() => {
// 页没打开,别在这调桥,白搭
})
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
private notifyH5Ready(): void {
this.controller.runJavaScript('window.onNativeReady && window.onNativeReady()');
}
}
2.2 这几个开关必须先打开
很多人上来就写桥,写完发现桥不工作,第一反应是"桥断了",其实是门没开。鸿蒙的 Web 跟浏览器不一样,浏览器默认啥都给你开着,这里得自己伸手去拧开关。
| 配置 | 不开会怎样 |
|---|---|
javaScriptAccess(true) | 页面能打开,JS 一行不跑,你挂在 window 上的对象凭空消失。浏览器默认就开的东西,这里要手动开 |
domStorageAccess(true) | localStorage、sessionStorage 全部静默失效。前端存个 token、存个草稿,刷新就没了,你还以为是浏览器bug |
fileAccess(true) | 只嵌网络页可以不开,嵌 $rawfile 里的本地页再开 |
mixedMode(MixedMode.All) | HTTPS 页面里混了个 HTTP 图片,直接被拦。跟浏览器策略一样,得显式放行 |
src 就两种来源:要么是线上的 Vue 站点,要么是打进安装包的 $rawfile('index.html')。想换地址就换 src,别指望改个 @State 网页 DOM 自己跟着变——人家有自己的想法。
2.3 加载回调什么时候用
把它当 iframe 生命周期记就行:
onPageBegin:开始请求了,这时候弹个原生 loading 正合适onProgressChange:进度条,懂的都懂onPageEnd:相当于iframe.onload。从这一刻起,你才敢调runJavaScriptonErrorReceive:没打开。先查地址、查证书、查混合内容,别一上来就怀疑桥有问题,桥这锅背得冤
2.4 推荐使用顺序
照这个顺序来,能少踩一半坑:
- 页面创建时 new 一个 WebviewController,build() 里放 Web,先把 javaScriptAccess 打开
- 需要存东西就再把 domStorageAccess 打开
- 等
onPageEnd触发,然后通知 H5:“桥能用了” - 之后原生用 runJavaScript 调 H5,H5 用注入对象调原生
- 用户要离开这块网页时,走原生导航栈。别只调
history.back(),那只能退 H5 自己的历史,退不回原生页面——就像你在地铁里按关门键,门纹丝不动,因为那按钮是给装修工人试电路用的
3. 双向通信怎么搞
这里有个关键认知:两个方向,两套 API,别想着用一个 postMessage 全包圆。H5 和原生是两个运行时,Vue 的 ref 不会变成 @State,原生对象也不能直接当参数传进网页。
过桥的只有 JSON。就这么简单粗暴。你传个 Vue 实例过去,对面收到的就是 [object Object],跟你在微信群里发了一段深情长文,对方只回了个"嗯"是一个效果。
两边先约定好消息格式,参数和返回值都用字符串:
interface BridgeMessage {
type: string;
payload: string;
}
3.1 H5 调原生:往 window 上挂对象
在 Web 上声明代理。name 就是挂到 window 上的名字,methodList 是白名单。没写进名单的方法,网页再怎么调都调不到——这就像公司门禁卡,你不是那个部门的,门刷不开,别硬试。
Web({ src: 'https://example.com/h5/', controller: this.controller })
.javaScriptAccess(true)
.javaScriptProxy({
name: 'nativeApi',
methodList: ['getToken', 'closePage', 'postMessage'],
controller: this.controller,
object: {
getToken: (): string => {
return JSON.stringify({ token: '原生登录态' });
},
closePage: (): void => {
// 退出当前原生页
},
postMessage: (raw: string): string => {
const msg = JSON.parse(raw) as BridgeMessage;
// 按 msg.type 分发
return JSON.stringify({ ok: true });
}
}
})
H5 那边就是普通函数调用,先判断对象在不在,再 parse 返回值:
function readToken() {
if (!window.nativeApi || !window.nativeApi.getToken) {
return;
}
const data = JSON.parse(window.nativeApi.getToken());
sessionStorage.setItem('token', data.token);
}
function tellNative(type, payload) {
window.nativeApi.postMessage(JSON.stringify({
type: type,
payload: JSON.stringify(payload)
}));
}
这事儿跟你在网页里接一个提前注入的 JS-SDK 一模一样:方法名固定,入参出参全是字符串。复杂对象调之前先 stringify,到对面再 parse。
登录态就走这条线:H5 在自己的 mounted 里调 getToken。别假设浏览器 Cookie 会自动出现在 Web 里——它俩不是一个浏览器,Cookie 不互通,就像你家的门禁卡开不了隔壁单元的门。退出登录时也一样,H5 调个 clearSession,或者由原生通知 H5 把 sessionStorage 清掉。两边都清,不能只清一边,清一半等于没清。
3.2 原生调 H5:runJavaScript
H5 得先把接收函数挂到 window 上,跟你先 addEventListener(‘message’) 再等消息是一个顺序——你得先把耳朵竖起来,别人说话你才听得见:
window.onNativeReady = function () {
readToken();
};
window.onNativeMessage = function (raw) {
const msg = JSON.parse(raw);
const payload = JSON.parse(msg.payload);
// 按 msg.type 更新页面
};
原生这边必须等 onPageEnd。在那之前,那些函数还没挂上去,你调了也是对着空气说话。参数用 JSON.stringify 拼进脚本,防止引号把脚本截断——这是拼接字符串的基本功,字符串拼接界的老手都知道,引号没转义好,整段脚本直接报语法错误,报的错还特别抽象。
private sendToH5(type: string, payload: string): void {
const raw = JSON.stringify({ type: type, payload: payload });
this.controller.runJavaScript(`window.onNativeMessage(${JSON.stringify(raw)})`);
}
runJavaScript 的回调拿到的是脚本的返回值字符串。想从网页读数据,就让脚本里 return JSON.stringify(...),回调里再 parse。异步函数就在脚本里自己 await 完,最后 return 一个 JSON 字符串。别把 Promise 对象当结果传回来——Promise 过桥之后就是个普通对象,你拿不到它内部的值,就像你给朋友发了个问号,朋友回了你一个问号的快递盒,你打开一看里面还是问号。
原生推 token 给 H5、刷新网页里某块数据、通知网页"用户点了原生按钮",都走这个方向。它灵活,但本质是在拼脚本。所以脚本内容只能来自业务常量或者已经校验过的 JSON,千万别把 H5 刚传回来的原文再执行一遍——那等于把别人塞给你的纸条原封不动念出来,纸条上写着"把你工资转给我"你也照念?
3.3 一次完整业务的配合顺序
拿"原生已登录,打开 H5,H5 要 token,还能通知原生关页"举例子,顺序是固定的,别乱:
原生页面 → Web:创建控制器并加载 src
Web → H5:解析页面
Web → 原生:onPageEnd
原生 → H5:runJavaScript onNativeReady
H5 → 原生:window.nativeApi.getToken
原生 → H5:返回 JSON 字符串
H5 → 原生:window.nativeApi.closePage
原生 → 原生:退出导航栈
对应到代码就是前面那三块:页面持有控制器,javaScriptProxy 提供 getToken 和 closePage,onPageEnd 里调 onNativeReady。H5 在 onNativeReady 里再去读 token。
别在 Vue 的 created 钩子里假设 nativeApi 已经存在。注入时机跟文档加载有关,就绪通知一律以 onPageEnd 为准。你 created 里喊一声 nativeApi,人家还在后台加载呢,谁理你。
methodList 里只留业务方法:取 token、关页、把消息交给原生。千万别留一个"执行任意脚本"的口子,那等于在自家大门上贴了个"欢迎光临",谁来都能进。
4. 出问题先查这几条
排查这种东西,按顺序来,别瞎试:
- 白屏:先看 src 和 onErrorReceive。这阶段桥还没开始,别往桥身上想
- nativeApi is undefined:javaScriptAccess 没开,或者 name 跟网页里写的对不上。注意大小写,这种事查起来最让人血压飙升
- 方法存在但一调就报错:方法没写进 methodList。白名单这东西,漏一个字都不行
- 原生调 H5 没反应:要么早于 onPageEnd 了,要么 H5 还没把函数挂到 window 上。查时序,别查代码
- 对面拿到 [object Object]:过桥前没 stringify。这是最经典的错误,没有之一,每个嵌过 Web 的人都至少踩过一次
- H5 里 localStorage 写不进去:domStorageAccess 没开。开一下就好,别怀疑人生
- 点返回只在网页历史里跳:关页要走原生导航栈,不是 history.back()。你退的是网页的后退键,不是原生的返回键,两码事
5. 收个尾
应用内嵌 H5,用的就是 Web:页面里放组件,一个控制器,把 JS 开关打开。通信走两条线——H5 调 window 上挂的白名单方法,原生在 onPageEnd 之后用 runJavaScript 调 H5。数据只走 JSON 字符串,别的别想。
元服务那套是另一套 postMessage,跟这套别混着用。混着用的下场就是:两边都能跑,两边都不对,排查三天发现是自己把两套桥接到一块了。
就这些。
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看,传送门https://blog.csdn.net/qq_34419312
更多推荐



所有评论(0)