[TOC]

条件编译

由于各个平台有起独特性,可以使用类似于C语言的条件编译语句,指定一段代码只在特定的环境中被编译。

格式:

//#ifdef %PLATFORM%
平台特有的API实现
//#endif

判断某个平台存在:

//#ifdef %PLATFORM%
需条件编译的代码
//#endif

判断某个平台不存在:

//#ifndef %PLATFORM%
需条件编译的代码
//#endif

%PLATFORM% 可取值如下:

平台 相关规范
APP-PLUS App HTML5+ App 规范
APP-PLUS-NVUE或APP-NVUE App nvue
H5 H5
MP-WEIXIN 微信小程序 微信小程序
MP-ALIPAY 支付宝小程序
MP-BAIDU 百度小程序
MP-TOUTIAO 字节跳动小程序
MP-QQ QQ小程序
MP-360 360小程序
MP 微信小程序/支付宝小程序/百度小程序/字节跳动小程序/QQ小程序/360小程序
QUICKAPP-WEBVIEW 快应用通用(包含联盟、华为)
QUICKAPP-WEBVIEW-UNION 快应用联盟
QUICKAPP-WEBVIEW-HUAWEI 快应用华为

支持的文件

  • .vue
  • .js
  • .css
  • pages.json
  • 各预编译语言文件,如:.scss、.less、.stylus、.ts、.pug

例如:

//#ifdef APP-PLUS
plus.push.addEventListener('click', function (msg) {
    var payload = null;
    var action = '';
    if (msg.payload) {
        if (typeof msg.payload === 'string') {
            payload = JSON.parse(msg.payload);
        }
        action = payload.action;
        if (action === 'open') {
            plus.webview.open(payload.url);
        }
    }
});
//#endif

在HTML或CSS中也可以使用条件编译:

<!-- #ifdef MP-WEIXIN -->
需条件编译的代码
<!-- #endif -->
/* #ifdef %PLATFORM% */
需条件编译的代码
/* #endif */

可以使用 || 连接多个平台,例如:

// #ifdef H5 || MP-WEIXIN
需条件编译的代码
// #endif

使用Vue进行开发

兼容支持

由于需要兼容多端,许多浏览器特性、Vue特性是不能使用的,支持情况如下:

支持:

  • 条件渲染(v-if、v-show)
  • 列表渲染(v-for)
  • 计算属性(computed)
  • 事件处理(v-on,@)、事件修饰符

不支持:

  • 所有的 BOM/DOM 都不能用
  • v-html 指令不能用,可以使用 rich-text组件 代替
  • Vue 过滤器(filter)
  • data 必须声明为返回一个初始数据对象的函数
  • 按键修饰符
  • 模板中不支持复杂的JavaScript表达式

事件处理

uni-app 支持大部分 Vue 中的事件,对部分事件进行了改写:

// 事件映射表,左侧为 WEB 事件,右侧为 ``uni-app`` 对应事件
{
  click: 'tap',
  touchstart: 'touchstart',
  touchmove: 'touchmove',
  touchcancel: 'touchcancel',
  touchend: 'touchend',
  tap: 'tap',
  longtap: 'longtap',
  input: 'input',
  change: 'change',
  submit: 'submit',
  blur: 'blur',
  focus: 'focus',
  reset: 'reset',
  confirm: 'confirm',
  columnchange: 'columnchange',
  linechange: 'linechange',
  error: 'error',
  scrolltoupper: 'scrolltoupper',
  scrolltolower: 'scrolltolower',
  scroll: 'scroll'
}

在 input 和 textarea 中 change 事件会被转为 blur 事件。

事件修饰符

  • stop 的使用会阻止冒泡,但是同时绑定了一个非冒泡事件,会导致该元素上的 catchEventName 失效!
  • prevent 可以直接干掉,因为uni-app里没有什么默认事件,比如 submit 并不会跳转页面
  • self 没有可以判断的标识
  • once 也不能做,因为uni-app没有 removeEventListener,虽然可以直接在 handleProxy 中处理,但非常的不优雅,违背了原意,暂不考虑

其他不支持的部分

模板中不支持复杂的JavaScript表达式

目前可以使用的有 + - * % ?: ! == === > < [] .

比如以下模板语法就不支持:

<view>{{ message.split('').reverse().join('') }}</view>

通常这种情况使用计算属性即可。

生命周期

应用程序生命周期

这是整个程序的生命周期函数,仅可在App.vue中监听,在其它页面监听无效。

  • onLaunch 当uni-app 初始化完成时触发(全局只触发一次)
  • onShow 当 uni-app 启动,或从后台进入前台显示
  • onHide 当 uni-app 从前台进入后台

页面生命周期

uni-app 支持如下页面生命周期函数:

  • onLoad 监听页面加载,其参数为上个页面传递的数据,参数类型为Object(用于页面传参)
  • onShow 监听页面显示
  • onReady 监听页面初次渲染完成
  • onHide 监听页面隐藏
  • onUnload 监听页面卸载
  • onPullDownRefresh 监听用户下拉动作,一般用于下拉刷新,参考示例
  • onReachBottom 页面上拉触底事件的处理函数
  • onShareAppMessage 用户点击右上角分享,仅微信小程序支持
  • onNavigationBarButtonTap 监听原生标题栏按钮点击事件,参数为Object,仅5+ App支持
  • onPageScroll 监听页面滚动,参数为Object

Vue 实例生命周期

跟正常开发 Vue 一样,由以下 Vue 的生命周期构成:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated

页面事件监听

pageScrollTo

pageScrollTo 可以滚动到指定页面指定位置,通常用于制作返回顶部,以及一些滑动特效。

uni.pageScrollTo({
  scrollTop: 0,
  duration: 300
});
  • scrollTop Number 必填,滚动到页面的目标位置(单位px)
  • duration Number 可选,滚动动画的时长,默认300ms,单位 ms

下拉刷新

首先,得在 page.json 中开启当前页的下拉刷新配置:

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

这样就可以在页面中监听其下拉事件了:

export default {
  onPullDownRefresh () {
    console.log('refresh');
    setTimeout(function () {
      uni.stopPullDownRefresh();
    }, 1000);
  }
}

:::info onPullDownRefresh 是跟 onLoad 同级的页面事件。 :::

下拉事件可以通过下拉页面触发,也可通过事件绑定触发,比如绑定一个按钮事件:

<template lang='pug'>
  .test
    button(@tap='refresh') refresh
</template>

<script>
export default {
  methods: {
    refresh () {
      uni.startPullDownRefresh();
    }
  }
}
</script>
  • uni.startPullDownRefresh() 开始刷新
  • uni.stopPullDownRefresh() 停止刷新

上拉加载更多

首先,得在 page.json 中开启当前页的触底距离,也可以不配置,默认为50:

{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "onReachBottomDistance": 50
      }
    }
  ]
}

在页面中监听其触底事件:

export default {
  onReachBottom () {
    console.log('get more data');
  }
}

封装了一个上拉加载更多的组件:

<template lang="pug">
scroll-view.wrap(scroll-y
  upper-threshold="0.01"
  @scrolltoupper='scrolltoupper'
  @scrolltolower='scrolltolower'
)
  .content
    .list
      .item(v-for='(item, key) in list' :key='key') {{item}}
  load-more(:loadingType='loadingType')
</template>

<script>
import throttle from 'lodash.throttle'
export default {
  data () {
    return {
      loadingType: 0,
      last: 20,
      list: Array.from({length: this.last}, (v, i) => `item${i+1}`)
    }
  },
  methods: {
    scrolltoupper: throttle(function () {
      if (this.loadingType) return
      this.loadingType = 1
      console.log('scrolltoupper');
    }, 2000),
    scrolltolower: throttle(function () {
      if (this.loadingType) return
      this.loadingType = 1
      setTimeout(() => {
        this.loadingType = 2
      }, 1000)
    }, 2000)
  }
}
</script>

<style scoped lang="stylus">
.wrap
  height: 100vh;
  .content
    .item
      font-size: 1.2em;
      padding: 1em;
      border-bottom: 1px dashed #ff0;
</style>

这是 load-more 组件的写法,参考了官方 Demo 的写法:

components/load-more.vue

<template>
    <view class="load-more">
        <view class="loading-img" v-show="loadingType === 1 && showImage">
            <view class="load1">
                <view :style="{background:color}"></view>
                <view :style="{background:color}"></view>
                <view :style="{background:color}"></view>
                <view :style="{background:color}"></view>
            </view>
            <view class="load2">
                <view :style="{background:color}"></view>
                <view :style="{background:color}"></view>
                <view :style="{background:color}"></view>
                <view :style="{background:color}"></view>
            </view>
            <view class="load3">
                <view :style="{background:color}"></view>
                <view :style="{background:color}"></view>
                <view :style="{background:color}"></view>
                <view :style="{background:color}"></view>
            </view>
        </view>
        <text class="loading-text" :style="{color:color}">{{loadingType === 0 ? contentText.contentdown : (loadingType === 1 ? contentText.contentrefresh : contentText.contentnomore)}}</text>
    </view>
</template>

<script>
    export default {
        name: "load-more",
        props: {
            loadingType: {
                //上拉的状态:0-loading前;1-loading中;2-没有更多了
                type: Number,
                default: 0
            },
            showImage: {
                type: Boolean,
                default: true
            },
            color: {
                type: String,
                default: "#777777"
            },
            contentText: {
                type: Object,
                default () {
                    return {
                        contentdown: "上拉显示更多",
                        contentrefresh: "正在加载...",
                        contentnomore: "没有更多数据了"
                    };
                }
            }
        },
        data() {
            return {}
        }
    }
</script>

<style>
    .load-more {
        display: flex;
        flex-direction: row;
        height: 80upx;
        align-items: center;
        justify-content: center;
    }

    .loading-img {
        height: 24px;
        width: 24px;
        margin-right: 10px;
    }

    .loading-text {
        font-size: 15px;
        color: #777777;
    }

    .loading-img>view {
        position: absolute;
    }

    .load1,
    .load2,
    .load3 {
        height: 24px;
        width: 24px;
    }

    .load2 {
        transform: rotate(30deg);
    }

    .load3 {
        transform: rotate(60deg);
    }

    .loading-img>view view {
        width: 6px;
        height: 2px;
        border-top-left-radius: 1px;
        border-bottom-left-radius: 1px;
        background: #777;
        position: absolute;
        opacity: 0.2;
        transform-origin: 50%;
        -webkit-animation: load 1.56s ease infinite;
    }

    .loading-img>view view:nth-child(1) {
        transform: rotate(90deg);
        top: 2px;
        left: 9px;
    }

    .loading-img>view view:nth-child(2) {
        -webkit-transform: rotate(180deg);
        top: 11px;
        right: 0px;
    }

    .loading-img>view view:nth-child(3) {
        transform: rotate(270deg);
        bottom: 2px;
        left: 9px;
    }

    .loading-img>view view:nth-child(4) {
        top: 11px;
        left: 0px;
    }

    .load1 view:nth-child(1) {
        animation-delay: 0s;
    }

    .load2 view:nth-child(1) {
        animation-delay: 0.13s;
    }

    .load3 view:nth-child(1) {
        animation-delay: 0.26s;
    }

    .load1 view:nth-child(2) {
        animation-delay: 0.39s;
    }

    .load2 view:nth-child(2) {
        animation-delay: 0.52s;
    }

    .load3 view:nth-child(2) {
        animation-delay: 0.65s;
    }

    .load1 view:nth-child(3) {
        animation-delay: 0.78s;
    }

    .load2 view:nth-child(3) {
        animation-delay: 0.91s;
    }

    .load3 view:nth-child(3) {
        animation-delay: 1.04s;
    }

    .load1 view:nth-child(4) {
        animation-delay: 1.17s;
    }

    .load2 view:nth-child(4) {
        animation-delay: 1.30s;
    }

    .load3 view:nth-child(4) {
        animation-delay: 1.43s;
    }

    @-webkit-keyframes load {
        0% {
            opacity: 1;
        }

        100% {
            opacity: 0.2;
        }
    }
</style>

注意到,这里没用使用图片,也没有使用字体图标,而是纯代码生成的一个加载更多动画。

全局引入:
main.js

import loadMore from './components/load-more.vue'
Vue.component('loadMore', loadMore)

页面滚动监听

通过 onPageScroll 监听页面滚动:

export default {
  onPageScroll (e) {
    console.log(e.scrollTop); // 页面在垂直方向已滚动的距离(单位px)
  }
}

可以使用防抖函数防止事件频繁调用:

import throttle from 'lodash.throttle'
export default {
  onPageScroll: throttle(function (e) {
    console.log(e.scrollTop);
  }, 2000)
}

页面分享

使用 onShareAppMessage 可设置页面分享,接收一个参数 obj,包括如下内容:

  • from String 转发事件来源。button:页面内转发按钮;menu:右上角转发菜单
  • target 如果 from 值是 button,则 target 是触发这次转发事件的 button,否则为 undefined

onShareAppMessage 需要设置一个返回值,用于自定义分享的标题、路径、图片,都是可选的。

  • title 默认为当前小程序名称
  • path 默认为当前页面 path ,若自定义必须是以 / 开头的完整路径
  • imageUrl 默认使用当前页面截图
export default {
  onShareAppMessage (e) {
    console.log(e);
    return {
      title: 'Hello world',
      path: '/',
      imageUrl: '/static/imgs/test.png'
    }
  }
}

001.png

scroll-view 滚动监听

在 scroll-view 组件中,提供了三个事件监听:

  • scrolltoupper 滑动到容器顶部触发的事件,距离由 upper-threshold 决定,默认 50
  • scrolltolower 滑动到容器底部触发的事件,距离由 lower-threshold 决定,默认 50
  • scroll 容器滚动时触发,接收一个 event 参数,可获取当前位置 event.detail = {scrollLeft, scrollTop, scrollHeight, scrollWidth, deltaX, deltaY} ```vue

```

以上代码,使用了 lodash.throttle 防止抖动。

参考资料