2023-09-06
小程序
00
请注意,本文编写于 324 天前,最后修改于 143 天前,其中某些信息可能已经过时。

目录

官方介绍
推荐理由
与vue的关系
与小程序关系
学习建议
踩坑笔记
样式兼容性问题
微信小程序上的兼容问题
微信小程序与WebComponts
支付宝小程序踩坑笔记
最佳实践
生命周期
样式
组件编写
uni-app框架原理及优化策略
跨平台实现原理图
uni-app小程序设计思路
uni-app运行时
uni-app编译时
总结
uni-app性能优化
setData优化
其他优化

本文写作已有一年半了迁移自老博客。系统梳理了uni-app开发小程序与web开发的异同,踩坑笔记。另外简单介绍了Uniapp框架小程序端的设计思路。

官方介绍

uni-app理念

  1. 为开发者提供免费、高效的开发工具,让天下没有难做的应用
  2. 改进应用形态,让用户更方便的获取数字服务

推荐理由

  1. 跨平台更多(开放包容策略)
    1. 真正做到一套代码多端发行
    2. 条件编译优雅的在一个项目中调用不同平台的特色能力,
    3. 可单独针对某个、某几个平台写组件或者页面
    4. uncloud 解决小程序云开发跨平台问题
  2. 运行体验更好
    1. 组件、api 与微信一致
    2. 兼容weex原生渲染
  3. 通用技术栈学习成本低
    1. vue的语法微信的api
    2. 内嵌mpvue
  4. 开放生态组件更丰富
    1. 支持通过npm引用第三方包
    2. 兼容mpvue组件及项目
    3. App端支持和原生混合编码
    4. DCloud将发布插件市场

与vue的关系

  • 使用vue语法开发
  • h5端支持vue的完整语法
  • app和小程序实现了大部分vue语法

与小程序关系

  • 组件标签靠近小程序规范
  • 应用及页面生命周期使用小程序生命周期,组件使用vue生命周期
  • 接口能力靠近微信小程序规范
  • 使用vue语法开发遵循vueSFC规范
    • 数据绑定及事件遵循vue规范
  • 多端兼容建议使用flex布局详见这里

学习建议

uni-app并不难学,但我们注意到很多新人在适应各个平台的规则限制时比较急躁。 每个端,有每个端的管理规则,这不是uni-app在技术层面上可以抹平的:

  • 比如H5端的浏览器有跨域限制;
  • 比如微信小程序会强制要求https链接,并且所有要联网的服务器域名都要配到微信的白名单中;
  • 比如App端,iOS对隐私控制和虚拟支付控制非常严格;
  • 比如App端,Android、国产rom各种兼容性差异,尤其是因为谷歌服务被墙,导致的push、定位等开发混乱的坑;
  • 如果你的App要使用三方sdk,比如定位、地图、支付、推送...还要遵循他们的规则和限制;

遇事耐心,不急不躁,虽然这不是成功的唯一要素,但它是你技术路上长远走下去的基础。

踩坑笔记

本人有一年多的uni-app开发三端(h5、微信小程序、支付宝小程序),老实说跨端的坑太多了,尤其是早期。 下面的踩坑总结 也说明uni-app跨端只做了浅层磨平

事实上根据uniapp官网文档 跨端注意非h5端不支持的的vue语法 这两篇文章的介绍,真正属于uniapp 的坑很少, 官方文档也只是说了大概,还有很多坑时来自h5与小程序差异的细节(比如IntersectionObserver、下拉刷新)

样式兼容性问题

准确来说这一块并非uni-app问题,以下内容是本人使用uni-app项目开发中遇到的问题。

  • calc函数 ios 10系统不支持, android有些低版本也不支持具体版本记不清了
  • position: fixed
    • 如果父元素增加了transform, 然后其子元素fixed-to-container便以父元素为参考对象了。这其实也不算是兼容性问题,属于CSS知识点了
    • 设置为fixed布局 微信小程序的上拉刷新
  • ios 10 不支持 HTMLElement.style直接赋值,推荐使用setAttribute方法
  • autofocus
    • 在小程序端可以完美的支持 自动聚焦&弹起软键盘
    • h5 andriod上会自动聚焦但不会弹软键盘,但如果手动点击过一次,再focus方法就可以了
    • h5 ios上 既不会自动聚焦也不会弹软键盘,但手动点击过一次后,再调用focus方法就可以了
  • ios 回弹问题
    • 如果在APP里面,ios可以关闭webview的回弹,但仅仅是最外层
    • h5可以同过hack的方式关闭回弹, 原理是监听scroll设置overflow:hidden,在开启scroll
  • ios h5页面 双击会滚动到底部 解决方案 监听touchend事件
  • 刘海问题
    • ios
      • 11之前的系统没有刘海问题
      • 11.0 ~11.2(不包含) 使用 constant
      • >=11.2 使用 env
    • anroid建议给一个默认高度就好一般40px, 或者让app提供bridge方法获取状态栏高度
  • position:stickyandroid 5~6系统中存在兼容问题
  • 小程序上设置样式作用域scoped不生效   (最新测试是有效的)
  • scroll-view中如果有fixed布局,在ios手机中滚动会出现一些奇怪的渲染问题

微信小程序上的兼容问题

  • scroll-view
    • scroll-view区域,不能使用小程序的下拉刷新功能
    • @scrolltolower (原生bindscrolltolower)事件,滑动过快可能不会触发,可以把lower-threshold设置大一点,会缓解很多很多。
  • 插槽 小程序上不支持 v-else-if v-else
  • 模版语法方面,小程序上稍弱
    • 小程序上不支持 :style="customeStyle" 但是支持 :style="{height: customeHeight}"
    • 不支持表达式
    • v-for="it in list.slice(0,3)" 小程序上不支持 ,要这样写 v-for="it in computedList"
    • <comp :text="getText(classes[index])"></comp> 不支持,要写成 :test(getText(index))
  • 事件机制
    • 不支持触发多个事件 比如
    • {this.$emit('input', 123); this.$emit('select', 123)}
  • v-show 微信小程序组件上v-show不生效

uniapp官方对组件上v-show不支持的解释
因为小程序没有v-show,只有if
另外还有hidden这个属性,但这个是作为属性而不是条件控制。
所以如果打算生成小程序的话,还是用v-if来控制,非要用v-show的话,可以自己通过style模拟这个效果。

  • 小程序对变量赋值undfined不会触发刷新 原因点这里

  • e.preventDefault e.stopPropagation 不支持条件控制,只能在模版上写死

  • uni.createIntersectionObserver 该API坑比较多

    • reactiveToViewport
      • h5上表现是相对滚动容器会否可见
      • 微信小程序上是相对屏幕(即使被其他元素遮挡了,该API告诉你元素在可见区)
    • observe
      • h5上可以调用多次
      • 小程序上只能调用一次
  • easycom的bug,不支持作用域插槽

微信小程序与WebComponts

先来看一个案例
左侧是h5 右侧是 微信小程序
image.png 之所以渲染的结果不一样 是因为微信小程序的组件使用后了 ShadowDOM(这个差异正式ShadowDom产生的)
解决方案

  • 在组件上增加样式 <testComp class="hack"></testComp> .hack{width: 100%}
  • 在组件内增加样式 :host {width: 100%}

讲道理 如果使用了ShadowDOM那么样式应该是隔离的,
事实上确实有部分东西是隔离的如css变量的获取,但组件的样式却不是隔离的,也就是 使用 .content .test-comp{} 外层可以修改组件样式。

后来看了下微信的文档 ,微信小程序使用的Exparser框架,Exparser的组件模型与WebComponents标准中的ShadowDOM高度相似,但并非完全一致

补充: 支付宝小程序自定义组件没有使用WebComponets技术。

支付宝小程序踩坑笔记

  • 组件上写class不生效
  • 多次组件嵌套 多个具名插槽好像不支持
  • 不支持作用域插槽
  • my.getSystemInfo 返回值有属性screenWidth是逻辑宽度(物理宽度*像素比DPR)
  • slot不支持备用插槽
  • @touchmove.stop.prevent不生效

最佳实践

生命周期

  • 页面上使用小程序声明周期书写业务逻辑(页面不推荐使用vue生命周期否则你会看到h5正常小程序不正常)
  • 组件上推荐使用vue的生命周期
  • 定时器的使用 在onShow开启, 在onHide onUnload生命周期中销毁
  • 定时器推荐写在页面上,因为组件上没有onShow onHide方法, 详见这里
  • onHide跳到其它页面触发 onUnloadredirectTonavigateBack时触发

样式

  • 样式推荐使用flex布局

组件编写

  • 模版上别使用复杂的表达式,用方法代替表达式
  • 以上踩坑笔记可以借鉴,作为多些写通用代码,通用组件的参考

uni-app框架原理及优化策略

跨平台实现原理图

注: 这里是官方文档的两张图,先体会一下。

image.png

image.png

总结

  • uniapp以微信小程序API为基准,把通用的组件和接口都uni化了
  • 不能通用的组件及接口则使用条件编译或单独针对该平台书写组件代码

uni-app小程序设计思路

uni-app是比较传统的小程序框架,包括编译器和运行时
小程序是视图层和逻辑层分开的双线程架构。
那么我们来分析一下使用Vue开发的代码如何在微信小程序上运行呢?

  1. 首先对于视图层,必须将vue中的template模版语法转换为小程序的wxml语法,这是唯一方案
  2. 对于逻辑层,要使用小程序的路由page.json,那么对于uni-app来说就需要多入口打包,生成小程序各页面所需JS。这两步是编译器做的事情,除此之外还有css的转换。
  3. Vue是响应式的,小程序也是响应式的,那么肯定需要把vue的响应式融入到小程序,这就是uni-app运行时做的事情包含一下内容
    • 不需要VDOM patch函数diff的是数据,调用setData传到是视图层
    • 因为组件使用的是Vue生命周期,这里需要转换为小程序的生命周期
    • 事件也要进行映射(映射关系纪录在wxml文件的标签属性data-event-opts上)
  4. uni-app小程序框架需要对vue框架进行一些改造,如下图 image.png
  • compile不需要静态标注
  • render不需要生成VNode
  • 补丁的生成是直接diff,然后setData

思路有了,还是建议在 uni-app项目中通过 JavaScript调试终端 运行npm run build:mp-weixin命令进行调试, 入口文件是node_modules/.bin/vue-cli-server,在这里设置断点。

可以参考这篇文章《uni-app是如何构建小程序的》 边读边调试。好文章在我这里绝不藏着掖着,也绝不照搬抄袭,这里我也偷个懒,以下内容记录我个人的总结,不做深入解读,仅供参考。

uni-app运行时

uni-app runtime 主要做了三件事情

  • 数据同步机制
  • 事件代理机制
  • 生命周期映射

image.png

uni-app编译时

  • uni-app 是基于 vue-cli 的扩展
  • vue-cli 运行会加载 packagedevDependencies 对象中 以 vue-cli-plugin-* 命名的插件,即加载 @dcloudio/vue-cli-plugin-uni 插件
  • vue-cli 的插件机制在执行插件时会传入两个参数一个是service实例对象(有个plugins属性包含所有插件实例),一个是webpack参数对象,有了这两个参数,@dcloudio/vue-cli-plugin-uni可以做任何事情。

@dcloudio/vue-cli-plugin-uni做了以下事情

  1. 多入口打包
    • 根据参数获取平台信息,找到lib/mp.js, mp.js会一个新的webpack参数对象,它内部会解析pages.json生成多入口对象process.UNI_ENTRY,即webpack参数对象的entry属性,entry: () => process.UNI_ENTRY
    • 使用module-alias修改template编译指向,分发个平台
    • webpack-preprocess-loader 处理条件编译
    • webpack-uni-pages-loaderloader 作用于 src/pages.json,解析生成项目及各页面的 config,并配合 wrap-loader 将转换后的结果引入 src/main.js
    • wrap-loader 自动引入各平台运行时兼容
  2. vue-loader会把vue拆分为templatestylescript三块内容再分发给不同的loaderuni-app则对vue-loader的分发进行了更改
js
{ resourceQuery: /vue&type=script/, use: [{ loader: '@dcloudio/webpack-uni-mp-loader/lib/script' }] }, { resourceQuery: /vue&type=style/, use: [{ loader: '@dcloudio/webpack-uni-mp-loader/lib/style' }] } { resourceQuery: /vue&type=template/, use: [{ loader: '@dcloudio/webpack-uni-mp-loader/lib/template' }] }
  • uni-mp-loader/lib/template.js 把vue模版语法转换为小程序模板语法
  • uni-mp-loader/lib/script.js 使用babel对js句法翻译,生命周期映射、事件代理等
  • uni-mp-loader/lib/style.js 多插入一些nomaliize样式用于磨平平台差异

vue语法转成小程序语法

  • 生命周期转换
  • 模版语法转换
    • 数据绑定  :key="val" >>  key={{val}}
    • 显示隐藏 v-show/v-if >> hidden/wx:if
    • 列表渲染 v-for >> wx:for
    • 双向数据绑定 v-model="myModel" >> value={{myModel}}/bindinput/setData
    • 取值  this.key >> this.data.key
  • 事件 & 传参数
    • @event >> bindtap="" catchtap=""
    • 父传子 :prop >> bind:prop
    • 子传父 this.$emit('myevent', data) >> this.triggerEvent('myevent', data)
    • $refs >> selectComponent
  • 组件注册
    • import  + 配置components >> 子组件component: true 父组件 配置useComponents
  • 。。。。。。

此外还有一些loader

  • webpack-preprocess-loader 处理条件编译
  • wrap-loader  自动引入个平台运行时兼容

总结

image.png

uni-app性能优化

setData优化

减少调用setData数据量

  • 改写了Vuepatch实现,删掉了vnode,仅Diff Data数据 (包减少30%)
  • nextTick机制保证不会重复setData
  • 借鉴westore JSON Diff 实现高效、精准的差量数据

差量更新的本质是调用小程序的setData方法(小程序支持setData本身支持细粒度更新)
this.setData({'obj.list.0.name': '细粒度更新'})

其他优化

  • 自定义组件实现局部数据刷新

 完结 

以下内容为草稿

uniapp项目升级

  • npx @dcloudio/uvm
  • npx @dcloudio/uvm 3.2.12.20211029

本文作者:郭敬文

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!