一、自定义组件
注:小程序与vue的最大区别就是,vue的所有页面都是由组件构成的(只是有父组件和子组件之分),小程序的页面是由 页面+自定义组件 构成的
在小程序中,页面只是最外面的那一层,比重并不大!
页面和组件的区别:
页面是一个页面的大容器,相当于父组件,需要定义在app.json中
组件是页面的零部件,相当于子组件,不需要定义在app.json中
1.组件的创建与引用
1.1 创建组件
本质上和页面的文件(数量和类型)是一样的
1.2 引用组件
1.3 局部引用组件
1.4 全局引用组件
1.5 全局引用 VS 局部引用
1.6 组件和页面的区别
页面和组件的数据都是定义在data中的,但是页面的方法是直接定义,组件的方法是定义在methods中!
2.组件的样式
2.1 组件样式隔离
组件样式隔离的注意点
.class 属性值 {
属性:值;
}
app.wxss的class选择器(仅限class选择器)的样式只对页面有效,对组件无效
2.2 修改组件的样式隔离选项
styleIsolation 的可选值
可选值 | 默认值 | 描述 |
---|---|---|
isolated | 是 | 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(形成隔离) |
apply-shared | 否 | 表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面(页面——>组件) |
shared | 否 | 表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了 apply-shared 或 shared 的自定义组件(页面<——>组件) |
3.组件的数据、方法和属性
3.1 data 数据
3.2 methods 方法
事件处理函数:用于和事件绑定
自定义方法:就是普通的函数,用_开头
3.3 properties 属性
和vue的prop类似,但是properties是可读可写的,和data一样
3.4 data 和 properties 的区别
3.5 使用 setData 修改 properties 的值
4.数据监听器
4.1 什么是数据监听器
注意:方法的参数值就是最新的字段值,这是默认获取的!!!
4.2 数据监听器的基本用法:监听属性的变化
可以监听data里面某些数据的变化,如果发生了变化就进行一系列逻辑:
4.3 监听对象属性的变化
和监视普通属性其实没什么区别
5.数据监听器 - 案例
5.1 案例效果
注:这里的fullColor目前只是一个普通的字符串,后面可以用observers监视去实现一个计算属性的效果!
5.2 渲染 UI 结构
5.3 定义 button 的事件处理函数
5.4 监听对象中指定属性的变化
利用监视实现了计算属性
5.5 监听对象中所有属性的变化:通配符
6.纯数据字段
6.1 什么是纯数据字段
不需要在页面上去显示的数据,只是运算的桥梁,就是纯数据字段
6.2 使用规则
6.3 使用纯数据字段改造数据监听器案例
7.组件的生命周期
7.1 组件全部的生命周期函数
7.2 组件主要的生命周期函数
attached最有用,类似于页面的onLoad生命周期函数!
7.3 lifetimes 节点
8.组件所在页面的生命周期
8.1 什么是组件所在页面的生命周期
8.2 pageLifetimes 节点
8.3 生成随机的 RGB 颜色值
Math.floor()函数:向下取整,最终得到的是[0,255]之间的数字
show:用这个生命周期是因为这里是否生成随机颜色,是依赖于页面是否被展示
最终实现的效果:每次切到这个页面的时候(或者是最开始访问的时候),就会获取新的颜色!
9.插槽
9.1 什么是插槽
即组件作为一个插槽函数,等待页面传入结构
9.2 定义单个插槽
9.3 启用多个插槽
9.4 定义多个插槽
test4.wxml
<view>
<slot name="before"></slot>
<view>这里是组件的内部结构</view>
<slot name="after"></slot>
</view>
9.5 使用多个插槽
home.wxml
注意:这里不一定用view,任何标签都可以!
<my-test4>
<view slot="before">这是通过插槽填充的内容</view>
<view slot="after">~~~~~~~</view>
</my-test4>
10.父子组件之间的通信
10.1 父子组件之间通信的 3 种方式
注:这里不仅仅是组件,页面也可以是父组件!
10.2 属性绑定:父到子
10.3 事件绑定:子到父
注:sync是我们自定义的属性,可以任意,但是下面要用到
代码:
test5.js
methods: {
addCount() {
this.setData({
count: this.properties.count + 1
})
// 触发自定义事件,将数值同步给父组件
this.triggerEvent('sync', { value: this.properties.count })
}
}
home.js
syncCount(e) {
// console.log('syncCount')
// console.log(e)
// console.log(e.detail.value)
this.setData({
count: e.detail.value
})
},
10.4 获取组件实例
test5.js
methods: {
addCount() {
this.setData({
count: this.properties.count + 1
})
// 触发自定义事件,将数值同步给父组件
this.triggerEvent('sync', { value: this.properties.count })
}
}
home.js
getChild() {
const child = this.selectComponent('#cA') //必须获取的是此页面应用的组件标签(test5组件)
console.log(child)
//可以用下面的方法,给子组件赋值,注意不能用this,只能用child
// child.setData({
// count: child.properties.count + 1
// })
child.addCount() //调用子组件的addCount方法
}
home.wxml
<my-test5 count="{{count}}" bind:sync="syncCount" class="customA" id="cA"></my-test5>
注:本质上是在父组件中操控子组件中的数据,因为就算调用了子组件中的方法,也只能是操作子组件的数据,对父组件没什么影响!
但是子组件本身也是显示在父组件中的,所以也相当于操作了页面中的数据!
11.behaviors
11.1 什么是 behaviors?
11.2 behaviors 的工作方式
11.3 创建 behavior
my-behavior.js
module.exports = Behavior({
data: {
username: 'zs'
},
properties: {},
methods: {}
})
11.4 导入并使用 behavior
test5.js
// components/test5/test5.js
const myBehavior = require('../../behaviors/my-behavior')
Component({
behaviors: [myBehavior],
/**
* 组件的属性列表
*/
properties: {
count: Number
},
/**
* 组件的初始数据
*/
data: {
username: 'ls'
},
/**
* 组件的方法列表
*/
methods: {
addCount() {
this.setData({
count: this.properties.count + 1
})
// 触发自定义事件,将数值同步给父组件
this.triggerEvent('sync', { value: this.properties.count })
}
}
})
11.5 behavior 中所有可用的节点
可用的节点 | 类型 | 是否必填 | 描述 |
---|---|---|---|
properties | Object Map | 否 | 同组件的属性 |
data | Object | 否 | 同组件的数据 |
methods | Object | 否 | 同自定义组件的方法 |
behaviors | String Array | 否 | 引入其它的 behavior |
created | Function | 否 | 生命周期函数 |
attached | Function | 否 | 生命周期函数 |
ready | Function | 否 | 生命周期函数 |
moved | Function | 否 | 生命周期函数 |
detached | Function | 否 | 生命周期函数 |
11.6 同名字段的覆盖和组合规则
https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html
如果冲突了,会比较复杂。
总结:
二、使用 npm 包
1.小程序对 npm 的支持与限制
但是npm还是可以用的!
2.Vant Weapp
2.1 什么是 Vant Weapp
官方文档地址 https://youzan.github.io/vant-weapp
2.2 安装 Vant 组件库
https://youzan.github.io/vant-weapp/#/quickstart#an-zhuang
1.打开cmd窗口
2.初始化一个package.json文件
npm init -y
3.安装vant weapp
npm i @vant/weapp@1.3.3 -S --production
4.构建npm包
5.修改app.json
删除“style”:“v2”这个配置,否则可能样式冲突
注:还是要以官方文档为准!
2.3 使用 Vant 组件
2.4 定制全局主题样式(使用 CSS 变量)
Vant Weapp 支持使用 CSS 变量来实现定制主题。
关于 CSS 变量的基本用法,请参考 MDN 文档:https://developer.mozilla.org/zh-CN/docs/Web/CSS/Using_CSS_custom_properties
有点类似于less,scss的操作,但是这是原生css的用法:css变量!
实际操作:
--加上变量名字即可组成为一个css变量名
在根节点page内定义css变量,这里面的变量是顶级作用域的
我们自己定义了css变量值,就可以覆盖Vant Weapp中默认的那个变量样式!
所有Vant Weapp可用的颜色变量,请参考 Vant 官方提供的配置文件:https://github.com/youzan/vant-weapp/blob/dev/packages/common/style/var.less
3.API Promise化
3.1 基于回调函数的异步 API 的缺点
3.2 什么是 API Promise 化
API Promise化 指的是:通过额外的配置,将官方提供的、基于回调函数的异步 API,升级改造为基于
Promise 的异步 API,从而提高代码的可读性、维护性,避免回调地狱的问题。
回调地域:回调函数中嵌套回调函数
Promise可以避免回调函数的嵌套
3.3 实现 API Promise 化
在终端执行npm install --save miniprogram-api-promise@1.0.4
(前提是已经有package.json这个文件了),然后点击构建npm(现在的版本好像不需要删除之前的miniprogramm_npm文件夹了)
强制删除的快捷键:选中要删除的文件,shift+delete即可
注:小程序中用npm安装完一个包之后都不能马上使用,需要点击构建npm才可以在小程序里面使用
app.js
//app.js
import { promisifyAll } from 'miniprogram-api-promise'
const wxp = wx.p = {} //wxp是一个常量,wx.p是wx对象的一个属性,它们指向同一个空对象 ——> 连续赋值
promisifyAll(wx, wxp) //把wx对象上面的api都Promise化,然后挂载到wxp这个对象上面,然后可以通过wx.p去调用Promise的函数(因为wxp不是全局对象,所以只能用wx对象调用 ——>巧妙的设计)
App({
//省略
})
3.4 调用 Promise 化之后的异步 API
以前的网络请求:
Page({
data: {
},
getInfo() {
wx.request({
url: 'https://www.escook.cn/api/get',
data: {
name: 'zs',
age: 20
}
method: 'get',
success: ({ data: res }) => {
this.setData({
colorList: [...this.data.colorList, ...res.data]
})
},
complete: () => {
wx.hideLoading()
this.setData({
isloding: false
})
}
})
}
})
home.js
// pages/home/home.js
Page({
data: {
},
async getInfo() {
//{data: res}的意思是,结构返回的Promise对象的data属性,重命名为res(所以这里的res以及变味了,本来是Promise对象,现在是Promise对象的data属性,不过还需要再.data才是真正的数据)
const {data: res} = await wx.p.request({ //wx.p.request的返回值是Promise对象,可以用async和await修饰,就不需要写回调函数了(不需要在success里面写,因为这个getInfo()方法本身就是一个异步的方法)
method: 'GET',
url: 'https://www.escook.cn/api/get',
data: {
name: 'zs',
age: 20
}
})
console.log(res)
console.log(res.data)
}
})
三、全局数据共享
1.简介
1.1 什么是全局数据共享
1.2 小程序中的全局数据共享方案
需要两个包:一个用来定义store(包括数据和方法),一个用来把store中的数据和方法绑定到组件或者页面中使用
2.MobX
2.1 安装 MobX 相关的包
npm install --save mobx-miniprogram@4.13.2 mobx-miniprogram-bindings@1.2.1
现在的版本好像不需要删除之前的miniprogramm_npm文件夹了
2.2 创建 MobX 的 Store 实例
在根目录下创建store文件夹,然后创建store.js文件
store.js
// 在这个 JS 文件中,专门来创建 Store 的实例对象
// 按需导入相关方法
import { observable, action } from 'mobx-miniprogram'
export const store = observable({
// 数据字段
numA: 1,
numB: 2,
activeTabBarIndex: 0,
// 计算属性:只有store里面可以定义计算属性,前面要加一个get修饰符,意味着这个属性是只读的
// 这里是计算属性sum
get sum() {
return this.numA + this.numB //this指的就是store对象
},
// actions 函数:专门用来定义 用来修改 store 中数据的值 的方法
updateNum1: action(function (step) { //updateNum1方法,用action包裹起来,作为action的参数
this.numA += step
}),
updateNum2: action(function (step) { //updateNum2方法
this.numB += step
}),
updateActiveTabBarIndex: action(function(index) { //updateActiveTabBarIndex方法
this.activeTabBarIndex = index
})
})
2.3 将 Store 中的成员绑定到页面中
message.js
// pages/message/message.js
import { createStoreBindings } from 'mobx-miniprogram-bindings'
import { store } from '../../store/store'
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载:进行store的绑定
*/
onLoad: function (options) {
//createStoreBindings方法
//第一个参数:this,代表页面Page的实例对象,意思是需要将store中的内容绑定给这个实例
//第二个参数:需要绑定的内容,会直接绑定到Page对象上面
//返回值为storeBindings,作为Page对象的属性,后面可以用于清理绑定关系
this.storeBindings = createStoreBindings(this, {
store, //数据源,会把store的内容绑定到Page上
fields: ['numA', 'numB', 'sum'], //绑定的字段,按需定义
actions: ['updateNum1'] //绑定的方法,按需定义
})
},
btnHandler1(e) {
// console.log(e)
// 直接调用我们绑定的store中的方法
this.updateNum1(e.target.dataset.step) //e.target.dataset.xxx可以取到传入的事件函数的参数值
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
this.storeBindings.detroyStoreBindings() //清理绑定的内容
}
})
message.wxml
<!--pages/message/message.wxml-->
<view>{{numA}} + {{numB}} = {{sum}}</view>
<!--data-xxx为事件传参,step是自己定义的参数名-->
<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>
<view>~~~~~~</view>
<my-numbers></my-numbers>
2.4 在页面上使用 Store 中的成员
2.5 将 Store 中的成员绑定到组件中
注:小程序中是页面和组件的store绑定方式完全不同的,自成一派!
app.json 定义组件
"usingComponents": {
"van-button": "@vant/weapp/button/index",
"my-numbers": "./components/numbers/numbers",
"van-tabbar": "@vant/weapp/tabbar/index",
"van-tabbar-item": "@vant/weapp/tabbar-item/index"
}
message.xwml 使用组件
<!--pages/message/message.wxml-->
<view>{{numA}} + {{numB}} = {{sum}}</view>
<!--data-xxx为事件传参,step是自己定义的参数名-->
<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>
<view>~~~~~~</view>
<my-numbers></my-numbers> <!--使用my-numbers组件,默认显示的是组件路径-->
number.js
// components/numbers/numbers.js
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'//和Page中用的方法不一样
import { store } from '../../store/store'
Component({
behaviors: [storeBindingsBehavior], //通过storeBindingsBehavior来实现自动绑定
storeBindings: {
store, //数据源
fields: {
// 映射的名字(任意):store中的名字
numA: 'numA', //直接写字段名就可以了,最方便
numB: 'numB',
sum: 'sum'
},
actions: {
updateNum2: 'updateNum2'
}
},
/**
* 组件的方法列表
*/
methods: {
btnHandler2(e) {
this.updateNum2(e.target.dataset.step)
}
}
})
numbers.wxml
<!--components/numbers/numbers.wxml-->
<view>{{numA}} + {{numB}} = {{sum}}</view>
<van-button type="primary" bindtap="btnHandler2" data-step="{{1}}">numB + 1</van-button>
<van-button type="danger" bindtap="btnHandler2" data-step="{{-1}}">numB - 1</van-button>
2.6 在组件中使用 Store 中的成员
注:使用的方法在页面和组件中是一样的!
四、分包
1.简介
1.1 什么是分包
分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
1.2 分包的好处
比如每个人负责一个分包的开发,更加高效!
1.3 分包前项目的构成
1.4 分包后项目的构成
注:这里TabBar页面指的是可以通过TabBar跳转到的页面!
主包的公共资源可以被任意分包访问,但是分包的私有资源只有自己这个包才可以访问!
1.5 分包的加载规则
类似于路由懒加载!
1.6 分包的体积限制
如果使用了分包,所有分包不能超过16m,每个不能超过2m,但是仅限于分包,这个大小限制和主包无关!
所以图片资源要很小才行,最好放在云端!
查看分包大小:
注:据说新版的分包总大小限制变成了20m
2.使用分包
2.1 配置方法
app.json
{
"pages": [
"pages/home/home",
"pages/message/message",
"pages/contact/contact"
],
"preloadRule": { //分包预下载
"pages/contact/contact": { //进入contact页面时就进行下载
"packages": [
"p1"
],
"network": "all"
}
},
"subpackages": [
{
"root": "pkgA",
"name": "p1", //别名
"pages": [
"pages/cat/cat",
"pages/dog/dog"
]
},
{
"root": "pkgB",
"name": "p2",
"pages": [
"pages/apple/apple"
],
"independent": true //独立分包
}
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#13A7A0",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black"
},
"tabBar": { //这里面就是所有tabBar页面
"list": [
{
"pagePath": "pages/home/home",
"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/contact/contact",
"text": "联系我们",
"iconPath": "/images/tabs/contact.png",
"selectedIconPath": "/images/tabs/contact-active.png"
}
]
},
"sitemapLocation": "sitemap.json",
"usingComponents": {
"van-button": "@vant/weapp/button/index",
"my-numbers": "./components/numbers/numbers",
"van-tabbar": "@vant/weapp/tabbar/index",
"van-tabbar-item": "@vant/weapp/tabbar-item/index"
}
}
2.2 打包原则
2.3 引用原则
3.独立分包
3.1 什么是独立分包
3.2 独立分包和普通分包的区别
3.3 独立分包的应用场景
在只需要访问分包,不需要访问主包时,可以使用独立分包!
3.4 独立分包的配置方法
3.5 引用原则
4.分包预下载
4.1 什么是分包预下载
分包预下载指的是:在进入小程序的某个页面时,由框架自动预下载可能需要的分包,从而提升进入后续分包页面时的启动速度。
4.2 配置分包的预下载
4.3 分包预下载的限制
五、案例 - 自定义组件:tabBar
1.案例效果
2.实现步骤
https://developers.weixin.qq.com/miniprogram/dev/framework/ability/custom-tabbar.html
3.具体实现
1.app.json
“custom”: true 表示开启自定义tabBar
“list” 不用动,这些定义不会用于tabBar的渲染,但是为了低版本兼容和声明tabBar页面应当保留这个定义
2.创建目录结构
注意:文件夹名和文件名都是固定的!在项目根目录下进行创建!
右键,新建组件,index组件
小程序会自动读取这个目录下的内容,作为tabBar,自动渲染到页面的相应的位置
3.导入vant的tabBar组件
app.json
"usingComponents": {
"van-button": "@vant/weapp/button/index",
"my-numbers": "./components/numbers/numbers",
"van-tabbar": "@vant/weapp/tabbar/index", //tabBar组件
"van-tabbar-item": "@vant/weapp/tabbar-item/index" //tabBar组件
}
4.index.wxml
<!--custom-tab-bar/index.wxml-->
<!--active-color可以修改选中项的文本颜色值-->
<van-tabbar active="{{active}}" bind:change="onChange" active-color="#13A7A0">
<!--info属性是图标右上角的数字标点的值,但是只有message需要这个值-->
<van-tabbar-item wx:for="{{list}}" wx:key="index" info="{{item.info ? item.info : ''}}">
<!--多个插槽:slot="icon"是未选中时的图标,slot="icon-active"是选中时的图标-->
<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>
5.index.js
// custom-tab-bar/index.js
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'
import { store } from '../store/store'
Component({
//修改样式作用域
options: {
styleIsolation: 'shared' //在自定义组件中,需要把作用域改为共享的,才可以去改写vant组件中的样式
},
//组件绑定store
behaviors: [storeBindingsBehavior],
storeBindings: {
store,
fields: { //绑定store中的数据
sum: 'sum',
active: 'activeTabBarIndex'
},
actions: { //绑定store中的方法
updateActive: 'updateActiveTabBarIndex'
},
},
//数据监听器
observers: {
'sum': function (val) { //监听sum数据,准备为info进行赋值,val是sum最新的值(自动获取的)
this.setData({
'list[1].info': val //赋值给info
})
}
},
/**
* 组件的初始数据
*/
data: {
// active: 0, //要移到store中进行公共存储,否则每次用wx.switchTab跳转页面之后,active就会重新赋值为0了,那么图标和页面就不同步了
"list": [ //根据list数组的数据去渲染每一个item项
{
"pagePath": "/pages/home/home",
"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/contact/contact",
"text": "联系我们",
"iconPath": "/images/tabs/contact.png",
"selectedIconPath": "/images/tabs/contact-active.png"
}
]
},
/**
* 组件的方法列表
*/
methods: {
//change事件绑定的函数
onChange(event) {
// event.detail(额外的信息)的值为当前选中项的tabBar索引(等价于在tabBar页面列表中的索引),用于给active重新赋值
// this.setData({ active: event.detail })
this.updateActive(event.detail); //更新索引值
wx.switchTab({ //wx.switchTab方法可以进行页面的跳转!
url: this.data.list[event.detail].pagePath, //event.detail就是tabBar索引,.pagePath得到的就是url
})
},
}
})
6.index.wxss
/* custom-tab-bar/index.wxss */
.van-tabbar-item { /*在父节点上去定义变量(如果父节点有这个变量,子节点就可以直接用了)*/
--tabbar-item-margin-bottom: 0; /*利用css变量修改vant的默认样式,让图标和文字间隙为0*/
}
7.store.js
// 在这个 JS 文件中,专门来创建 Store 的实例对象
import { observable, action } from 'mobx-miniprogram'
export const store = observable({
// 数据字段
numA: 1,
numB: 2,
activeTabBarIndex: 0,
// 计算属性
get sum() {
return this.numA + this.numB
},
// actions 函数,专门来修改 store 中数据的值
updateNum1: action(function (step) {
this.numA += step
}),
updateNum2: action(function (step) {
this.numB += step
}),
updateActiveTabBarIndex: action(function(index) { //用于自定义的tabBar使用,更新索引值
this.activeTabBarIndex = index
})
})
总结: