跟我一起跳进uni-app这个坑

7,135 阅读10分钟

uni-app最近超级火,各大招聘要求都把uni-app添加到任职需求,所以自己作死把公司的一个微信公众号项目用uni-app开发。

本文适合有Vue开发经验者阅读。

一、准备工作

1、下载HBuilderX编辑器

下载HBuilderX编辑器,HBuilderX是uni-app官方推荐,其中包含可视化搭建uni-app项目、运行项目、打包编译项目。

下载地址点这里,因为我只开发微信公众号项目,也是就H5项目,所有只下载标准版的就可以。

2、解决HBuilderX编辑器无法对less文件格式化问题

我是使用less作为css的预编译语言,如果你选择的是sass作用css的预编译语言,可以跳过这步。

修改这两个文件的配置,就可以解决这个问题。

把红框的内容添加进去即可。

二、创建uni-app项目

在HBuilderX编辑器中创建项目非常简单,只需要两步。

推荐选择内置uni-ui项目模板,该模板已内置大量常用组件,而且不要删除太多无用文件和代码。

创建好后项目的目录结构如图所示

  • components 放置内置组件。
  • pages 放置开发页面。
  • static 静态资源。
  • App.vue 项目页面主入口 相当Vue项目的App.vue。
  • main.js 相当Vue项目的main.js。
  • manifest.json 一些打包编译相关的配置。
  • pages.json 配置 路由、窗口样式、原生导航栏、底部原生tabbar。
  • uni.scss 配置uni-app内置的常用样式变量。

三、启动uni-app项目

启动uni-app项目,在HBuilderX编辑器中也非常简单,一步搞定。

开始编译

编译成功

在浏览器打开http://loaclhost:8080/,如果页面如下图所示,说明项目启动成功。

四、开发阶段

1、怎么去除原生导航栏

我这个项目原型中是没有原生导航栏的,所以要把它先去掉。

方法在文档这个地方这个地方

navigationStyle设为customtitleNView设为false时,原生导航栏不显示。所以去除H5端的原生导航栏有两种方法。

都是在pages.json文件中

统一去除原生导航栏

"pages": [
    {
        "path": "pages/index/index",
        "style": {
            "navigationStyle": "custom",
            "navigationBarTitleText": "uni-ui基础项目"
        }
    }
],

或者,只针对H5端去除原生导航栏。

"pages": [
    {
        "path": "pages/index/index",
        "style": {
            "h5":{
                "titleNView":false
            }
        }
    }
],

下图就是去原生导航栏后的效果

2、路由配置

目前前端项目大多都是用路由来控制页面跳转,所以入坑一个新框架,先要属性一下这个框架的路由怎么配置。

uni-app的路由配置方式和小程序很像,如果你会小程序,配置起来更容易了。

uni-app项目的路由还是pages.json中pages属性中配置,其实上面已经应用到了。

pages属性的值是个数组,数组每项是个json对象,每个路由就配置在json对象中,其中path属性是页面路径,style是配置页面的样式,我们先不看。

在uni-app项目中,我们一般把页面写在pages文件夹下,例如在pagse文件夹下添加一个叫home.vue的文件,文件内容如下

<template>
    <view>我是home页面</view>
</template>

<script>
</script>

<style>
</style>

那么我们要在pages.json中这么配置。

"pages": [
    {
        "path": "pages/index/index",
        "style": {
            "navigationStyle": "custom"
        }
    },
    {
        "path": "pages/home",
        "style": {
            "navigationStyle": "custom"
        }
    }
],

刷新页面,你会发现浏览器上没有 我是home页面

当然这里不是配置错了。只是在uni-app有个规定pages节点的第一项为应用入口页(即首页)。所以我们要改一下。

"pages": [
    {
        "path": "pages/home",
        "style": {
            "navigationStyle": "custom"
        }
    },
    {
        "path": "pages/index/index",
        "style": {
            "navigationStyle": "custom"
        }
    },
],

这时你会发现,编译失败了。 原因就是在uni-app中配置文件,每一项结束尾巴都不能加,(逗号)。,把逗号去掉,在编译,成功后刷新页面,就会看到home.vue这个页面里面的内容。

现在我们把home.vue这个文件删掉,在编译,会报错,编译不成功。

提示我们找不到home.vue这个文件,所以在新增/减少页面,都需要对 pages 数组进行修改!

"pages": [
    {
        "path": "pages/index/index",
        "style": {
            "navigationStyle": "custom"
        }
    },
],

重新编译,编译成功。

3、怎么引入阿里图标

阿里图标这东西,几乎在每个项目中都会使用到。

在Vue项目,我们引入阿里图标是在public文件夹下的index.html文件,通过link引入的。

但是在uni-app项目中没有index.html文件,那么要在哪里引入呢。

在前面有提到过App.vue是项目页面主入口,那我们可以尝试在这里引入。

在Vue项目中.vue文件中我们是这么引入样式

<style lang="less" scoped>
    @import "./css/index.less";
</style>

去MDN查询@import的用法(入口),发现 @import可以这么用。

@import url("chrome://communicator/skin/");

那么尝试在App.vue这么引入阿里图标

<style>
    @import url("https://at.alicdn.com/t/font_1811528_5xzkmbymkb7.css");
</style>

然后在pages/index/index.vue文件中这么使用

<template>
    <view class="container">
        <text class="iconfont iconSIMCard"></text>
    </view>
</template>

刷新页面,发现引入成功。

4、关于标签的使用

如果你会小程序开发,这一点可以忽略不看。

我们之前开发微信公众号,是用H5开发的,自然可以使用html5的标签。如常用的divspanimg

那么在uni-app项目中只能用它自带的组件标签,如<view></view>就相当<div></div><text></text>相当<span></span>,,<image></image>相当<img/> ,具体可点这里这里看。

5、关于图片路径问题

说到这个路径,我们首先要搞懂几个常识。

~表示Web应用程序根目录,/也是表示根目录,../表示当前目录的上一级目录,./表示当前目录。

在uni-app项目引入图片,要用内置组件标签<image></image>,其src属性就是配置图片路径,仅支持相对路径、绝对路径,支持 base64码,但自定义组件里面使用<image>时,若 src 使用相对路径可能出现路径查找失败的情况,故建议使用绝对路径。

在项目中我是把图片资源放在根目录的assets/images文件夹中。如果你的页面在pages文件夹中的路径很深,例如

那你是不是要在页面上这么写

<template>
    <view>
        <image src="../../../../assets/images/indeximg1.png"></image>
    </view>
</template>

其实我们可以利用~来改造一下。

<template>
    <view>
        <image src="~assets/images/indeximg1.png"></image>
    </view>
</template>

在less中使用background也可以利用~

.home{
    background:url(~images/card/home.png) no-repeat;
}

这样写可以避免因为少些或者多些../导致的路径错误,配置起来也轻松。

另外我们可以像Vue项目中在根目录下添加一个vue.config.js文件,来配置webpack,当然在uni-app项目中配置和在Vue项目中配置是有差异,可以点这里看。

我们在vue.config.js文件配置一个路径别名,配置方法和在Vue项目中一样。

const path = require('path');
function resolve(dir) {
    return path.resolve(__dirname, dir)
}
module.exports = {
    configureWebpack: config => {
        return baseConfig = {
            resolve: {
                extensions: ['.js', '.vue', '.json'],
                alias: {
                    'assets': resolve('assets'),
                    'css': resolve('assets/css'),
                    'images': resolve('assets/images'),
                }
            },
        }
    },
}

6、关于第三方组件的使用

在uni-app中使用第三方组件都会下载到components文件夹中。所以引入第三方组件的做法和Vue项目开发一样。

如要使用 Popup 弹出层 ,这么引入

components: {
    uniPopup: () => import('@/components/uni-popup/uni-popup.vue'),
    uniPopupDialog: () => import('@/components/uni-popup/uni-popup-dialog.vue'),
},

7、关于页面的跳转

  • 页面传参

uni.navigateTo()url参数为路径,其后可以带参数,用于页面传参,参数在新页面中onLoad钩子函数接收,如下示例。

uni.navigateTo({
    url: 'test?id=1&name=uniapp'
});
// 在test.vue页面接受参数
export default {
    onLoad: function (option) { //option为object类型,会序列化上个页面传递的参数
        console.log(option.id); //打印出上个页面传递的参数。
        console.log(option.name); //打印出上个页面传递的参数。
    }
}
  • 页面栈

框架以栈的形式管理当前所有页面,记住栈是先进后出的。

当用户按左上角返回按钮、安卓用户点击物理back按键时,触发uni.navigateBack,关闭当前页面,也就是将当前页面出栈,返回上一个页面,也就是返回页面栈中最上层的页面。当然uni.navigateBackOBJECT.delta可以控制返回页面栈中第几层的页面。可以用getCurrentPages()可以得到所有页面栈的对象。

uni.navigateTo(),保留当前页面,将当前页面入栈,再跳转新的页面。

uni.redirectTo(),关闭当前页面,不将当前页面入栈,再跳转新的页面。

uni.navigateTo(), uni.redirectTo() 只能打开非 tabBar 页面。

不能在 App.vue 里面进行页面跳转。如果用浏览器刷新整个页面,会清空当前页面栈。

8、关于消息弹窗的使用

跟服务端交互时肯定要用消息弹窗,比如报错时给出错误提示。uni-app框架封装几种消息弹窗的足迹。

uni.showToast() 显示消息提示弹窗。 uni.hideToast() 隐藏消息提示框。 uni.showModal() 显示模态弹窗,类似于标准 html 的消息框:alert、confirm。

10、关于加载动画的使用

uni.showLoading() 显示 loading 提示框, 需主动调用 uni.hideLoading() 才能关闭提示框。

uni.hideLoading() 隐藏 loading 提示框。

showToast 和 showLoading 是底层同一个,所以 showToast 和 showLoading 会相互覆盖,而 uni.hideLoading() 也会关闭 showToast,所以注意调用顺序。

11、关于使用nginx反向代理出现Invalid Host header问题

点开manifest.json文件,在h5中devServer选项中设置"disableHostCheck": true

12、关于使用formData格式请求接口

uni-app中是用uni.request()和服务端通信的。其中data参数是传给服务端的数据。

最终发送给服务器的数据是 String 类型,如果传入的 data 不是 String 类型,会被转换成 String。转换规则如下:

  • 对于 GET 方法,会将数据转换为 query string。例如 { name: 'name', age: 18 } 转换后的结果是 name=name&age=18。
  • 对于 POST 方法且 header['content-type'] 为 application/json 的数据,会进行 JSON 序列化。
  • 对于 POST 方法且 header['content-type'] 为 application/x-www-form-urlencoded 的数据,会将数据转换为 query string。

会发现uni.request()不能发送formData类型的数据给后端。如果要发送formData类型的数据,要用uni.uploadFile(),其作用是将本地资源上传到开发者服务器,客户端发起一个 POST 请求,其中 content-type 为 multipart/form-data。

formData参数是用uni.uploadFile()上传本地资源中其他额外的 form data数据。

uni.uploadFile()中几个必填的参数,如urlfilePathname可以随便填写。所以可用uni.uploadFile()发起数据格式为formData的接口请求了。

13、关于元素尺寸的获取

因为uni-app跨平台时不支持引入jQuery等操作 DOM 的插件,H5 平台可以通过条件编译的方式引入使用。所以在uni-app中最好不要引入jQuery,可以使用uni.createSelectorQuery()创建一个SelectorQuery对象实例来获取DOM信息。

例如要获取<view class="p-list"></view>的高度。

const _this = this;
uni.createSelectorQuery().select(".p-list").boundingClientRect(data => {
    _this.height = data.height;
}).exec();

14、关于区域上拉刷新功能实现

用uni-app中scroll-view内置组件和第三方uni-load-more组件实现。

注意scroll-view中要设定固定高度才可以生效。

<template>
  <view class="p-list">
      <scroll-view scroll-y="true" @scrolltolower="lower" :style="{height:height+'px'}">
          <view v-for="item in list">{{item}}</view>
          <uni-load-more :status="status" iconType="auto" v-show="isShow"></uni-load-more>
      </scroll-view>
  </view>
</template>
<script>
export default {
    data(){
        return {
            current:1,//页码
            list:[],
            height: 0,//列表区域高度
            isShow:false,
            status: 'loading',//加载更多的状态
        }
    },
    components: {
    	uniLoadMore: () => import('@/components/uni-load-more/uni-load-more.vue'),
    },
    mounted() {
        this.getViewHeight();
    },
    methods: {
    	getViewHeight() {
            const _this = this;
            uni.createSelectorQuery().select(".p-list").boundingClientRect(data => {
                _this.height = data.height;
            }).exec();
        },
        lower() {
            if (this.isShow) return;
            this.isShow = true;
            this.current++;
            this.getList();
        },
        getList(){
        	let data = new Object;
            data.current = this.current;
            data.size = 15;//每次加载条数
            API.getRenewRecord(data)
              .then(res => {
                  this.isShow = false;
                  if (res.code == 200) {
                      this.list = [...this.list, ...res.data]
                      if (res.data.length == 0) {
                          this.status = 'noMore';
                          this.isShow = true;
                      }
                  }
              })
              .catch(err => {
                  this.isShow = false;
              })
        }
    }
}
</script>

15、关于区域下拉刷新功能实现

如果页面头部有固定内容,下拉刷新只能用scroll-view内置组件实现。 注意scroll-view中要设定固定高度才可以生效。

<template>
  <view class="p-list">
      <scroll-view scroll-y refresher-enabled 
      	:refresher-triggered="triggered"
        :refresher-threshold="100" 
        @refresherrefresh="onRefresh" :style="{height:height+'px'}">
          <view v-for="item in list">{{item}}</view>
      </scroll-view>
  </view>
</template>
<script>
export default {
	data(){
    	return {
            current:1,//页码
            list:[],
            height: 0,//列表区域高度
            triggered:false,//下拉刷新状态,true开启下拉刷新,false结束下拉刷新
        }
    },
    mounted() {
        this.getViewHeight();
    },
    methods: {
    	getViewHeight() {
            const _this = this;
            uni.createSelectorQuery().select(".p-list").boundingClientRect(data => {
                _this.height = data.height;
            }).exec();
        },
        onRefresh() {
            if (this.triggered) return;
            this.triggered = true;
            this.current++;
            this.getList();
        },
        getList(){
            let data = new Object;
            data.current = this.current;
            data.size = 15;//每次加载条数
            API.getRenewRecord(data)
              .then(res => {
                  this.triggered = false;
                  if (res.code == 200) {
                      this.list = [...res.data,...this.list]
                  }
              })
              .catch(err => {
                  this.triggered = false;
              })
        }
    }
}
</script>

因为在微信浏览器中,下拉时整个页面会下滑,然后导致scroll-view的下拉刷新卡顿。用以下方法解决

先在pages.json中开启该页面的下拉刷新监听

{
   "pages":[
    	{
            "path": "testList",
            "style": {
                "enablePullDownRefresh": true
            }
        },
    ]
}

然后在页面中监听下拉刷新,一旦下拉刷新马上执行uni.stopPullDownRefresh把下拉刷新马上关闭。

onPullDownRefresh() {
     uni.stopPullDownRefresh()
},

同时在App.vue中,把下拉刷新的页面效果隐藏

<style>
    uni-page[data-page=websms] uni-page-refresh {
        display: none;
    }
</style>

16、IOS收起软键盘后页面不下滑的解决

<template>
    <view>
    	<input v-model.trim="value" @blur="handleblur"/>
    </view>
</template>
<script>
export default{
    methods: {
        handleblur() {
            uni.pageScrollTo({
                scrollTop: 0,
                duration: 0
            });
        },
    }
}
</script>

17、IOS6不能撑开display:flex嵌套使用的父级高度

在父级元素加上min-height属性。或者避免嵌套

18、关于自定义打包后的index.html页面

uni-app项目中默认是没有index.html,不像Vue项目默认带有index.html。那么要用CND引入第三方库,该怎么操作。

首先在根目录下,新建index.html文件

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <title>
            <%= htmlWebpackPlugin.options.title %>
        </title>
        <script>
            document.addEventListener('DOMContentLoaded', function() {
                document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
            })
        </script>
        <link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
    </head>
    <body>
        <noscript>
            <strong>Please enable JavaScript to continue.</strong>
        </noscript>
        <div id="app"></div>
        <!-- built files will be auto injected -->
    </body>
</html>

然后在manifest.json中添加如下配置,即可。

"h5":{
    "template": "index.html",
}

19、关于首屏优化的操作

开启摇树优化和预加载 然后在manifest.json中添加如下配置,即可。

"h5":{
    "optimization": {
        "prefetch": true,
        "preload": true,
        "treeShaking": {
            "enable": true
        }
    }
}

20、关于IOS首屏加载过慢的问题。

可以看这里

五、部署阶段

1、 H5发布

先配置

然后打包编译

最后打包生成的文件部署到服务器上。