文章目录
- 前言
- 案例的结构重点
- 完整代码
- 先把两条调用方向讲透
- 方向一:ArkUI 调 JavaScript
- 方向二:JavaScript 调 ArkUI
- `jsResult` 这个状态为什么要留着
- 双向通信流程可以怎么理解
- 关键代码怎么迁移到真实业务
- 安全提醒不是装饰
- 这个案例适合的真实场景
- 常见坑我提前说几个
- 写在最后
前言
一旦Web组件不只是拿来“展示网页”,事情就开始复杂了。活动页想调用原生分享,H5 想拿登录态,原生按钮想通知网页切 tab,最后都会落到一个词上:桥接。
这个话题一上来就讲实现细节,往往很容易把人讲懵。因为桥接不是一个 API,而是一套通信关系。案例这一页的价值就在这,它没有急着写满接口,而是先把 ArkUI 和网页之间的调用方向讲清楚。
说白了,你得先知道谁调用谁,再谈怎么调用。
案例的结构重点
案例把桥接拆成了三部分:
- ArkUI 调 JavaScript
- JavaScript 调 ArkUI
- 双向通信的完整流程
同时,它还放了一个最小状态jsResult,用来观察交互后的结果变化。这个思路是对的,因为桥接这种东西,如果没有一个状态出口,很难确认链路到底通没通。
完整代码
import{DEMO_BG_COLOR,DEMO_CARD_COLOR,DEMO_THEME_COLOR,DEMO_SUBTEXT_COLOR}from'./types'@Entry@Componentstruct WebJavaScriptBridge{@StateisShow:boolean=true@StatejsResult:string='等待调用...'callJsDemo():void{this.jsResult=`JS 调用结果:${Date.now()}`}build(){Column(){if(this.isShow){Scroll(){Column({space:16}){Text('Web JavaScript 交互').fontSize(16).fontColor(DEMO_THEME_COLOR).fontWeight(FontWeight.Bold)Column(){Text('JS Bridge 交互模式').fontSize(13).fontColor('#4D96FF').margin({bottom:10})Column({space:12}){Column(){Text('方向一: ArkUI -> JS').fontSize(12).fontColor('#4D96FF').fontWeight(FontWeight.Bold)Column(){Text('controller.runJavaScript(code)').fontSize(11).fontColor('#FFFFFF')}.width('100%').height(40).backgroundColor('#4D96FF').borderRadius(6).justifyContent(FlexAlign.Center)}Column(){Text('方向二: JS -> ArkUI').fontSize(12).fontColor('#FFA500').fontWeight(FontWeight.Bold)Column(){Text('.onControllerAttached() 注册 JS 回调').fontSize(11).fontColor('#FFFFFF')}.width('100%').height(40).backgroundColor('#FFA500').borderRadius(6).justifyContent(FlexAlign.Center)}Column(){Text('双向通信流程').fontSize(12).fontColor('#6BCB77').fontWeight(FontWeight.Bold)Column({space:4}){Text('1. ArkUI 调用 runJavaScript() 执行 JS').fontSize(10).fontColor('#FFFFFFCC')Text('2. JS 通过 window.ohosCallNative() 回调').fontSize(10).fontColor('#FFFFFFCC')Text('3. 注册 javaScriptProxy 实现双向调用').fontSize(10).fontColor('#FFFFFFCC')}.width('100%').backgroundColor('#6BCB77').borderRadius(6).padding(8)}}}.backgroundColor(DEMO_CARD_COLOR).padding(12).borderRadius(12)Column({space:8}){Text('交互结果展示').fontSize(14).fontColor(DEMO_THEME_COLOR).fontWeight(FontWeight.Bold)Text(this.jsResult).fontSize(13).fontColor('#1A1A1A')Button('模拟 JS 调用').onClick(()=>{this.callJsDemo()}).fontSize(12).backgroundColor(DEMO_THEME_COLOR)Button('模拟 runJavaScript').onClick(()=>{this.jsResult='执行 JS: changeTitle("New Title")'}).fontSize(12).backgroundColor('#6BCB77')}.backgroundColor('#E8F4FD').padding(14).borderRadius(12)Column({space:4}){Text('安全注意事项').fontSize(13).fontColor('#FF6B6B')Text('使用 javaScriptProxy 时注意安全').fontSize(12).fontColor('#333333')Text('避免注入恶意代码和 XSS 攻击').fontSize(12).fontColor('#333333')Text('验证所有 JS 回调的数据合法性').fontSize(12).fontColor('#333333')}.width('100%').backgroundColor('#FFF0F0').padding(12).borderRadius(8)}.width('100%')}}Text('WebJavaScriptBridge - JS Bridge 通信').fontSize(12).fontColor(DEMO_SUBTEXT_COLOR).margin({top:12})}.width('100%').height('100%').backgroundColor(DEMO_BG_COLOR).padding(16)}}先把两条调用方向讲透
这部分是整篇最重要的认知点。
方向一:ArkUI 调 JavaScript
这是最容易理解的一条链路。原生侧通过控制器执行网页里的 JS 代码:
controller.runJavaScript(code)比如网页里有个changeTitle(),那原生按钮就可以主动触发它。这个能力特别适合什么场景?
很典型的一类,是原生操作区控制网页内容。比如原生头部点“切换主题”“打开弹层”“跳到某个 section”,页面主体其实是 H5,这时候就要用原生去调 JS。
方向二:JavaScript 调 ArkUI
反过来,网页也可能需要告诉原生侧一些事情。比如:
- H5 登录完成,通知原生刷新用户信息
- 点击网页里的分享按钮,唤起原生分享面板
- H5 页面里的某个链接不是普通跳转,而是触发原生路由
案例里用这句描述得比较抽象:
.onControllerAttached()注册JS回调真实项目里,核心思想就是把一个可调用的原生接口暴露给网页脚本。网页拿到之后,就能带着参数回调原生。
jsResult这个状态为什么要留着
案例用@State jsResult保存交互结果:
@StatejsResult:string='等待调用...'然后在点击按钮后更新它:
callJsDemo():void{this.jsResult=`JS 调用结果:${Date.now()}`}这不是为了做花活,而是为了让桥接变得可观测。
桥接出问题时,最麻烦的是你经常不知道卡在哪一段。是按钮没点到?是原生方法没执行?还是网页没回传?只要你在页面里保留一个明确的结果显示区,调试效率会高很多。
双向通信流程可以怎么理解
案例把完整流程总结成三步:
1.ArkUI 调用runJavaScript()执行JS2.JS通过 window.ohosCallNative()回调3.注册 javaScriptProxy 实现双向调用这三句本质上讲的是一件事:原生和网页都不能把对方当成本地函数直接调用,中间需要一个约定好的桥。
你可以把它理解成一套跨边界协议。
原生发消息过去,网页按约定的方法名和参数处理;网页再回消息回来,原生按同样约定接住。真正麻烦的不是调用本身,而是调用协议是否稳定。
关键代码怎么迁移到真实业务
如果你要落地到项目里,建议最开始只定义少量桥接动作,不要一口气开放十几个接口。
一个靠谱的做法是:
- 先约定统一的数据格式,比如
{ action, payload } - 让原生只暴露少数可控动作,比如分享、登录态获取、页面关闭
- 对所有来自网页的数据做校验,不要直接信任
桥接的核心不是“能调通”,而是“可控、可维护、可排查”。这点很重要。
安全提醒不是装饰
案例最后专门给了安全区,这一点非常对。
Text('使用 javaScriptProxy 时注意安全')Text('避免注入恶意代码和 XSS 攻击')Text('验证所有 JS 回调的数据合法性')很多人做桥接只关注功能,结果把网页当成绝对可信。只要外部内容来源稍微复杂一点,这种思路就会出事。
至少要守住三条底线:
第一,不要执行未经约束的任意脚本。
第二,不要把敏感能力全部开放给网页。
第三,所有回调参数都要校验格式和内容,不能拿来就用。
这个案例适合的真实场景
活动页分享、H5 支付结果通知、内容页唤起原生评论区、嵌入式管理后台、Hybrid 登录流,这些都是桥接高频场景。
尤其当你项目里一部分页面是网页、一部分页面是原生时,桥接能力几乎绕不过去。区别只在于你是先把它设计清楚,还是后面边补边救火。
常见坑我提前说几个
第一个坑,是直接把方法名和参数写死在多个页面里。后面一改协议,所有地方一起炸。
第二个坑,是桥接动作没有日志。调试时只看到“没反应”,但不知道消息有没有发出去。
第三个坑,是把桥接当成万能方案。很多高频交互如果可以原生化,长期看还是原生更稳。
第四个坑,是没有设计调用失败的兜底。网页调原生失败、原生调网页失败,都应该有明确反馈。
写在最后
桥接这件事,最怕的不是复杂,而是含糊。只要你先把方向分清楚,再把协议和安全边界收紧,它其实就是一套可以逐步扩展的通信层。
这个案例最适合拿来当认知起点。你不一定今天就把完整桥接写完,但至少从现在开始,原生和网页之间那条线不会再是一团雾了。