HarmonyOS Web组件加载本地HTML并实现Hash路由双向通信
2026/5/14 23:07:53 网站建设 项目流程

文章目录

    • 前言
    • 运行效果图
      • 整体架构:两层结构,各司其职
      • 第一步:本地 HTML 放在哪?
      • 第二步:开启必要权限
      • 第三步:HTML 里的 Hash 路由怎么写
      • 第四步:ArkTS → JS 方向通信
      • 第五步:JS → ArkTS 方向通信(JSBridge)
      • 双向通信全链路汇总
      • 踩坑记录
      • 写在最后

前言

在 HarmonyOS 开发里有个很常见但不太好搞的需求:把一个本地 HTML 页面嵌进 App,还要和原生 ArkTS 代码互相通信。

本篇文章就拿一个真实的WebHashRoutePage实现来拆解,把整个链路讲清楚。代码不多,但坑不少。
项目文件地址 : https://gitcode.com/QZRuoCheng/demoCase

运行效果图

整体架构:两层结构,各司其职

这个方案的核心思路很清晰:

  • ArkTS 层:负责原生导航栏、底部按钮、状态管理
  • Web 层:加载 rawfile 目录里的index.html,跑纯前端 Hash 路由

两层之间通过JSBridge互相喊话,谁都不越界。

第一步:本地 HTML 放在哪?

放在src/main/resources/rawfile/目录下,比如index.html

加载方式:

Web({src:$rawfile('index.html'),controller:this.controller})

$rawfile()是 ArkTS 提供的资源引用语法,专门用来加载 rawfile 目录里的文件。不能用相对路径,也不能用file://,就得用这个。

第二步:开启必要权限

Web 组件默认很保守,要用 JS 就得手动开:

Web({src:$rawfile('index.html'),controller:this.controller}).javaScriptAccess(true)// 允许 JS 执行,必须开.domStorageAccess(true)// 允许 localStorage.fileAccess(true)// 允许访问本地文件.mixedMode(MixedMode.All)// 允许混合内容.cacheMode(CacheMode.Default)

javaScriptAccess(true)是最关键的,没开的话 JS 直接不跑,啥都没有。

第三步:HTML 里的 Hash 路由怎么写

这个index.html实现了一个轻量 Hash 路由引擎,核心逻辑就这几十行:

// 路由表:路径 → 渲染函数varroutes={'/home':renderHome,'/list':renderList,'/detail':renderDetail,'/about':renderAbout};// 路由核心逻辑functionroute(){varhash=window.location.hash||'#/home';varpath=hash.split('?')[0].replace('#','');// 去掉 # 和查询参数varrenderFn=routes[path];// 更新导航高亮document.querySelectorAll('.nav a').forEach(function(a){a.className=a.getAttribute('data-route')==='#'+path?'active':'';});// 渲染页面内容document.getElementById('app').innerHTML=renderFn?renderFn():'<div class="page"><h2>404</h2></div>';}// 监听 Hash 变化window.addEventListener('hashchange',route);// 初始加载document.addEventListener('DOMContentLoaded',function(){if(!window.location.hash)window.location.hash='#/home';route();});

路由跳转很简单,直接改window.location.hash就行:

// JS 内部跳转window.location.hash='#/list';// 带参数跳转(Detail 页解析 id)functiongoDetail(id){window.location.hash='#/detail?id='+id;}

Detail 页解析参数:

functionrenderDetail(){varhash=window.location.hash;varid=hash.split('?id=')[1]||'0';// 拿到 id 参数// ...}

第四步:ArkTS → JS 方向通信

从原生侧触发 Web 里的路由跳转,用runJavaScript()

privatenavigateTo(hash:string){this.currentRoute=hash// 同步 ArkTS 状态this.controller.runJavaScript(`window.location.hash = '${hash}'`)// 驱动 JS 路由}

底部按钮点击时调用这个方法:

Button('列表').backgroundColor(this.currentRoute==='#/list'?'#007DFF':'#CCCCCC')// 高亮当前路由.onClick(()=>this.navigateTo('#/list'))

注意runJavaScript()是异步的,但这里直接改 hash 不需要等回调,所以没问题。

页面加载完成后,也要主动同步一次当前 hash:

.onPageEnd(()=>{this.controller.runJavaScript('window.location.hash',(err,result)=>{if(!err&&result){this.currentRoute=result// 把 JS 里的 hash 同步到 ArkTS 状态}})})

第五步:JS → ArkTS 方向通信(JSBridge)

这是整个方案里最关键的部分,也是最容易踩坑的地方。

先定义桥接对象:

classNativeBridge{privatepage:WebHashRoutePageconstructor(page:WebHashRoutePage){this.page=page}// JS 调用:window.nativeBridge.onRouteChange('#/list')onRouteChange(route:string){this.page.currentRoute=route}// JS 调用:window.nativeBridge.showToast('hello')showToast(msg:string){promptAction.showToast({message:msg,duration:2000})}}

注册 JSProxy(必须在页面加载前):

privatebridge:NativeBridge=newNativeBridge(this)aboutToAppear(){try{this.controller.registerJavaScriptProxy(this.bridge,// 暴露的对象'nativeBridge',// JS 里的变量名:window.nativeBridge['onRouteChange','showToast']// 允许被 JS 调用的方法白名单)}catch(e){console.error('注册JS Proxy失败: '+JSON.stringify(e))}}

** 关键点**:registerJavaScriptProxy必须在aboutToAppear()里调用,也就是页面加载之前注册。如果在onPageEnd之后注册,JS 那边window.nativeBridge就是 undefined。

JS 侧调用方式:

functionnotifyArkTS(){if(window.nativeBridge&&window.nativeBridge.showToast){window.nativeBridge.showToast('来自 JS 的消息');}}

调用前一定要判空,因为如果 Web 组件在某些设备上没有正确注册 Proxy,直接调用会报错。

双向通信全链路汇总

方向方式代码示例
ArkTS → JScontroller.runJavaScript()controller.runJavaScript("window.location.hash = '#/list'")
JS → ArkTSwindow.nativeBridge.xxx()window.nativeBridge.showToast('hello')
ArkTS 读 JS 数据runJavaScript()带回调controller.runJavaScript('window.location.hash', callback)
ArkTS 监听 JS 事件注册 JSProxy 方法registerJavaScriptProxy(bridge, 'nativeBridge', ['onRouteChange'])

踩坑记录

坑1:JSProxy 注册时机

registerJavaScriptProxy必须在aboutToAppear()里调用,不能在onPageEnd里。官方文档说"页面加载前注册",但没说清楚具体位置。踩过一次才知道。

坑2:runJavaScript 的字符串拼接

// 容易出问题this.controller.runJavaScript(`window.location.hash = '${hash}'`)// 如果 hash 里包含单引号就会语法错误// 更安全的写法this.controller.runJavaScript(`window.location.hash =${JSON.stringify(hash)}`)

坑3:JS 调用 ArkTS 方法白名单

registerJavaScriptProxy第三个参数是方法白名单,只有在列表里的方法才能被 JS 调用。如果在 JS 里调用了一个不在白名单里的方法,会静默失败,不报错,但也没效果。

坑4:javaScriptAccess默认是 false

Web 组件默认不允许执行 JS。这个属性没有在文档里特别强调,很多人直接用 Web 组件加载 HTML,然后发现 JS 完全不跑,排查半天。

写在最后

这套方案适合什么场景?

适合:已有 H5 页面需要嵌入原生 App,要和原生能力互调(Toast、导航、数据存储)。
不适合:纯前端内容展示(直接用 Web 组件就行,不用 JSBridge);或者复杂的双向数据同步(考虑用原生 ArkUI 组件代替)。
Hash 路由本身没什么难度,难的是 ArkTS 和 JS 之间的状态同步——谁是状态的"真相来源"要想清楚。这个示例里 ArkTS 的currentRoute是主状态,JS 的 hash 跟着 ArkTS 走,方向很清晰,不会乱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询