微信小程序笔记 – 扩展

API Promise化

API Promise化,指的是通过额外的配置,将官方提供的、基于回调函数的异步 API,
升级改造为基于 Promise 的异步 API,从而提高代码的可读性、维护性,避免回调地狱的问题

回调函数的缺点

默认情况下,小程序官方提供的异步API都是基于回调函数实现的,例如:网络请求的 API 需要按照如下的方式调用:

示例
wx.request({
    url: '',
    method: '',
    data: {},
    success: () => {}, // 请求成功的回调函数
    fail: () => {}, // 请求失败的回调函数
    complete: () => {} // 请求完成的回调函数
  })

缺点:容易造成回调地狱的问题,代码的可读性、维护性差

实现 API Promise化

安装

在小程序中,实现 API Promise化 主要依赖于 miniprogram-api-promise 这个第三方的 npm包。安装步骤如下:

发布地址

终端
npm install --save miniprogram-api-promise

构建

默认情况下,安装完 npm包 后默认放置的是 node_modules 中,
但是小程序无法去读取 node_modules 的内容。此时需要进行构建的方式去将 node_modules
中对应的包迁移到 miniprogram_npm 目录下,就可使用 npm 下载的 npm包

修改 project.config.json 对应配置

project.config.json
{
 // ...
  "setting": {
    // ...
    "packNpmManually": true,
    "packNpmRelationList": [
      {
        "packageJsonPath": "./package.json",
        "miniprogramNpmDistDir": "./"
      }
    ]
}

在小程序项目中进行 npm 包的构建

%title插图%num

在 miniprogram_npm 目录下就有了 miniprogram-api-proise 包

%title插图%num

使用

在 app.js 中去全局导入 miniprogram-api-proise 的内容,并将 Promise化的 api 挂载到 wx 顶级对象下的 p 对象

%title插图%num
app.js
/*
在小程序入口文件中,只需要调用一次 promisifyAll() 方法,
即可实现异步 API 的 Promise 化
*/
import {
  promisifyAll
} from 'miniprogram-api-promise'
// (wx 是小程序的顶级对象) 通过给 wx 挂载一个 p 的空对象,并将下标同样指向给定义的 wxp
// 因此 通过 wxp 的方式调用的 API,等同于调用 wx 中 p 对象的方法
const wxp = wx.p = {};
// 将 Promise化的 API 挂载到对应对象中
/*
  promisifyAll() 会将参数1 中 wx 对象的所有 API 进行 Promise化
  并最后挂载到 参数2 下 用户创建的对象上
*/
promisifyAll(wx, wxp);
使用 Promise化 API

通过 app.json 进行全局导入 Promise 化的 API,将该对象放置在了 wx 顶级对象的 p 对象中。
所有页面都可直接通过 wx.p 使用 Promise 化的 API

若使用时提示报错,需要在 本地设置 中启用 “将 JS 编译成 ES5”

案例演示

使用 Promise化的 API 发起一次网络请求,并将返回的结果赋值在页面中

index.wxml
<view class="container">
  <view>
    <image src="{{img}}" mode=""/>
  </view>
  <button bind:tap="getImg">发起图片申请</button>
</view>
index.js
Page({
  data: {
     img:''
  },
  async getImg() {
    const {
      data: res
    } = await wx.p.request({
      url: 'https://image.anosu.top/pixiv/json',
      data: {},
    });
    this.setData({
      img:res[0].url
    })
  }
})
app.js
import {
  promisifyAll
} from 'miniprogram-api-promise'
const wxp = wx.p = {};
promisifyAll(wx, wxp);

App({})

效果展示

%title插图%num
全局数据共享
介绍

全局共享

全局数据共享 (又叫:状态管理) 是为了解决组件之间数据共享的问题。开发中常用的全局数据共享方案有:Vuex、Redux、Mobx 等

%title插图%num

如若没有全局共享的数据,组件之间的传递会变得臃肿和麻烦。需要不断通过相邻的节点传递到最终目标

小程序的全局共享

在小程序中,可使用 mobx-miniprogram 配合 mobx-miniprogram-bindings 实现全局数据共享,其中:

  • mobx-miniprogram 用来创建 Store 实例对象
  • mobx-miniprogram-bindings 用来把 Store 中的共享数据或方法,绑定到组件或页面中使用
%title插图%num
安装 MobX相关包

下载包

MobX 相关的包安装完毕后,需要删除原来的 miniprogram_npm 目录后,重新构建 npm

终端
npm install --save mobx-miniprogram@4.13.2 mobx-miniprogram-bindings@1.2.1

构建包

需要留意 project.config.json 文件需要进行构建前的必要改动。参考之前步骤

%title插图%num
创建 MobX实例

创建目录结构

在小程序根目录中,可以创建一个 Store 目录,用来放置 Mobx实例

%title插图%num

引入 Mobx实例对象

store.js
// 创建 Store 实例对象
import { observable } from 'mobx-miniprogram';
// 导出 MobX 的 Store 实例
export const store = observable({})

写入共享数据

通过用 get 的修饰符的函数调用时无需加括号,以变量方式调用会返回结果,实现计算属性的效果。该值是只读的

store.js
// 创建 Store 实例对象
import {
  observable
} from 'mobx-miniprogram';
// 导出 MobX 的 Store 实例
export const store = observable({
  numA: 1,
  numB: 2,
  // 计算属性
  get sum() {
    return this.numA + this.numB;
  }
});

修改共享数据

一般外界不允许直接去修改 store 中的数据,可使用内置 action(fn) 函数去修改 MobX实例 中存放的数据字段的值

  • observable({}) 用来创建 MobX 实例对象,action(fn) 方法用来修改创建的数据字段
store.js
// 创建 Store 实例对象
import {
  action,
  observable
} from 'mobx-miniprogram';
// 导出 MobX 的 Store 实例
export const store = observable({
  numA: 1,
  numB: 2,
  get sum() {
    return this.numA + this.numB;
  },
  // actions 方法,用来修改 store 中的数据
  updateNum1: action(function (step) {
    this.numA += step;
  }),
  updateNum2: action(function (step) {
    this.numB += step
  }),
});
将 Store 绑定至页面

MobX 共享数据导入到页面

通过导入使用 mobx 创建的 store 对象,和 mobx-miniprogram-bindingscreateStoreBindings 方法,实现绑定页面操作

该方法相当于直接将 store 中存放的字段和方法挂载在 页面的对应位置 (例如:this.data 对应 fields 中的字段)

JavaScript
import { createStoreBindings } from 'mobx-miniprogram-bindings';
import { store } from '../../store/store';

Page({
  // 生命周期 - 页面加载
  onLoad: function (options) {
    // 将 mobX 数据绑定到页面上
    this.storeBindings = createStoreBindings(this, {
      // 数据源
      store,
      // 需要绑定的字段
      fields: ['numA', 'numB', 'sum'],
      // 需要绑定的方法
      actions: ['updateNum1']
    })
  },
  // 生命周期 - 页面卸载
  onUnload: function () {
    // 清理 MobX绑定到页面的数据
    this.storeBindings.destroyStoreBindings();
  },
})

在页面上使用 MobX 数据

通过 mobx-miniprogram-bindingscreateStoreBindings 方法导入的 MobX 共享数据,直接可以通过调用常规数据来调用

  • createStoreBindings 负责将 MobX 创建的 store 数据导入到 wx 页面实例中,它传入两个参数,
    一个是挂载点 this;一个是对象,对象里面需要填入 数据源 和 字段(fields)、方法(actions)
  • 使用 store 中的成员,如果是 字段,直接使用 Mustache语法 即可,
    如果是 方法,可以为某些元素绑定事件,调用映射过来的方法即可
Vue HTML
<view>
  {{numA}} + {{numB}} = {{sum}}
</view>
<van-button type="primary" bindtap="btnHandler1" data-step="{{1}}">
numA+1
</van-button>
<van-button type="danger" bindtap="btnHandler1" data-step="{{-1}}">
numA-1
</van-button>
JavaScript
import {
  createStoreBindings
} from 'mobx-miniprogram-bindings';
import {
  store
} from '../../store/store';

Page({
  onLoad: function (options) {
    // 将 mobX 数据绑定到页面上
    this.storeBindings = createStoreBindings(this, {
      // 数据源
      store, 
      // 需要绑定的字段
      fields: ['numA', 'numB', 'sum'],
      // 需要绑定的方法
      actions: ['updateNum1']
    })
  },
  btnHandler1(e){
    // 调用 MobX 传入的方法
    this.updateNum1(e.target.dataset.step)
  }
})

效果展示

%title插图%num
将 Store 绑定至组件

相关文档

MobX 共享数据导入到组件

通过导入使用 mobx 创建的 store 对象,和 mobx-miniprogram-bindingsstoreBindingsBehavior 方法,实现绑定到组件操作

  • storeBindingsBehavior 对象导入到组件实例的 behaviors 数组中;
  • storeBindings 传入对应的 数据源、fields 字段,actions 方法
示例
import {storeBindingsBehavior} from 'mobx-miniprogram-bindings';
import {store} from '../../store/store';

Component({
  // 通过 storeBindingsBehavior 来实现自动绑定
  behaviors:[storeBindingsBehavior],
  storeBindings:{
    // 数据源
    store,
    // 指定要绑定的字段数据
    fields:{
      // 三种写法
      numA:()=>store.numA,
      numB:(store)=>store.numB,
      sum:'sum'
    },
    // 指定要绑定的方法
    actions:{
      // 可自定义指向 store 的方法
      updateNum2:'updateNum2'
    }
  }
})

在组件上使用 MobX 数据

  1. 创建组件,并在 app.json 文件的 usingComponents 下注册并命名该组件。在组件中引入 store 数据。
  2. 通过 mobx-miniprogram-bindings 提供的 storeBindingsBehavior 导入到 组件实例的 behaviors 数组中。
  3. 通过 storeBindings 方法导入 MobX 创建的 store 中需要的数据。可按需要填写 数据源、字段、方法的参数
app.json
{
    // ...
    "usingComponents": {
      "test":"/Components/test/test"
    }
}
test.js
import {storeBindingsBehavior} from 'mobx-miniprogram-bindings';
import {store} from '../../store/store';

Component({
  // 通过 storeBindingsBehavior 来实现自动绑定
  behaviors:[storeBindingsBehavior],
  storeBindings:{
    // 数据源
    store,
    // 指定要绑定的字段数据
    fields:{
      // 三种写法 左边的变量可随意命名
      num_A:()=>store.numA,
      numB:(store)=>store.numB,
      sum:'sum'
    },
    // 指定要绑定的方法 左边的变量可随意命名
    actions:{
      updateNum2:'updateNum2'
    }
  },
  methods:{
    // 组件中使用 MobX 传入的方法
    btnHandler2(e){
       this.updateNum2(e.target.dataset.step);
    }
  }
})
test.wxml
<view class="myComponents">
  <view>
  {{num_A}} + {{numB}} = {{sum}}
</view>
<van-button bindtap="btnHandler2" data-step="{{1}}">numB加1</van-button>
<van-button bindtap="btnHandler2" data-step="{{-1}}">numB减1</van-button>
</view>

效果展示

%title插图%num

由于是共享数据,值会同步更新
蓝色区域为组件区域

使用 MobX 注意事项

相关文档

不要使用 SetData 修改 MobX 字段

要更新 MobX 创建的 store 的字段数据,不应该使用 this.setData(),它不会改变 store 存储的对应值。
正确做法是 页面或组件 直接调用 Mobx 中自带的 actions 方法去修改对应的 字段数据;

MobX 会自行检查自己的 字段是否发生变化,如果变化会调用 setData 渲染界面

从页面或组件的 .js 中使用 setData 修改值,只是修改了页面的 data 数据。并没有修改 MobX 中数据

错误例子
import {storeBindingsBehavior} from 'mobx-miniprogram-bindings';
import {store} from '../../store/store';

Component({
  behaviors:[storeBindingsBehavior],
  storeBindings:{
    store,
    fields:{
      numB:'numB',
    },
    actions:{
      updata:'updata'
    }
  },
  methods:{
    updataMobXdata(){
      // 不能直接修改 MobX 传入的值
      this.setData({
        numB: this.data.numB + 1
      })
  }
})

MobX 会检查字段的值是发生的变化,会自行调用微信小程序的 this.setData() 把更新后的值渲染到页面

正确例子
import {storeBindingsBehavior} from 'mobx-miniprogram-bindings';
import {store} from '../../store/store';

Component({
  behaviors:[storeBindingsBehavior],
  storeBindings:{
    store,
    fields:{
      numB:'numB'
    },
    actions:{
      updata:'updata'
    }
  },
  methods:{
    updataMobXdata(){
      this.updata({
        numB: this.data.numB + 1
      })
    }
  }
})
store.js
import {
  observable,
  action
} from 'mobx-miniprogram';

export const store = observable({
  numA: 1,
  numB: 2,
  get sum() {
    return this.numA + this.numB;
  },
  // actions 方法,用来修改 store 中的数据
  updata: action(function (fn) {
    this.numB = fn.numB ?? this.numB;
    this.numA = fn.numA ?? this.numA;
  }),
})

错误示范

%title插图%num

使用 setData 并没有全局同步更新

正确示范

%title插图%num

使用 MobX 提供的 actions 方法实现同步更新

分包
介绍

分包介绍

分包指的是把一个完整的小项目程序,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载

%title插图%num

分包的优势

对小程序进行分包的好处主要有以下两点:

  • 可以优化小程序首次启动的下载时间
  • 在多团队共同开发时可以更好的解耦操作

分包前项目的构成

分包前,小程序项目中所有的页面资源都被打包到了一起,导致整个项目体积过大,影响小程序首次启动的下载时间

%title插图%num

分包后的项目构成

分包后,小程序项目由一个主包 + 多个分包组成:

  • 主包:一般只包含项目的启动页面或者 TabBar页面,以及所有分包都需要用到的一些公共资源
  • 分包:只包含和当前分包有关的页面和私有资源
%title插图%num

分包的加载规则

在小程序启动时,默认会下载主包启动主包内页面

  • tabBar 页面需要放在主包中

当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示

  • 非 tabBar 页面可以按照功能的不同,划分为不同的分包之后,进行按需下载

分包的体积限制

目前小程序分包的大小有以下两个限制:

  • 整个小程序所有的分包大小不超过 16M (主包 + 分包)
  • 单个分包 / 主包 大小不能超过 2M
配置分包

分析结构

根据小程序项目中文件夹来划分主包和分包

%title插图%num

修改配置文件

app.jsonsubpackages 节点中声明分包的结构,root 为分包的存放目录,page 为页面,name 为别名

app.json
{
  // 主包的所有资源
  "pages": [
    "pages/index/index"
  ],
  // 通过 subpackage 节点 声明分包的结构
  "subPackages": [
    {
      // 第一个分包的根目录
      "root": "packageA",
      // 当前分包下 所有页面的相对存放路径
      "pages": [
        "pages/cat",
        "pages/dog"
      ]
    },
    {
      // 第二个分包的根目录
      "root": "packageB",
      // 分包的别名
      "name": "pack2",
      // 当前分包下 所有页面的相对存放路径
      "pages": [
        "pages/apple",
        "pages/banana"
      ]
    }
  ]
}
创建分包

生成分包

在 app.json 的 subpackages 中创建分包对象后,开发者工具会自动创建对应的 文件目录。可以创建多个分包

%title插图%num

创建别名

在每一个分包对象中,使用 name 设置别名,设置别名是为了方便后续的管理操作中查看对应包的状态

JSON
{
  "pages": [
    "pages/index/index"
  ],
  "subPackages": [
    {
      "root":"pkgA",
      "name":"pk1",
      "pages":[
        "pages/cat/cat",
        "pages/dog/dog"
      ]
    },
    {
      "root":"pkgB",
      "name":"pk2",
      "pages":[
        "pages/apple/apple",
        "pages/banana/banana"
      ]
    }
  ]
}

查看包体积

小程序允许包的体积限制在 16M,每个分包限制在 2M,通过小程序的 详情 → 本地代码 中可以查看当前体积

%title插图%num
普通分包的原则

打包原则

  • 小程序会按 subpackages 的配置进行分包,subpackages 之外的目录被打包到主包中
  • 主包也可以有自己的 pages (即最外层的 pages 字段)
  • tabBar 页面必须在主包内
  • 分包之间不能互相嵌套

引用原则

  • 主包无法引用分包内的私有资源
  • 分包之间不能相互引用私有资源
  • 分包可以引用主包内的公共资源
独立分包

介绍

独立本质上分包也是分包,只不过它比较特殊,它可以独立于主包和其他分包而单独运行

%title插图%num

独立分包不用依赖主包,也可以独立执行,适用于登录页面或广告页面

独立分包和普通分包的区别

  • 普通分包必须依赖于主包才能运行
  • 独立分包可以在不下载主包的情况下独立运行

独立分包的应用场景

开发者可以按需,将某些具有一定功能独立性的页面配置到独立分包中,原因如下:

  • 当小程序从普通分包页面启动时,需要首先下载主包
  • 而独立分包不依赖主包即可运行,可以很大程度上提升分包页面的启动速度

当只需要执行分包的独立性业务,而不需要运行主包。即可设置该分包为独立分包
* 一个小程序可以设置多个独立分包 *

配置独立分包

为普通分包的对象属性设置 independent : true 即可为该分包声明为独立分包

JSON
{
  "pages": [
    "pages/index/index"
  ],
  "subPackages": [
    // ...
    {
      "root":"pkgB",
      "name":"pk2",
      "pages":[
        "pages/apple/apple",
        "pages/banana/banana"
      ],
      // 通过此节点 声明当前 pkgB 分包为独立分包
      "independent": true
    }
  ]
}
独立分包的原则

打包原则

同上

引用原则

独立分包和普通分包以及主包之间,是相互隔绝的,不能相互引用彼此的资源。例如:

  • 主包无法引用独立分包内的私有资源
  • 独立分包之间,不能相互引用私有资源
  • 独立分包和普通分包之间,不能相互引用私有资源
  • 注意:独立分包中不能引用主包内的公共资源
分包预下载

介绍

分包预下载指的是在小程序的某个页面时,由框架自动预下载可能需要的分包,从而提升进入后续分包页面时的启动速度。

在用户可能会访问对应分包的页面上,由框架自动预先下载需要的分包
如果用户真的使用了该分包,则省去了等待下载时间,从而提升进入后续分包页面时的启动速度。

配置分包预下载

预下载分包的行为,会在进入指定的页面时触发。在 app.json 中,使用 preloadRule 节点定义分包的预下载规则

  • network 设置指定网络模式下进行预下载
  • packages 设置需要预下载的分包,可以用 root 路径 或者 name 别名
JSON
{
  "pages": [
    "pages/index/index"
  ],
  // 分包的预下载规则
  "preloadRule": {
    // 触发分包预下载的页面路径
    "pages/index/index":{
      // network 表示在指定的网络下进行预下载
      // 默认值为 wifi 可选值 all (不限制网络) wifi (仅 wifi 下载)
      "network": "all",
      // packages 表示进入页面后预先下载的哪些分包 可以为包的 root 或者 name
      "packages": ["pkgA"]
    }
  },
  "subPackages": [
    {
      "root":"pkgA",
      "name":"pk1",
      "pages":[
        "pages/cat/cat",
        "pages/dog/dog"
      ]
    }
  ]
}

效果展示

%title插图%num

设置分包预下载的页面当进入时,
会执行预下载分包操作

分包预下载的限制

同一个分包中的页面享有共同预下载大小限额 2M,例如:

%title插图%num
自定义 tabBar

为自己的 tabBar 栏目添加额外的效果,例如动态显示消息气泡的数值

实现步骤

介绍

自定义 tabBar 分为 3 大步骤,分别是:

  • 配置信息
  • 添加 tabBar 代码文件
  • 编写 tabBar 代码

详细步骤,可以参考小程序官方给出的文档:自定义tabBar

前置操作

声明自定义 tabBar

微信开发者工具中若想创建自定义 tabBar,需要在 app.json 中的 tabBar 项指定 custom 字段为 true
同时其余 tabBar 相关配置也补充完整

所有 tab 页的 json 里需声明 usingComponents 项,也可以在 app.json 全局开启

注意:为了保证低版本兼容以及区分哪些页面是 tab 页,tabBar 的相关配置项需完整声明,
但这些字段不会作用于自定义 tabBar 的渲染

app.json
{
  "pages": [
    "pages/index/index",
    "pages/message/message",
    "pages/call/call"
  ],
  "tabBar": {
    "custom": true,
    "list": [{
      "pagePath": "pages/index/index",
      "text": "首页",
      "iconPath": "images/tabs/home.png",
      "selectedIconPath": "images/tabs/home-active.png"
    },{
      "pagePath": "pages/message/message",
      "text": "消息",
      "iconPath": "images/tabs/message.png",
      "selectedIconPath": "images/tabs/message-active.png"
    },
    {
      "pagePath": "pages/call/call",
      "text": "联系我们",
      "iconPath": "images/tabs/contact.png",
      "selectedIconPath": "images/tabs/contact-active.png"
    }
  ]
  }
}

创建自定义 tabBar 代码目录

在代码根目录中添加入口文件(必须命名为下方文件名):

custom-tab-bar/index.js
custom-tab-bar/index.json
custom-tab-bar/index.wxml
custom-tab-bar/index.wxss

右键新建 Component 组件,并命名为 index,会自动生成对应项目依赖文件

%title插图%num

设置完成后,会自动将该组件渲染到页面的底部 tabBar 区域展示

样式渲染

使用 vant 提供的 Tabbar 标签样式:官方 tabbar标签样式文档安装 vant组件库

引入

在 app.json 或 index.json 中引入组件

app.json
"usingComponents": {
  "van-tabbar": "@vant/weapp/tabbar/index",
  "van-tabbar-item": "@vant/weapp/tabbar-item/index"
}

配置

导入 vant组件库 提供的演示模板,并关联 active 和 组件的方法到 js 文件,实现基本交互

custom-tab-bar/index.wxml
<van-tabbar active="{{ active }}" bind:change="onChange">
  <van-tabbar-item icon="home-o">标签</van-tabbar-item>
  <van-tabbar-item icon="search">标签</van-tabbar-item>
  <van-tabbar-item icon="friends-o">标签</van-tabbar-item>
  <van-tabbar-item icon="setting-o">标签</van-tabbar-item>
</van-tabbar>
custom-tab-bar/index.js
Component({
  data: {
    active: 0
  },
  methods: {
    onChange(e) {
      this.setData({
        active: e.detail
      });
    }
  }
})

效果展示

%title插图%num

自定义图标

可以通过 slot 自定义图标,其中 icon slot 代表未选中状态下的图标,icon-active slot 代表选中状态下的图标 相关文档

custom-tab-bar/index.wxml
<!--custom-tab-bar/index.wxml-->
<van-tabbar active="{{ active }}" bind:change="onChange">
  <!-- info 是悬浮在右上角的气泡消息样式 -->
  <van-tabbar-item info="3">
    <image
      slot="icon"
      src="/images/tabs/home.png"
      mode="aspectFit"
      style="width: 30px; height: 18px;"
    />
    <image
      slot="icon-active"
      src="/images/tabs/home-active.png"
      mode="aspectFit"
      style="width: 30px; height: 18px;"
    />
    首页
  </van-tabbar-item>
  <van-tabbar-item icon="search">标签</van-tabbar-item>
  <van-tabbar-item icon="friends-o">标签</van-tabbar-item>
  <van-tabbar-item icon="setting-o">标签</van-tabbar-item>
</van-tabbar>

效果展示

%title插图%num

遍历渲染 TabBar

按照 app.json 中定义的 list 项要求,以 wx:for 方式遍历的方式去让 vant组件库 铺设 自定义tabBar标签栏

  • wx:for 遍历 list 对象进行铺设,并使用 style 属性修改 图标大小、字体大小 (注意组件样式隔离)
  • 在 wxss 中使用审查元素查看 vant组件库 中查找对应的 CSS变量 去覆盖原参数,实现去除下边距
  • 在 js 中为 message 对象里添加 info 属性,方便 wx:for 遍历wxml 时做 渲染判断
custom-tab-bar/index.wxml
<van-tabbar active="{{ active }}" bind:change="onChange">
  <van-tabbar-item wx:for="{{list}}" wx:key="index" 
  info="{{item.info ? item.info : ''}}">
    <image
      slot="icon"
      src="{{item.iconPath}}"
      mode="aspectFit"
      style="width: 25px; height: 25px;"
    />
    <image
      slot="icon-active"
      src="{{item.selectedIconPath}}"
      mode="aspectFit"
      style="width: 25px; height: 25px;"
    />
    {{item.text}}
  </van-tabbar-item>
</van-tabbar>
custom-tab-bar/index.js
Component({
 // 需要关闭组件隔离
 options: {
    styleIsolation: 'shared'
  },
  data: {
    active: 0,
    // 从 app.json 中的 tabBar 项目的 list 粘贴过来的
    /*
       app.json 中的 list 的路径不能直接给 当前 编程式导航使用
       需要给前面加 /
    */
    "list": [{
      "pagePath": "/pages/index/index",
      "text": "首页",
      "iconPath": "/images/tabs/home.png",
      "selectedIconPath": "/images/tabs/home-active.png"
    }, {
      "pagePath": "/pages/message/message",
      "text": "消息",
      "iconPath": "/images/tabs/message.png",
      "selectedIconPath": "/images/tabs/message-active.png",
      info: 2
    },
    {
      "pagePath": "/pages/call/call",
      "text": "联系我们",
      "iconPath": "/images/tabs/contact.png",
      "selectedIconPath": "/images/tabs/contact-active.png"
    }
  ]
  },
  methods: {
    onChange(e) {
      this.setData({
        active: e.detail
      });
    }
  }
})
custom-tab-bar/index.wxss
/* 覆盖 vant 组件 CSS变量 样式的值 去掉下边距 */
.van-tabbar-item {
  --tabbar-item-margin-bottom: 0;
}

效果展示

%title插图%num
动态改变样式

实现气泡提示同步 sum值

通过 MobX 中的全局共享数据存储 数值,并使用监听器去动态监听其变化来执行 tabBar 重新渲染的操作

>>> 前置代码继承 MobX案例的代码

custom-tab-bar/index.js
import {
  storeBindingsBehavior
} from 'mobx-miniprogram-bindings';
import {
  store
} from '../store/store'
Component({
  options: {
    styleIsolation: 'shared'
  },
  behaviors: [storeBindingsBehavior],
  storeBindings: {
    store,
    fields: {
      // 从全局 MobX 数据中获取 sum 值
      sum: 'sum'
    }
  },
  // 监听 sum 值的变化
  observers: {
    // 当 sum 值发生变化时 执行回调函数
    "sum": function (val) {
      this.setData({
        // 将新值 赋值到 list[1].info 成员中
        'list[1].info': val
      })
    }
  },
  data: {
    active: 0,
    "list": [{
        "pagePath": "/pages/index/index",
        "text": "首页",
        "iconPath": "/images/tabs/home.png",
        "selectedIconPath": "/images/tabs/home-active.png"
      }, {
        "pagePath": "/pages/message/message",
        "text": "消息",
        "iconPath": "/images/tabs/message.png",
        "selectedIconPath": "/images/tabs/message-active.png",
        info: 0
      },
      {
        "pagePath": "/pages/call/call",
        "text": "联系我们",
        "iconPath": "/images/tabs/contact.png",
        "selectedIconPath": "/images/tabs/contact-active.png"
      }
    ]
  },

  /**
   * 组件的方法列表
   */
  methods: {
    onChange(e) {
      this.setData({
        active: e.detail
      });
    }
  }
})

效果展示

%title插图%num

实现 TabBar 的页面切换

通过 tabBar标签栏 提供的 onChange 事件;当触发该事件时,使用 wx.switchTab() 进行对应路径跳转

%title插图%num

由于存在页面渲染不同步的问题,应该将 active 属性设置为 MobX 共享数据

通过 MobX 全局管理并负责 vant组件库设置的选中和切换的 active 属性,可以解决这个问题

左侧为BUG展示:使用 setDate() 方法去改变 active,并用 wx.switchTab() 执行页面切换后,下方 tabBar 按钮的选中不同步

将 vant组件库的 tabBar标签栏 监听的 active 变量放置在 MobX 数据的 字段中

store.js
import {
  observable,
  action
} from 'mobx-miniprogram';

export const store = observable({
  // 作为 vant组件中 active 属性的存值 
  activeTabBarIndex: 0,
  // 改变 active 的方法
  updateActiveTabBarIndex: action(function (index) {
    this.activeTabBarIndex = index;
  })

})
custom-tab-bar/index.js
import {
  storeBindingsBehavior
} from 'mobx-miniprogram-bindings';
import {
  store
} from '../store/store'
Component({
  behaviors: [storeBindingsBehavior],
  storeBindings: {
    store,
    fields: {
      sum: 'sum',
      // 映射 MobX 到 this.data 的字段为 active
      active: 'activeTabBarIndex'
    },
    actions: {
      //  映射 MobX 到 this.methods 的字段为 updateActive
      updateActive: 'updateActiveTabBarIndex'
    }
  },
  observers: {
    "sum": function (val) {
      this.setData({
        'list[1].info': val
      });
    }
  },
  // ...
  methods: {
    onChange(e) {
      // 通过 MobX 提供的方法改变对应全局属性
      this.updateActive(e.detail);
      // 跳转到对应 tabBar页面
      wx.switchTab({
        url: this.data.list[e.detail].pagePath,
      });
    }
  }
})

效果展示

%title插图%num

替换 TabBar栏 选中颜色

通过 vant组件库提供的样式自定义属性,修改对应的颜色值 >>>更多自定义样式属性介绍

参数 说明 类型 默认值
active 当前选中标签的索引 number
fixed 是否固定在底部 boolean true
placeholder 固定在底部时,是否在标签位置生成一个等高的占位元素 boolean false
border 是否展示外边框 boolean true
z-index 元素 z-index number 1
active-color 选中标签的颜色 string #1989fa
inactive-color 未选中标签的颜色 string #7d7e80
safe-area-inset-bottom 是否为 iPhoneX 留出底部安全距离 boolean true

代码演示

custom-tab-bar/index.wxml
<van-tabbar
 active="{{ active }}"
 bind:change="onChange"
 active-color="#13A7A0">
  <van-tabbar-item wx:for="{{list}}" wx:key="index" 
  info="{{item.info ? item.info : ''}}">
    <image
      slot="icon"
      src="{{item.iconPath}}"
      mode="aspectFit"
      style="width: 25px; height: 25px;"
    />
    <image
      slot="icon-active"
      src="{{item.selectedIconPath}}"
      mode="aspectFit"
      style="width: 25px; height: 25px;"
    />
    {{item.text}}
  </van-tabbar-item>
</van-tabbar>

效果展示

%title插图%num