来龙去脉

公司有个工具类型 app 的开发需求,没有原生开发人员,这个重担就落在我这个前端头上。查阅了一些资料,目前主要有 React Native,Flutter,uni-app 三种跨端 app 开发框架。对比和参照之后,再结合公司情况,最后选择了 uni-app。

uni-app 的优势

无需原生开发人员,只需要前端,会 vue 就会 uni-app,操起键盘就是干。

踩坑之旅

CSS 样式

单位使用 upx,与微信小程序的 rpx 相似,基准宽度为 750upx,可以根据设计稿尺寸计算,计算公式:

设计稿 1px / 设计稿基准宽度 = uni-app样式 1upx / 750upx

最方便的做法是,把设计稿的宽度缩放到 750px,蓝湖上有这个功能,这样 1px = 1upx,省的换算了。

生命周期

大致上和 vue 的差不多,分成页面生命周期和应用生命周期,页面生命周期就是针对单页面的,应用生命周期就是针对整个 app 的,只能在 App.vue 中使用。
虽说同样兼容 vue 的生命周期,但是官方建议使用 uni-app 的生命周期代替。

官方推荐使用 uni-app 里的onLoad 代替 vue 里面的 created
官方推荐使用 uni-app 里的onReady 代替 vue 里面的 mounted

但是有一个坑,在组件中没有生命周期,onLoad,onShow,onReady 全部失效,所以在组件中,需要使用 created, mounted 等 vue 的生命周期。

引入 npm 包

  • es6 和 commonjs 两种方式引入都支持,但是在用 es6 import 方式偶尔会出现引入不了的情况,这时就用 require 吧。
  • 不能使用含有 dom 操作的库,比如 animejs、swiper 就不能用。
  • 不能使用浏览器自带对象,比如 document、window、localStorage、cookie 等,我的项目中引用的 web3 库中调用了 window.XMLHttpRequest,被迫修改源码。

使用路由

uni-app 无法使用 vue-router, 路由全部配置在 page.json 这个文件中,在页面中没有专门的 $route$router对象,仅能在页面的 onLoad 生命周期里面接受路由传参。

// 传参
let url = 'navigate/navigate?id=1&name=rou'
uni.navigateTo({ url })
// navigate.vue 页面接受参数
onLoad(option) { //option为object类型,会序列化上个页面传递的参数
  console.log(option.id)
  console.log(option.name)
}

使用 Vuex

常规的引入方式在 uni-app 中不行,this.$storeundefined,需要在 main.js 中的 vue 原型上挂载一次:

import store from './store'
Vue.prototype.$store = store

网络请求

由于前面提到的原因,uni-app 不支持使用axios,自带的 uni.request 方法又很难用,连promise 都不支持,所以我自己简单封装了一个:

import { HOST } from './config'

class Http {
  /**
   * get请求
   * @param {*} url 请求url
   * @param {*} config 请求配置项,可覆盖url
   */
  get(url, data = {}, config) {
    return this.init(Object.assign({}, {
      url,
      data,
      method: 'GET'
    }, config))
  }

  /**
   * post请求
   * @param {string} url 请求url
   * @param {*} data 请求data
   * @param {*} config 请求配置项,可覆盖url
   */
  post(url, data = {}, config) {
    return this.init(Object.assign({}, {
      url,
      data,
      method: 'POST'
    }, config))
  }

  /*@@@@@ 注意:以下方法不作为http实例使用 @@@@@*/

  /**
   * 初始化网络请求
   * @param {*} options 请求配置
   */
  init(options) {
    if (options.loading !== false) {
      uni.showLoading({
        mask: true,
        title: ''
      })
    }
    return new Promise((resolve, reject) => {
      const {
        url,
        data,
        method
      } = options

      console.log('请求参数', url, data ? data : '空参数')
      uni.request({
        url: HOST + url,
        data,
        method,
        success: (res) => {
          uni.hideLoading()
          console.log(res.data, '返回数据', url)
          if (res.statusCode >= 200 && res.statusCode < 300) {
            if (res.data !== null && res.data.result === 'success') {
               resolve(res.data)
            } else {
               reject(res.data.error || res.data.message)
            }
          } else {
            if (options.error !== false) {
              uni.showToast({
                title: msg,
                icon: 'none',
                duration: 2000
              })
            }
            reject(msg)
          }
        },
        fail: (err) => {
          uni.hideLoading()
          reject(err.errMsg)
        }
      })
    })
  }
}

export default new Http()

多语言国际化

可以使用 vue-i18n,但是 uni-app 不支持在取值表达式中直接调方法,因此,$t方法不可用,所以需要通过计算属性的方式:

<template>  
  <view class="uni-content">  
    <text>{{ i18n.invite }}</text>  
    <text>{{ i18n.game }}</text>  
  </view>  
</template>  

<script>  
export default {  
  computed: {  
    i18n () {  
      return this.$t('index')  
    }  
  }  
}  
</script>

另外在 Tabbar 和 标题栏上的文字是在 pages.json 中写死的,翻译需要借助uni.setTabBarItemuni.setNavigationBarTitle方法:

onLoad() {
  // 设置标题
  uni.setNavigationBarTitle({
    title: this.i18n.index.title
  })
  
  // 设置 Tabbar
  uni.setTabBarItem({
    index: 0,
    text: this.i18n.index.assets,
  })
  uni.setTabBarItem({
    index: 1,
    text: this.i18n.index.me,
  })
}

总结

对于一个功能简单不要求性能的 app 来说,uni-app 提供的功能已经足够用了,可以在不需要原生开发的情况下,迅速开发出安卓和 iOS app,虽然一些原生可以实现的功能实现不了,不过整体开发下来还是比较愉快,很多的坑还是因为多端不兼容,很适合小团队和个人开发者使用。