小程序跨平台框架调研与实战

2,689 阅读22分钟

小程序跨平台框架调研

小程序开发现状

2017年1月9日,微信小程序正式上线,至今4年多一点的时间,小程序发展突飞猛进。的确,对于某些轻交互场景(如点餐、商店等等)小程序的便携性与轻量化远胜过同类型app或移动h5网页。并且对于某些使用场景,小程序具有先天优势。

于是,各大平台逐步推行自己的小程序,百度、阿里系、头条系...
于是,各大类小程序有不同的语法:.wxml、.swan、.ttml...
于是,各大平台小程序又有不同api: wx.request、 swan.request、tt.request...
于是,开发者崩溃了...
💣💣💣

原生小程序开发

由于平台众多, 语法多、各平台私有化强成了开发小程序的一道大门槛,并且使用小程序原生开发对于我们开发者而言也存在众多弊端:

  • 对Node、预编译器、webpack支持不好,影响开发效率和工程构建流程
  • 多语言开发时,熟悉语法框架的学习成本
  • 单个业务开发时 多语法的思维切换与开发工具的切换 耗费大量开发成本
  • ...

对于单个业务需要部署多平台(如: 功能A希望在微信小程序与百度小程序上同时上线)也存在很多开发阻塞:选择原生小程序开发不止是在各个平台开发维护成本高,同时对于发布运维成本也提高。

💟 但是,办法总比困难多 --- 各类小程序框架应运而生,mpvue、taro、uni等一系列小程序框架

小程序框架开发

既然原生开发的限制多,且不利于运维维护,往往三方开发框架的选择是解决相关问题最好办法。

小程序诞生以来,结合着各类前端框架的火热,各类小程序三方开发框架雨后春笋般涌出,他们以传统前端框架语法、多平台兼容等一系列特性为卖点,吸引许多关注。确实,熟悉的开发方式、多平台打包发布这些特点能将上述【原生小程序开发】遇到的问题迎刃而解,但新的问题又出现了:

  • 众多框架选择的担心:市面上三方框架多,该如何选择最适合当前业务的开发框架(需要考虑适配、性能、开发语法 等等问题)
  • 框架性能比不上原生的担心:原生开发是性能天花板 框架在原生基础上的封装是不是比不上原生 或者说能否达到可用状态?
  • 各类平台不兼容情况的担心
    1. 平台api有 但框架无
    2. 框架有 但在个别平台不兼容
    3. 少部分平台有的单独特性 如 百度特有sitmap 组件等

小程序跨平台框架对比

要选择一个最适合当前业务的合适框架,需要考虑因素众多。一般从两个维度来考虑是否选择当前框架:

  1. 面向用户:提供完整的业务实现,并保证高性能体验
  2. 面向开发者: 平缓的学习曲线、现代开发体验(工程化)、多端复用、高效的社区支持、活跃的开发迭代

mpvue taro uni三大小程序框架横向对比

下面我们选用常见的小程序三大框架: mpvue taro uni(此三类框架在我们业务中均有使用过) 从以上细分维度去横向对比:

面向用户:业务实现能力

  • mpvue:支持微信的所有原生组件和api,无限制;同时框架封装了自己的跨端API,使用方式类似mpvue.request()
  • taro:支持微信的所有原生组件和api,无限制;同时框架封装了自己的跨端API,使用方式类似Taro.request();支持Taro 代码与小程序代码混写,可通过混写的方式调用框架尚未封装的小程序新增API;支持条件编译
  • uni-app:支持微信的所有原生组件和api,无限制。在跨端方面,即便仍然使用微信原生的组件和API,也可以直接跨端编译到App、H5、以及支付宝百度头条等小程序。但为了管理清晰,推荐使用uni封装的API,类似uni.request();同时支持条件编译,可在条件编译代码块中,随意调用各个平台新增的API及组件

面向用户:性能

三方框架,内部大多做了层层封装,这些封装是否会增加运行负载,导致性能下降?尤其是与原生微信小程序开发相比性能怎么样,这是大家普遍关心的问题。
我们选用业务中最易达到性能瓶颈的来比较:(数据来源及比较方法:小程序开发:用原生还是选框架

在一般业务中从触发上拉加载到数据更新、页面渲染完成,需要准确计时。人眼视觉计时肯定不行,我们采用程序埋点的方式,制定了如下计时时机:1. 计时开始时机:交互事件触发,框架赋值之前,如:上拉加载(onReachBottom)函数开头 2.计时结束时机:页面渲染完毕(微信setData回调函数开头) 测试方式:从页面空列表开始,通过程序自动触发上拉加载,每次新增20条列表,记录单次耗时;固定间隔连续触发 N 次上拉加载,使得页面达到20*N 条列表,计算这 N 次触发上拉到渲染完成的平均耗时。

列表条数原生mpvuetarouni
200770969752641
4008764493974741
6001111-1250910
8001406-15471113
10001690-18781321

说明:以400条微博列表为例,从页面空列表开始,每隔1秒触发一次上拉加载(新增20条微博),记录单次耗时,触发20次后停止(页面达到400条微博),计算这20次的平均耗时,结果微信原生在这20次 触发上拉 -> 渲染完成 的平均耗时为876毫秒,最快的uni-app是741毫秒,最慢的mpvue是4493毫秒

注: mpvue在数据达到600条时app直接报出异常并停止渲染 所以没有数据 这就意味在在页面组件太多的时候 mpvue不适用

为什么uni性能可以很好甚至超越: 见‘揭秘uni小程序列表加载性能优于原生微信小程序!’

面向开发者

  • 平缓的学习曲线:简单易学,最好能复用现有技术栈,丰富的学习资料
  • 高效的开发体验:现代前端开发流程、工程化支持
  • 高效的社区支持:遇到问题,可很快的寻求到帮助
  • 活跃的开发迭代:框架处于积极更新升级状态,无需担心停更

三个框架在不同的开发维度表现的比较

mpvuetarouni
DSL语法vuereactvue
IDE支持性使用vue相关的eslint 无自动补全有相关eslint规则 有自动补全插件eslint相关都可以集成 有自动不选插件 条件编译插件 并且有自己的IDE
ts支持
工程化方式有 基于vue-cli 相关的封装

注: 更多对比参见 跨端框架深度评测:微信原生、wepy、mpvue、uni-app、taro、chameleon

各小程序框架对于我们业务需求的不足之处

  1. mpvue
  • 只支持编译成微信小程序 多端能力不足
  • 只支持类vue语法(很多vue并没有实现)--- 简单的数据绑定 条件渲染 循环列表等等 本身限制很多 如 class绑定的语法限制
  • 同一页面组件反复进入数据被覆盖 (由于本身的组件复用的优化机制 导致的bug)并且里面的路由参数会出现不更新的问题
  • 组件编译某些情况出问题 导致编译失败
  • ...
  1. taro 泰罗·奥特曼(2.x)
  • 编译器效率极低 占用大量cpu性能 经常出现一个文件写入多行代码后就内存溢出 开发效率极低 (并且加上 当时使用taro项目去编译的百度小程序 百度小程序开发者工具存在同样的编译效率低下的问题 曾经一度盲改业务代码)
  • 升级小版本后出现不兼容问题
  • getSystemInfoSync 在部分安卓机型下识别的高度错误
  • ...

注:新版本taro(3.x)对上述问题都有一定的改进,且新版本后也支持vue语法了

总结:为什么选择uni

  • 如果你只开发微信小程序,不做多端,uni-app是更好的选择,除非你有兴趣手动优化原生小程序的代码,或者对react非常熟悉不愿意学习vue也可以使用taro
  • 另外注意,使用微信原生开发,对于webpack、各种预处理器、工程化流程的支持很不好,这样对比下来还是用框架开发更合适。
  • 如果你主要为了统一各家小程序,uni-app仍然是最好的选择,taro次之。
  • 如果你还需要跨端到H5侧,那么uni-app在跨端兼容方面会让你更省心。

在我们选型框架时,选用的对比框架都是vue、react语法类别的,这对于开发者的开发习惯和学习成本都是有益的,尤其像我们业务团队技术栈都是vue相关的。(虽然新版本的taro也支持vue语法,但taro最初是基于react开发,本身设计理念偏向react,vue只是他为了扩大市场做得额外的适配,所以在底层设计上肯定更倾向react),同时对比到mpvue的性能差距和多端能力,这让我们更加坚定选择uni-app作为我们的小程序开发框架。

为啥说小程序的框架开发体验更贴近现代化的工程化项目?

  1. 更为强大的组件化开发能力:vue的组件开发比小程序自定义组件开发的体验要好很多
  2. 可使用 Sass 等 CSS 预处理器
  3. 完整的 ES Next 语法支持
  4. 自定义构建策略

附:为什么uni里面的 列表渲染 性能可以强于原生

微信原生框架耗时主要在setData调用上,开发者若不单独优化,则每次都会传递大量数据;而 uni-apptaro 都在调用setData之前自动做diff计算,每次仅传递变动的数据。 例如当前页面有20条数据,触发上拉加载时,会新加载20条数据,此时原生框架通过如下代码测试时,setData会传输40条数据

data: {  
    listData: []  
},  
onReachBottom() { //上拉加载  
    let listData = this.data.listData;  
    listData.push(...Api.getNews());//新增数据  
    this.setData({  
        listData  
    }) //全量数据,发送数据到视图层  
}

开发者使用微信原生框架,完全可以自己优化,精简传递数据,比如修改如下:

data: {  
    listData: []  
},  
onReachBottom() { //上拉加载  
    // 通过长度获取下一次渲染的索引  
    let index = this.data.listData.length;  
    let newData = {}; //新变更数据  
    Api.getNews().forEach((item) => {  
        newData['listData[' + (index++) + ']'] = item //赋值,索引递增  
    })   
    this.setData(newData) //增量数据,发送数据到视图层  
}

web开发也有原生js开发、vue、react框架等情况。如果不做特殊优化,原生js写的网页,性能经常还不如vue、react框架的性能。
也恰恰是因为Vuereact框架的优秀,性能好,开发体验好,所以原生js开发已经逐渐减少使用了。

uni跨平台方案实战

uni项目框架架构设计

项目开发方式类型选择

官方提供两种方式初始化项目方式:

  1. 通过HBuilderX用可视化界面 dcloud
  2. 通过vue-cli形式初始化项目 采用传统npm + ide 形式开发cli

选择理由:

  1. 内置了d.ts,同其他常规npm库一样 正常开发并有语法提示。
  2. vscode 支持性好
  3. 由于整个项目时基于vue-cli改造 所以它具有更好的自定义构建策略支持

注:cli里面的模板项目由于是挂到github上的,需要爬上梯子才能拿到

项目框架结构

1. for development

uni脚手架初始化项目 整体是vue-cli里面的模板改造的,他的特性如下:

  • compiles 和 hot-reloads 是依赖于 vue-cli-service
  • development与production模式区分依赖于cross-env 例子: cross-env NODE_ENV=development UNI_PLATFORM=h5 vue-cli-service uni-serve

以下几点是我对框架的自定义操作:

1. 静态资源cdn接入 见本章最佳实践分享--- cdn
2. env注入

项目需要配置接口环境,使用vue-cli环境变量 功能,项目中加入.env.local文件:

TAG=local
ENV=dev

其中ENV是用来控制环境变量的,TAG是tag名用来区分版本与ci相关(后文会涉及)

同时使用webpack.DefinePlugin插件注入全局的环境变量

 new webpack.DefinePlugin({
        __DEV__: process.env.ENV === 'dev',
        __TAG__: JSON.stringify(process.env.TAG)
      })
3. dev脚本编译

由于本地加入了cdn的编译配置,由vue-clicompiles 和 hot-reloads无法对编译成cdn的静态资源显示,解决方案是在本地dev server的同时对cdn的资源进行静态托管。这里推荐使用live-server快速搭建静态服务(相关工具包还有很多,还可以用express,但轻量快速还是推荐live-server)。

由于uni编译出来的代码输出于dist/dev/[mp-key]文件夹中(这里的key是指uni系统中对不同平台小程序的简称如:微信: mp-weixin),若选择的编译平台不同,输出的文件夹也不同,同时也不能一次性编译所有平台开发代码。

故采用命令行交互形式去让开发者选择要编译的平台(同时选择了要静态托管的cdn目录)并且可以设置其他选项如:uni编译模式 这里推荐inquirer工具库

const inquirer = require('inquirer')

const UNI_PLATFORM = ['mp-weixin', 'mp-baidu']
const NODE_ENV = ['development', 'production']

export default inquirer.prompt([
  {
    type: 'list',
    name: 'UNI_PLATFORM',
    message: '请选择一个你要创建的项目',
    choices: UNI_PLATFORM
  },
  {
    type: 'list',
    name: 'NODE_ENV',
    message: '请选择编译环境',
    choices: NODE_ENV
  }
])

效果:

示例.gif

那怎么同时开启两个服务?

这里使用了node子进程child_process相关 同时开启一个子进程去跑live-server静态服务(有兴趣的同学可以查阅node相关文档)

2. for business coding

1. 全局layout

由于uni中没有router,但业务需求中又有公共样式、公共组件组件所以项目中使用全局组件的形式做全局layout:每个页面的根元素用<layout>标签包裹


// 页面
<template>
  <layout :showAuth="false" :showToolBar="false">
  ...
  </layout>
</template>


// layout组件使用 slot
<view class="layout" @click="handleClick">
 <slot name="swiper"></slot>
</view>

设计功能:

  1. 全局auth-modal 检查业务功能权限
  2. 全局页面形式 swiper list default (根据不同的页面形式 页面展示不同的功能样式)
  3. 全局组件挂载
  4. 页面高度计算
2. 全局page-settings

还是由于小程序里面没有router,其页面路由都是由pages.json注册,对于页面路径管理非常不方便


INDEX: {
    path: '/pages/index/index', // 首页
    trackName: '进入首页',
    tdk: {}
 },

引入全局的page-settings 不仅可以方便路由以类似于 vue.router.name 的形式跳转管理,还可以做到tdk配置(小程序需要分享,且百度小程序又seo需求)并且可以对于全局的页面类型埋点可以做到配置

3. 全局api代理、promise化

uni提供一系列的api调用,但这些api大多数的的兼容性并不是很好:如: 百度有的api 微信没有相同的, 原生新出的api uni里面没有实现 ;并且对于api的调用总是需要使用回调函数的形式拿到其结果(对于异步类型的)这样对于我们的代码体验非常不好。此时考虑将其代理后解决上述一系列问题

// 使用 es6 Proxy构造函数代理 uni
new Proxy(uni, {
  get: function (target, property) {
    // target 是代理对象 uni
    // property 是取的属性

    // 可以做同步与异步的 api判断 异步api做promise化代理
    return (...arguments) => {
      return new Promise((resolve, reject) => {
        const [params = {}, ...others] = arguments;
        params.success = (...args) => {
          resolve(...args);
        };
        params.fail = (...args) => {
          reject(...args); //eslint-disable-line
        };

        // host可以设置成uni 也可以做api 检测设置白名单 解决 原生新出的api uni里面没有实现问题
        host[property](params, ...others);
      });
    };
  },
});

两者使用后对比

// 正常使用 
const task = uni.request({
  url:'/'
  success(res) {
    console.log(res);
  },
}); 


// Promise 化 
jsapi.request({url: '/'}).then(res => { 
  // 此处即为 success 回调的 res 
  console.log(res) 
})

getCurrentPages 代理改写

在兼容支付宝小程序与头条小程序时发现getCurrentPages()获取页面堆栈结果 uni并没有对其常用字段进行兼容 如: 微信小程序 百度小程序 含有 route字段 支付宝小程序__prototype__ 上有route 头条小程序__prototype__ 有is

我们可以代理改写掉他的字段调用


export default new Proxy(getCurrentPages, {
  apply(target, thisArg, argumentsList) {
    const instance = target()
    // 对instance数组里面的每个页面栈对象进行兼容性调整 参照微信 PageObject[]
    // 所有PageObject 包含 route(页面path) options(参数对象)
    instance.forEach(item => {
      // 微信小程序 百度小程序 route 支付宝小程序__prototype__ 上有route 头条小程序__prototype__ 有is
      const route = item.route || item.is

      return {
        ...item,
        route
      }
    })
    return instance
  }
})

这样改写后 每次调用getCurrentPages获取小程序当前页面堆栈中的路由字段 都只用.route获取 无需考虑兼容性 大家在自己代码中也可以用类似的方式解决同类问题

4. 全局css

uni提供两种方式使用全局css:

  • 项目根目录uni.scss文件里面写入全局css
  • App.vue文件里面写入
    区别是:uni.scss是一个特殊文件,在代码中无需 import 这个文件即可在scss代码中使用这里的样式变量。uni-app的编译器在webpack配置中特殊处理了这个uni.scss,使得每个scss文件都被注入这个uni.scss,达到全局可用的效果。 但App.vue是基于vue的能力去的,他并不会全部文件注入,采用第二种方式写入全局样式
5. rpx支持

uni本身提供了rpx 这对于移动端的响应式布局有很大的帮助

6. 开发基本配置相关

uni小程序配置文件分为两大部分
pages.json 文件用来对 uni-app 进行全局配置,决定页面文件的路径、窗口样式、原生的导航栏、底部的原生tabbar 等
manifest.json 文件是应用的配置文件,用于指定应用的名称、图标、权限等

3. for production

1. 小程序ci/cd

构建/部署说的简单点,就是先利用 webpack这类的工具把工程打包,然后把打包得到的文件放在服务器上某个托管静态资源的 Web 容器里(像 Java 就可以放在 Tomcat,或者用 Nginx 托管静态资源...)。 类比到小程序(指我们当前uni项目),要实现小程序的自动化构建/部署需要以下步骤:

  • 1. 项目代码打包(uni打包压缩成各个平台小程序的代码)
  • 2. cdn文件的上传
  • 3. 小程序平台对相关代码的压缩、打包
  • 4. 打包好的代码上传到小程序平台

前两个是已有功能(cdn上传和普通的ci流程中的无任何区别),主要讲一下我们是怎么实现后两个功能的。

若小程序压缩上传代码是在本地手动操作的,它依赖于开发者工具的内置打包、上传功能,我们ci中无法利用开发者工具去打包上传,但各个小程序官方都提供了相关的命令行工具去让开发者脱离开发者工具打包上传。

-微信小程序CI
-百度小程序命令行工具

除了微信、百度小程序,头条小程序也支持(支付宝没有相关能力工具,其他平台需要大家自己去探索啦!),大致分为两种形式: api形式调用 --- 微信小程序 命令行工具调用 --- 百度小程序

两者对于平台小程序代码的打包都是在平台本身的服务器(也就是说我们只要把uni打包好的代码上传到平台后台就ok了),不同的是,微信小程序用api调用很简单好用,很容易集成(相关代码参考上面的官方文档),但对于百度小程序设计的本意是让开发者在开发时手动操作上传的,并不是用来做ci集成,但也可以对其加以改造:

利用node child_process.execSync 的能力 使命令行可以在我们的脚本中执行


cp.execSync(`swan upload 
--project-path ./dist/build/mp-baidu 
--token ${token} 
--release-version ${version} --min-swan-version 3.230.33`, { stdio: [0, 1, 2] })

其中的version可以结合ci中的tag号去定义版本号(前文注入的全局环境变量__TAG__),这样对于后期版本排查,代码排查都有很好的帮助

2. 日志系统

日志系统是每个前端项目中建立监控体系的首要:

我们之前选择的是阿里云arms系统去实行监控排查开始监控微信小程序,但是由于arms系统是收费的,并且费用还不低,在使用了一段时间后我们下掉了该系统。土豪忽略可以随便接入

接入需要注意的是: 版本号字段使用__TAG__;对比了其接入方式(手动埋点型、自动收集型)决定采用自动收集型去植入:每个页面实例中需要用Monitor.hookPage去包裹其(原理是重写里面的生命周期,对其进行埋点与错误收集)。但是它是针对于原生小程序接入的,对于uni(看了源码之后发现Monitor.hookPage函数对于里面的参数只有小程序生命周期函数的改写)只需要将项目里面的有关于小程序的生命周期通过hookPage调用后结构到页面实例里面就行 (对于hookAPP函数也是相同的接入方法: App.vue 文件中调用后结构就好了)

hookPage

export default {
  //vue其他节点
  ...Monitor.hookPage({
   // 小程序相关生命周期
  }),
}

后期这也是一个待优化点:

  • 自建的日志系统

uni最佳实践分享

1. cdn

传统web项目的cdn设置(下面以vue-cli项目举例)

项目中build后,生成的静态文件都可以已cdn的方式发布(包括js css img),vue-cli内置全局的 publicPath配置节点去配置发布服务器的发布路径,之后只要把static目录下的所有文件发布至cdn服务器后,部署index.html就可以了

对比传统项目 小程序的表现

对于js css而言,其文件是要给小程序打包后上传到小程序后台(作为代码包,可类比离线包,也就是说小程序其实是安装在微信app内的类“离线包”的东西),原生小程序的img需要放在static目录下,被小程序打包后作为小程序代码包的一部分,且小程序中使用img有以下规则

  • 支持 base64 格式图片。
  • 支持网络路径图片。

对于base64当然是有一定优点的,但是图片资源一般是超过40kb会有性能问题(uni官方给出的数据),所以使用cdn网络图片加载img资源不仅能降低代码包的大小,还可以避免40kb限制问题,同时静态资源中,图片是占大头的。

uni如何cdn

uni是基于vue-cli改造而来,他必然支持webpack的相关配置,通过vue.config.js自定义各类配置。但他有相关限制:

publicPath 不支持 outputDir 不支持 assetsDir 固定 static

要想改变cli的规则,我们先查看他的内部的默认规则

技巧: 使用vue inspect命令输出当前项目的配置


vue inspect > output.js

输出后发现默认的对于images配置如下:


 /* config.module.rule('images') */
      {
        test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 4096,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: 'static/img/[name].[hash:8].[ext]'
                }
              }
            }
          }
        ]
      }
 

由此可以看来:对于img,4kb以上的文件都会被哈希并且输出到static目录下,对此我们使用chainWebpack 函数对其进行自定义更改: 1. 加上publicPath 并且去除limit限制 2. 指定输出目录


function cdnFile(rule) {
  rule
    .use('file-loader')
    .loader('file-loader')
    .tap(options => {
      const env = cdnSettings[process.env.ENV] || {}
      return {
        esModule: false,
        publicPath: env.publicPath,
        name: 'mwebuni/[name]-[hash:8].[ext]'
      }
    })
}

修改后发现,不仅对于publicPath可以支持,小程序开发中对于静态资源引入方式的限制也都统统去除(无论是css还是模板中,引入方式都和传统web开发一致,不需要特定目录,特定路径方式),这点对于我们框架开发有重大意义

uni中iconfont如何cdn

iconfont本质上也是一堆字体文件,我们可以同样对字体文件进行cdn设置(具体方式与上述设置img文件是一样的,这里就不过多赘述),实际上字体文件大小一般不会特别大到影响性能,这里其实可以直接使用小程序自带的功能放在static文件夹下面将其本地化还可以提升性能。

2. uni条件编译

uni专门为跨端兼容提供了条件编译手段:uni-条件编译

但每个平台有自己的一些特性,因此会存在一些无法跨平台的情况。

  • 大量写 if else,会造成代码执行性能低下和管理混乱。
  • 编译到不同的工程后二次修改,会让后续升级变的很麻烦。

它是以特定语法让编译器识别匹配出当前平台的代码,从而进行条件编译(uni对特定的语法提供了vscode插件)

页面的条件编译

对于pages.json文件,



{
  "path": "xxxxx1",
  "style": {
    "navigationBarTitleText": "xxxx1"
  }
}
// #ifdef MP-WEIXIN
,{
  "path": "xxxx",
  "style": {
    "navigationBarTitleText": "xxxx"
  }
},
{
  "path": "xxxx2",
  "style": {
    "navigationBarTitleText": "xxxx2"
  }
}
// #endif

问题点: 为什么对于第二项的条件编译中的需要放在 // #ifdef MP-WEIXIN 里面包裹?

由于uni的条件编译只是识别特定代码模块来相对于当前的编译平台去除(或者添加)代码,他并不会改变代码里面的语法 ,放在外面会导致编译出来的json文件最后一项有,这会导致json语法错误!

3. 底部tab切换

首先看实现效果:

2.gif

首页置顶 切换成logo, 非置顶状态切换成:回到顶部箭头按钮,切换收藏与首页时同时底部tab按钮发生变化。

iconPathselectedIconPath 可以搞定tabbar切换时的icon更改,但是首页滚动和改成 回到顶部箭头按钮呢?

我们需要监听页面滚动且调用setTabBarItemapi实现。但是对于已经切换到收藏页面又切换回来,首页还是会回到selectedIconPath的设置,我们通过onTabItemTap 生命周期中去检测顶部位置后再次调用setTabBarItem设置相关icon即可以达到效果

4. 动画

实现效果:

1.gif

vue中有transition 组件来方便快捷的实现动画,uni中也提供了自己的一套动画实现方式:uni-createanimation

他是以jsapi的形式去生成一个动画对象,里面包含描述动画属性的很多字段,生成的动画对象绑定至 view 中即可,通过属性对象绑定,我们还可以方便的去控制动画出现时机,消失时机等等。

如上图样例所示: 页面开始滚动的时候收起,页面停止滚动放出后过一段时间再次收起 控制好滚动时机调用animation的实例方法可以实现

5. 组件生命周期的手动实现(对于头条 支付宝小程序的兼容)

vue 组件生命周期中无法访问当前组件所在页面的生命周期 但是 uniapp 也有暴露出相关 api 来访问


 onPageShow
 onPageHide
 onPageResize
 

注意: 以上 3 个生命周期并没有在 uni-app 官方文档中写出来,它们在微信小程序与百度小程序是正常使用的,但由于支付宝小程序与头条小程序原生就没有,所以uni中也没有支持

但是在我们业务中,对 onPageShow onPageHide生命周期依赖性很强,支付宝与头条端不支持导致整个项目无法做到对这两端的覆盖,于是解决。方法有两个: 1. 重构整个项目涉及于 onPageShow onPageHide生命周期的相关业务,更换实现方法 2. 手动实现这两个生命周期

由于第一种方法改动特别大,工作量较多直接放弃。 转向方法二:

手动实现这两个生命周期可以有两种方式:

  1. 每个页面的onShow生命周期中去获取当前页面所用到的组件后去触发组件里面自定义方法(即onPageShow
  2. 组件中自定义customPageShow方法,使用全局事件形式,页面onShow的时候去触发component methods里面的自定义customPageShow事件

首先对于方法1: 暂且不说如何去获取当前页面所用到的所有组件,获取到之后的递归调用性能耗费就比较大了。且小程序中没有什么好的方式能获取当当前的vue实例,获取到的当前页面实例里面没有使用的子组件的信息 --- 行不通 方式2考虑的只有两点:如何注册&怎么触发

这里可以注意两个点:

  1. 在组件的mounted中注册customPageShow,(注意注册了必须按时机销毁)注册的时候注意
  2. 在每个页面的onShow中手动触发customPageShow,此时可以借助hookPage,实现所有页面的onShow生命周期的改写(无需每个页面去改代码)

//注册
mounted() {
  const registerPage = this.getCurrentPageUrl()
  this.emitPageShow = () => {
    const emitPage = this.getCurrentPageUrl()
    if (registerPage === emitPage) {
      this.onCustomPageShow()
    }
  }
  this.onCustomPageShow()
  uni.$on('customPageShow', this.emitPageShow)
}

// 触发
const showHook = page.onShow
const onShow = function () {
 // 这里注意this指向 调用onShow即为页面vm对象
  showHook && showHook.call(this)
  uni.$emit('customPageShow')
}

  • 第一次进入页面,onShow回调执行,触发全局事件customPageShow,但此时子组件还未注册事件,无事发生
  • 子组件mounted触发,手动执行一次onShow回调,并注册全局onShow事件 原因: 页面触发onShow比当前页面下组件的mounted早,在mounted里面手动触发一次回调
  • 为保证customPageShow只调用了当前页面组件下的customPageShow 注册时检查页面

6. 原生组件引入(以百度小程序引入原生sitmap组件为例)

写在最后

经过1个半月的框架重构 + 业务代码迁移,我们的小程序(包括微信小程序与百度小程序)于5月中旬上线,服务至今,后续又上线头条平台,欢迎大家给相关建议。

若您的业务中,需要有小程序跨平台需求、有小程序开发需求,uni将会是你不错的选择之一。

感谢大家的阅读,希望大家针对给出指正,谢谢!!!