找回密码
 立即注册

微信小程序 WebView First 开发方案实践

匿名  发表于 2022-8-30 13:37:26 阅读模式 打印 上一主题 下一主题
WebView First,是指优先利用 WebView 停止开辟,在 WebView 没法满足需求时,再利用原生(微信小法式原生,后续提到原生皆为此意)实现。
前言

首先声明态度,我是一个微信小法式黑。作为一个 Web 开辟者,心里是抵抗小法式的,但何如生活所迫,该写还是得写。幸亏还有一条开辟的是 Web,但进口是小法式的门路可以走。
虽然在小法式中 WebView 的利用很是普遍,但还是以原生为主的方式停止开辟。缺少以 WebView 为主的案例。
本文是我的 WebView First 理论的记录,希望能给想尝试 WebView First 的列位,供给帮助和底气。
先容

首要原则

以 WebView 为主构建小法式,优先经过 JS-SDK 挪用微信才能(同公众号),其次再斟酌跳转到原生页面调小法式的接口。
尽能够让用户长时候逗留在 WebView ,避免 WebView 与原生页面之间切换。
原生页面与 WebView 之间经过 URL 停止通讯。
益处


  • 提升开辟体验、进步开辟效力
  • 下降小法式考核频次
  • 大部分场景下有更好的性能
  • 更多的 npm 包可以利用
  • 更多的处理计划可以参考
  • 更轻易地迁移到其他端
那价格是什么?


  • WebView 页面初次衬着较慢(初次衬着指重启小法式,并非第一次拜候小法式)【可用 Web 白屏/首屏题目标优化方式停止优化】
  • 原生页面与 WebView 页面之间,切换本钱高【和产物商量,削减这类场景出现】
  • 页面栈治理受限
  • 利用小法式的才能受限【小部分可用 JS-SDK 取代,其他需要跳转到原生页面实现】
  • Android 下持久贮存(如 localStorage、cookie)不合适预期【具体说明见下文】
  • 没法利用现成且丰富的小法式组件【但你有更多组件库可以挑选】
  • 部分组件实现的性能不如小法式供给的原生组件
  • 没法利用自界说导航栏【可以跳转到原生页面实现,但不保举利用的缘由同第2点】
  • 页面切换动画结果纷歧致(WebView 内切换、WebView 与原生之间切换)
  • 原生视频/直播的小窗口(画中画)在安卓中不能带到 WebView【没有很好的处理计划,避免利用画中画时前往 WebView 吧】
与原生 First 的差别
原生 First 即优先利用原生,部分页面嵌入 WebView。

  • WebView First 大部分操纵在 WebView 中停止,大幅度下降了 WebView 页面与原生页面的切换(切换和通讯本钱较高)
  • WebView First 结构更加简单,根基不需要斟酌小法式
  • 持久贮存,原生 First 首要存在小法式,WebView First 存在 WebView。
理论
本文将以 Vue 百口桶作为 Web 侧的技术栈停止说明。
小法式页面结构


微信小法式 WebView First 开辟计划理论-1.jpg

小法式页面结构

为了让左上角的出现返回,我们需要一个进口页面(没有内容的),当进口页面 onShow 时,则 navigateTo 到 WebView 页面。
对于其他原生页,倡议是设想成弹窗处置,最初都要退回到 WebView 页面。(务必和产物说好,避免出现 WebView 与原生之间的频仍跳转)
情况区分

从微信 7.0.0 起头,可以经过判定 userAgent 中包括 miniProgram 字样来判定小法式 WebView 情况。
  1. const IS_WEAPP = navigator.userAgent.toLowerCase().includes('miniprogram')
复制代码
原生与 WebView 的通讯

虽然小法式供给了 message 事务,答应 WebView 向小法式发送消息,可是由于触发条件相当刻薄,很难满足需求。
再由于需要通讯的场景都是 WebView 与原生之间切换,所以采用了经过跳转地址停止传参。
具体实现见下一节。
别的,需要留意地址的长度限制。
路由跳转治理

路由跳转大致分为三类: 1. WebView 到 WebView 2. 原生到原生 3. WebView 到原生 4. 原生到 WebView
对应处置方式: 1. 不需要任何处置 2. 不需要任何处置 3. 利用 JS-SDK 停止跳转,利用 navigateTo 将原生页推入页面栈中 4. 尽能够利用前进的形式退回,按照情况,革新 WebView 的内容。
3、4两种情况尽能够避免,由于 WebView 初始化略慢,这会大大下降用户体验、增加保护的难度。
然后我这边引入了第三方库 qs 用来对 query 停止剖析和字符串化。
WebView 跳转到原生

我挑选了路由设置和导航保卫停止路由治理。
  1. const router = createRouter({
  2.   routes: [
  3.     {
  4.       path: '/payment',
  5.       name: 'Payment'
  6.       component: { /* ... */ }, // 若没有对应的页面,可设备为 404 页
  7.       meta: {
  8.         weapp: '/pages/payment', // 小法式中对应页面的途径
  9.       },
  10.     },
  11.     /* ...其他路由 */
  12.   ],
  13.   /* ...其他设置项 */
  14. })
  15. router.beforeEach((to) => {
  16.   /* 判定小法式情况 */
  17.   if (!IS_WEAPP) return true
  18.   /* 判定 meta.weapp 能否存在 */
  19.   if (!to.meta?.weapp) return true
  20.   // qs.stringify 将工具拼接成 key=value 形式的字符串
  21.   const query = qs.stringify({
  22.     // 需要的数据同一增加到 query 中,如 token
  23.     token: 'token',
  24.     ...to.query
  25.   })
  26.   const url = `${to.meta.weapp}?${query}`
  27.   /* 挪用 JS-SDK 跳转 */
  28.   wx.miniProgram.navigateTo({ url })
  29.   return false
  30. })
复制代码
原生跳转到 WebView

在小法式中设备一个全局变量作为 web-view 的地址,当原生的 WebView 页面 onShow 时,更新 web-view 组件的 src 的值。
  1. // webview-src.js
  2. /* 这个就是全局变量,也可以放到 getApp().globalData 中 */
  3. export const state = {
  4.   src: '',
  5.   query: {}
  6. }
  7. export function setSrc(options = {}) {
  8.   state.query = {
  9.     is_weapp: 'true',
  10.     ...options.query,
  11.   }
  12.   const _query = qs.stringify(query)
  13.   // __WEBVIEW_URL__ 是 WebView 的拜候地址
  14.   state.src = `${__WEBVIEW_URL__}${options.path}?${_query}`
  15. }
  16. export function navigateBack(options, navigateOptions = {}) {
  17.   setSrc(options)
  18.   wx.navigateBack(navigateOptions)
  19. }
  20. export function navigateTo(options, navigateOptions = {}) {
  21.   setSrc(options)
  22.   wx.navigateTo({
  23.     ...navigateOptions,
  24.     url: '/pages/webview/index' // WebView 页面途径
  25.   })
  26. }
  27. export function navigateTo(options, navigateOptions = {}) {
  28.   setSrc(options)
  29.   wx.navigateTo({
  30.     ...navigateOptions,
  31.     url: '/pages/webview/index' // WebView 页面途径
  32.   })
  33. }
  34. // WebView 页面的 js 文件
  35. import * as webview  from './webview-src'
  36. Page({
  37.   data: {
  38.     src: ''
  39.   },
  40.   onShow() {
  41.     this.setData({
  42.       src: webview.state.src
  43.     })
  44.   }
  45. })
复制代码
持久贮存

一般来说,间接利用 localStorage 或是 cookie 作为持久贮存就行了。可是安卓中小法式是多进程,分歧进程的 WebView 的持久贮存数据是隔离的。当你把小法式的进程关了,再翻开时能够会拿到和上一次完全纷歧样的数据。
那末,我们就需要将 WebView 的持久贮存数据存到 WebView 之外。可选的只要存到小法式的 storage 和存到办事器。
存小法式的 storage 中,则必须经过 message 事务。存在两个题目:一、message 事务没法稳定触发;二、取回数据只能经过地址传数据,长度存在限制。故,这个计划被放弃了。
存到办事器,首先需要处理的是区分装备,其次再处理平安性的题目。
区分装备

我采用的是由办事器天生装备 ID,存在小法式中,经过地址将装备 ID 传给 WebView。
平安性

若装备 ID 泄露了,会被轻松获得到全数的持久贮存数据。我们可以进步盗用的门坎,增加了一次性签名,以装备 ID 和时候作为签名数据,经过公私钥的形式建立签名。而签名的建立在小法式内停止(WebView 中也行,但会下降平安性),签名也经过地址传给 WebView。
别的还可以定期更换装备 ID。
上传数据也可以适当增加门坎,比如拉取时一样的签名机制,比如拉取时授与上传数据用的 token(下次拉取时生效),等等。
其他说明

由于持久贮存数据丧失,仅仅只是在小法式进程被杀的情况。所以只需要在小法式初次启动进入 WebView 的时辰需要从办事器拉数据。在路由参数中增加初次拜候的标识。
Web 侧拉取数据的处置

我用了比力粗鲁的计划,在建立 Vue 实例前请求数据,将数据更新到持久贮存以后间接革新页面,同时移除地址参数中的装备 ID 和签名。
假如你的项目中,读取持久贮存数据都发生在 Vue 实例建立以后,那末不必经过革新重新读取数据。
  1. async function pullStorage() {
  2.   /* 仅小法式情况且是安卓系统 需要拉取数据 */
  3.   if (!(IS_WEAPP && IS_ANDROID)) return
  4.   const url = new URL(location.href)
  5.   const deviceId = url.searchParams.get('device_id') // 装备 ID
  6.   const deviceSign = url.searchParams.get('device_sign') // 拉取数据用的签名
  7.   if (!(deviceId && deviceSign)) return
  8.   try {
  9.     /* 倡议请求 */
  10.     const fetchUrl = `/pull-storage?id=${deviceId}&sign=${deviceSign}`
  11.     const data = await fetch(fetchUrl).then((res) => res.json())
  12.     /* storageKeys 是项目中所用到的 storage 的键名 */
  13.     /* storage 是 localStorage 的封装 */
  14.     /* 将拉取到的数据存到 localStorage 中 */
  15.     storageKeys.forEach(key => {
  16.       const value = data?.[key]
  17.       storage.setItem(key, value)
  18.     })
  19.     /* 保存装备 ID,用于上传数据 */
  20.     storage.setItem('deviceId', deviceId)
  21.   } catch (err) {
  22.     console.error(err) // 请求失利不做处置
  23.   } finally {
  24.     url.searchParams.delete('device_id')
  25.     url.searchParams.delete('device_sign')
  26.     location.replace(url.toString()) // 革新页面
  27.     await new Promise(() => {}) // 阻止衬着,期待页面革新
  28.   }
  29. }
  30. function init() {
  31.   const app = createApp(App)
  32.   /* ... */
  33.   app.mount('#app')
  34. }
  35. pullStorage().finally(init)
复制代码
JS-SDK

能用 JS-SDK 处理的,就不要跳到原生页面。
不外要留意并不是一切 JS-SDK 的接口都能在小法式中利用,具体请查询 《web-view | 微信开放文档》。
除了小法式独有的路由相关的接口,其他接口均需要先辈行权限考证设置。此处有两个坑。
相关设置

JS-SDK 一切的参数和设置,均为公众号,而非小法式。appId 利用公众号的;平安域名设置在公众号下。
wx.config 签名参数 url

分歧装备、分歧版本微信 url 的取值纷歧致。

  • iOS 初次拜候时的地址
  • Android + 旧版本微信(猜测 8.x 之前,没有明白的版天职界限),当前地址
  • Android + 新版本微信,两者皆可
别的留意不要带 hash。
总结

牺牲了特定场景的用户体验和产物设想自在度,换来开辟体验、开辟效力以及大部分场景的用户体验。
可是,假如对小法式原生组件或才能依靠比力严重(且不能零丁分手出来一个页面),或是有不能接管的价格,不倡议尝试该计划。
最初,愿人间没有小法式。
回复

使用道具

说点什么

您需要登录后才可以回帖 登录 | 立即注册
HOT • 推荐

神回复

站长姓名:王殿武 杭州共生网络科技 创始人 云裂变新零售系统 创始人 飞商人脉对接平台 创始人 同城交友聚会平台 创始人 生活经验分享社区 创始人 合作微信:15924191378(注明来意)