小程序 --- 自定义组件
0. 概述
**公共组件: ** 将页面内的功能模块抽取成自定义组件, 以便于在不同的页面中重复使用, 一般存在在项目根目录的 components 文件夹中, 一个组件一个文件夹
**页面组件: ** 将复杂的页面拆分成多个低耦合的模块, 有助于代码维护, 一般存在在对应页面的目录中
1. 创建

2. 注册
**公共组件, 全局注册: ** 在 app.json 文件中配置 usingComponents 进行注册, 注册后可以在任意页面中使用
{
"usingComponents": {
// key: 组件名, vlaue: 组件存放路径
"custom-checkbox": "./components/custom-checkbox/custom-checkbox"
}
}
使用组件
<custom-checkbox />
**页面组件, 局部注册: ** 在 页面.json 文件中配置 usingComponents 进行注册, 注册后只能在当前页面使用
{
"usingComponents": {
// key: 组件名, vlaue: 组件存放路径
"custome-swiper": "./custome-swiper/custome-swiper"
}
}
使用组件
<custome-swiper />
3. 数据、方法
组件的数据和方法需要在 组件.js的 Component 方法中定义,
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
}
})
4. 样式隔离
- 自定义组件中不允许使用标签选择器、ID 选择器、属性选择器, 建议优先使用 class 选择器
- 子选择器只能用于 view 组件,用于其他组件可能会出现样式失效的问题
- 全局样式 或 组件所在的页面样式,都对自定组件无效
- 父组件和自定义组件,如果使用了后代选择器, 可能会出现一些非预期情况,要使用组件样式隔离
子组件的 js 文件中,配置 stylelsolation
Component({
options:{
// isolated: 开启样式隔离, 默认值
// apply-shared: 父组件的样式,会影响子组件的样式, 反之不会
// shared: 父组件的样式,会影响子组件的样式和其他使用了 apply-share 和 share 属性的自定义组件
styleIsolation: "isolated"
}
})
5. 外部样式类
默认情况下, 组件和组件使用者之间如果存在相同的类名不会相互影响, 组件使用者如果想修改组件的样式,需要解除样式隔离,但是解除样式隔离后,在极端情况下会产生样式冲突、css嵌套太深等问题,所以小程序中通过组件使用者给组件传入 CSS类名的方法来修改组件的样式
1. 组件使用者的 scss 文件中定义样式
.warpper-class{
color: red !important; // 如果确保外部样式类生效,需要添加 !important 来提高权重,以防组件中有相同的样式
}
2. 组件使用者中传递外部样式类
<custom extend-class="warpper-class" />
3. 在组件的 js 文件中定义 externalClasses 配置项
Component({
externalClasses:['extend-class'] // 接收父组件传给子组件的外部样式类
})
4. 组件的 wxml 文件中,使用该外部样式类
<view class="extend-class">
通过外部样式类修改组件的样式
</view>
6. 使用 CSS 变量
多个页面中使用这个组件、一个组件中批量修改样式、定制主题, 建议使用 CSS 变量
1. 多个页面中使用这个组件
全局 scss
page {
--color: lightseagreen; // 使用 --属性,来定义
}
组件使用者的 scss
.van-button--primary {
font-size: 28rpx !important; // 提高权重
background-color: var(--color) !important;
border: 1px solid var(--color) !important
}
2. 批量修改样式
<van-button type="default" class="my-button">默认按钮</van-button>
.my-button {
--color: lightseagreen;
}
7. 数据监听
写法一: 一个一个监听
Component({
properties: {
label: "标题"
},
data: {
num: 10,
count: 100,
obj: {
name: "Tom",
age: 28
},
arr: [1,2,3]
},
methods: {
updateData(){
this.setData({
num: this.data.num + 1,
count: this.data.count -1,
"arr[1]": 100,
"obj.name": "Anay"
})
}
},
// 监听数据是否发生变化
observers: {
num: function (newNum) { // 监听数值类型
console.log(newNum);
},
count: function (newCount){
console.log(newCount);
},
"arr[1]": function (newItem) { // 监听数组类型
console.log(newItem);
},
"obj.name":function(newName){ // 监听对象中的指定属性
console.log(newName)
},
"obj.**":function(newName){ // 监听对象中的所有属性
console.log(newName)
},
// 对于用 properties 接收的数据,在页面初次渲染的时候,会立刻执行一次这个监听器
// 之后每次发生改变也会执行一次这个监听器
label: function(newLabel){ // 监听 properties 接受的数据
console.log(newLabel)
}
}
})
**写法二: 同时监听多个数据 **
Component({
data: {
num: 10,
count: 100,
obj: {
name: "Tom",
age: 28
},
arr: [1,2,3]
},
methods: {
updateData(){
this.setData({
num: this.data.num + 1,
count: this.data.count -1
})
}
},
// 监听数据是否发生变化
observers: {
"num,count": function (newNum,newCount){
console.log(newNum, newCount);
}
}
})
8. 组件通信
1. 父传子
1. properties 传递数据
pages/cart/cart.wxml
<view>
<!-- label 和 position 都是传递的参数 -->
<custom-checkbox label="我已阅读并同意 用户协议 和隐私协议" position="right"/>
</view>
components/custom-checkbox/custom-checkbox.wxml
<view>
<view class="box {{ position==='right' ? 'right' : 'left' }}"> <!-- 根据 position的值 来控制样式 -->
<checkbox value="1" checked="{{ isChecked }}" bindtap="aggreyUser"/>
<view>
<text>{{ label }}</text> <!-- 使用 双大括号{{}},来展示参数 -->
</view>
</view>
</view>
components/custom-checkbox/custom-checkbox.js
Component({
// 接收父组件传递的参数
properties: {
position:{ // 全写方式
type: String, // 限制数据类型,只能是 String、Number、Boolmean、Object、Array、null(表示不限制类型)
value:'' // 默认值
},
label: String // 简写方式, 只限制传递参数的数据类型
},
methods: {
updateChecked(){
console.log(this.properties.label) // 访问父组件传给子组件的参数对象
this.setData({
label: "修改参数过来的值" // 一般不建议修改, 因为有可能会造成数据的混乱
})
}
}
})
2. slot 插槽传递标签
1. 默认插槽
默认情况下, 一个组件的 wxml 中只能有一个 slot(默认插槽)。
父组件的 .wxml 文件中
<custom-checkbox>
<!-- 默认情况下. 自定义组件的子节点内容不会进行展示,如果想通过向子组件内传递内容,来展示不同的页面,需要通过 slot 插槽来实现 -->
我是子组件的内容
</custom-checkbox>
子组件的 .wxml 文件中
<view>
<view>
<!-- 默认插槽,接收父组件传递过来的内容 -->
<slot />
</view>
</view>
2. 多具名插槽
如果需要使用多个 slot 时, 需要在组件的js中声明启用
父组件的 .wxmll 中
<view>
<custom-checkbox>
<text slot="slot-top">我需要显示在默认插槽的顶部</text>
<text>我是默认插槽的内容</text>
<text slot="slot-bottom">我需要显示在默认插槽的底部</text>
</custom-checkbox>
</view>
子组件的 .js 文件中,开启多插槽功能
Component({
options:{
multipleSlots:true
}
})
子组件的 .wxml 文件中
<view>
<view>
<slot name="slot-top" />
</view>
<view>
<slot />
</view>
<view>
<slot name="slot-bottom" />
</view>
</view>
2. 子传父
1. 自定义事件
1. 子组件的 js 文件中定义传递的数据,以及自定义事件名
Component({
properties: {},
data: {
num: 100
},
methods: {
// 将数据传递给父组件
sendData() {
// 参数一: 自定义事件名
// 参数二: 需要携带的数据
this.triggerEvent("sendNum", this.data.num)
}
}
})
2. 父组件的 wxml 文件中,绑定自定义事件及事件处理函数
<!-- 父组件中,通过 bind:子组件中定义的自定义事件 来绑定时间处理函数 -->
<view>
<text>子组件传过来的数据为: {{ num }}</text>
</view>
<custom01 bind:sendNum="saveNum"/>
3. 父组件的 js 文件中,声明事件处理函数
Page({
data:{
num: ''
},
saveNum(event){
// 可以通过 event.detail 来获取子组件传给父组件的数据
this.setData({
num: event.detail
})
}
})
2. 子组件实例
父组件可以通过 this.selectComponent() 方法,获取子组件实例对象, 这样就可以直接访问子组件的任意数据和方法
1. 父组件的 html 文件中,给子组件一个 class 或 id
<custom01 class="child" id="child" />
<button plain type="warn" bind:tap="getData">获取子组件数据</button>
2. 父组件的 js 文件中,调用 this.selectComponent() 方法,获取子组件的数据和方法
Page({
data:{
num: ''
},
getData(){
const childObj = this.selectComponent(".child") // 可以是类选择器 或 ID选择器
this.setData({
num: childObj.data.num
})
}
})
3. 兄弟组件
1. 事件总线
事件总线是对 发布-订阅模式 的一种实现,是一种集中式事件处理机制, 允许不同的组件之间彼此进行通信,常用于两个非父子关系组件和兄弟组件之间的通信,可以使用第三方的 发布订阅JS包 (PubSubJS) 来实现事件总线的功能
1. 官网
https://gitcode.com/mroderick/PubSubJS/overview
2 安装
npm i pubsub-js
3. 点击 工具 => 构建Npm
4. 数据发送方
// 1. 引入 pubsub-js
import PubSub from "pubsub-js"
Component({
data:{
name: "Tom"
},
methods: {
sendToBrother(){
// 2. 发布自定义事件,并携带数据
PubSub.publish("getData",this.data.name)
}
}
})
5. 数据接收方
// 1. 导入 pubsub-js
import PubSub from 'pubsub-js'
Component({
data:{
name: ""
},
// 生命周期配置项中,声明 attached() 生命周期函数
lifetimes:{
attached(){
// 2. 订阅监听自定义事件
PubSub.subscribe("getData", (msg,data) =>{
console.log(msg,data); // msg 是需要监听的自定义事件名称, data 是携带的数据
this.setData({
name: data
})
})
}
}
})
4. 全局数据共享 --- getApp()
在小程序中,可以通过 getApp() 方法 获取到小程序全局唯一的 App 实例,因此在 app.js 文件中的 App() 方法中添加全局共享的数据、方法,从而实现页面、组件之间的数据传递
1. 基本使用
app.js
App({
// 全局共享的数据
globalData: {
token: ""
},
// 全局共享的方法
setToken(token){
this.globalData.token = token
}
})
pages/login/login.js
// 获取全局唯一的 App() 实例
const appInstance = getApp()
Page({
login(){
// 调用全局的方法 setToken()
appInstance.setToken("123456");
}
})
pages/index/index.js
// 获取全局唯一的 App() 实例
const appInstance = getApp()
Page({
onLoad(){
if (!appInstance.token){
wx.redirectTo({
url: '/pages/login/login',
success: (res) => {},
fail: (res) => {},
complete: (res) => {},
})
}
}
})
2. 注意事项
- 不要在 App() 方法中使用 getApp() 方法, 使用 this 就能拿到 app 实例
- 不要通过 app 实例调用生命周期函数
5. 全局数据共享 --- store
0. 官方文档
https://www.npmjs.com/package/mobx-miniprogram
https://www.npmjs.com/package/mobx-miniprogram-bindings
1. 介绍
上面已经有了 6 中小程序页面、组件间的数据通信方案, 分别是:
- 数据绑定:
properties - 获取组件实例:
this.selectComponent() - 事件绑定:
this.triggerEvent() - 获取应用实例:
getApp() - 页面间通信:
EventChannel - 事件总线:
pubsub-js
在中小型项目中, 使用这些数据通信方式已经能够满足项目需求。但是随着项目的业务逻辑越来越复杂, 组件和页面间通信就会变得非常复杂。例如: 有些状态需要再多个页面间进行同步使用, 一个地方发生变更, 所有使用的地方都需要发生改变, 这时候如果使用前面的数据通信方案进行传递数据, 管理和维护就爱那个存在很大的问题, 为了方便进行页面、组件之间数据的传递, 小程序官方提供了一个扩展工具库: mobx-miniprogram, 它是针对微信小程序开发的一个简单、高效、轻量级状态管理库, 它基于 Mobx 状态管理框架实现。使用 mobx-miniprogram 定义管理的状态是响应式的, 当状态一旦发生改变, 所有关联组件都会自动更新相对应的数据, 通过该扩展工具库, 开发者可以很方便的在小程序中全局共享状态, 并自动更新视图组件, 从而提升小程序的开发效率。
**注意: ** 在使用 mobx-program 需要安装两个包: mobx-miniprogram 和 mobx-miniprogram-bindings
mobx-miniprogram的作用: 创建Store对象, 用于存储应用的数据mobx-miniprogram-bindings的作用: 将状态和组件、页面进行绑定关联, 从而在组件和页面中操作数据
2. 安装
npm i mobx-miniprogram mobx-miniprogram-bindings
3. 创建 Store 对象
1. 创建 Store 对象需要使用 mobx-miniprogram ,因此需要先熟悉 mobx-miniprogra 三个核心概念:
- observable:用于创建一个被监测的对象,对象的属性就是应用的状态(state),这些状态会被转换成响应式数据。
- action:用于修改状态(state)的方法,需要使用 action 函数显式的声明创建。
- computed:根据已有状态(state)生成的新值。计算属性是一个方法,在方法前面必须加上
get修饰符
2. 开始使用
1. 在项目的根目录下创建 stores 文件夹,然后在该文件夹下新建 indexStore.js
2. 使用 observable 方法需要接受一个 store 对象,存储应用的状态
stores/indexStore.js
import {
observable, // observable:用于创建一个被监测的对象,对象的属性就是应用的状态(state),这些状态会被转换成响应式数据。
action // action:用于显式的声明创建更新 state 状态的方法
} from 'mobx-miniprogram'
export const numStore = observable({
// 创建应用状态
numA: 1,
numB: 2,
// 使用 action 更新 numA 以及 numB
update: action(function () {
this.numA += 1
this.numB += 1
}),
// 计算属性,根据已有的状态产生新的状态,前面需要使用 get 修饰符,
get sum() {
return this.numA + this.numB // 计算属性内部必须有返回值
}
})
4. 在组件中使用数据和方法
如果需要 Page 或者 Component 中对共享的数据进行读取、更新操作,需要使用 mobx-miniprogram-bindings,其作用就是将 Store 和 页面或组件进行绑定关联
如果需要在组件中使用状态,需要 mobx-miniprogram-bindings 库中导入 ComponentWithStore 方法,在使用时:需要将 Component 方法替换成 ComponentWithStore 方法 ,原本组件配置项也需要写到该方法中
components/custom01/custom01.wxml
<view>
<view>{{ numA }} + {{ numB }} = {{ sum }} </view>
<button type="primary" plain bind:tap="updateNum">更新</button>
</view>
<view>
<view>{{ numA }} + {{ numB }} = {{ sum }} </view>
<!-- 或者可以直接绑定 store 中定义的方法, 因为 update 会被注入到 methods 对象中, 直接调用即可 -->
<button type="primary" plain bind:tap="update">更新</button>
</view>
components/custom01/custom01.js
import {
ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
numStore
} from '../../stores/indexStore' // 导入 store
ComponentWithStore({
methods: {
updateNum() {
this.update() // 直接使用 this.update() 使用 store 中定义的方法
}
},
// 用来配置当前组件需要和哪些 store 进行关联, 在从 store 对象中引入数据和方法后
// 如果是数据,则会被注入到 data 对象中
// 如果是方法, 则会被注入到 methods 对象中
storeBindings: {
store: numStore, // 关联的store
fields: ['numA', 'numB', 'sum'], // 关联的数据列表
actions: ['update'] // 关联的方法列表
}
})
4. 在替换以后,就会新增一个 storeBindings 配置项,配置项常用的属性有以下三个:
-
store: 指定要绑定的 Store 对象
-
fields: 指定需要绑定的 data 字段
-
actions: 指定需要映射的 actions 方法
// components/custom01/custom01.js
import { ComponentWithStore } from 'mobx-miniprogram-bindings'
import { numStore } from '../../stores/numstore'
ComponentWithStore({
data: {
someData: '...'
},
storeBindings: {
store: numStore,
fields: ['numA', 'numB', 'sum'],
actions: ['update']
}
})
5. 注意事项:
-
导入的数据会同步到组件的 data 中
-
导入的方法会同步到组件的 methods 中
5. 在页面中使用数据和方法
1. 方式一
Component 方法用于创建自定义组件,小程序的页面 (page) 也可以视为自定义组件,因此页面也可以使用 Component 方法进行构建,从而实现复杂的页面逻辑开发。如果我们使用了 Component 方法来构建页面,那么页面中如果想使用 Store 中的数据,使用方式和组件的使用方式是一样的(同上面的在在组件中使用一样, 将 Page() 替换成 ComponentWithStore()
pages/index/index.wxml
<view>
<view>{{ numA }} + {{ numB }} = {{ sum }} </view>
<button type="primary" plain bind:tap="update">更新</button>
</view>
pages/index/index.js
import {
ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
numStore
} from '../../stores/indexStore'
ComponentWithStore({ // 将 page() 替换为 ComponentWithStore()
storeBindings: {
store: numStore,
fields: ['numA', 'numB', 'sum'],
actions: ['update']
}
})
2. 方式二
如果不想使用 Component 方法构建页面。这时候需要使用 mobx-miniprogram-bindings 提供的 BehaviorWithStore 方法来和 Store 建立关联,小程序的 behavior 方法是一种代码复用的方式,可以将一些通用的逻辑和方法提取出来,然后在多个组件中复用,从而减少代码冗余,提高代码的可维护性。在页面中也可以使用 behaviors 配置项
1. 页面文件夹中新建 behavio 文件
pages/index/behaviors.js
import { BehaviorWithStore } from 'mobx-miniprogram-bindings'
import { numStore } from '../../stores/numstore'
export const indexBehavior = BehaviorWithStore({
storeBindings: {
store: numStore,
fields: ['numA', 'numB', 'sum'],
actions: ['update'],
}
2. 在页面 js 文件中引入 behavior.js
pages/index/index.js
import {
indexBehavior
} from './behaviors'
Page({
behaviors: [indexBehavior] // 注册 indexBehavior
})
3. 页面中使用
pages/index/index.wxml
<view>
<view>{{ numA }} + {{ numB }} = {{ sum }} </view>
<button type="primary" plain bind:tap="update">更新</button>
</view>
6. fields、actions 的对象写法
1. fields 的对象写法 --- 映射形式
pages/index/behaviors.js
import {
BehaviorWithStore
} from 'mobx-miniprogram-bindings'
import {
numStore
} from '../../stores/indexStore'
export const indexBehavior = BehaviorWithStore({
storeBindings: {
store: numStore,
fields: { // 映射形式
a: 'numA',
b: 'numB',
s: 'sum'
},
actions: ['update']
}
})
pages/index/index.wxml
<view>
<!-- 需要使用 key 声明 numA -->
<view>{{ a }} + {{ b }} = {{ s }} </view>
<button type="primary" plain bind:tap="update">更新</button>
</view>
2. fields 的对象写法 --- 函数形式
pages/index/behaviors.js
import {
BehaviorWithStore
} from 'mobx-miniprogram-bindings'
import {
numStore
} from '../../stores/indexStore'
export const indexBehavior = BehaviorWithStore({
storeBindings: {
store: numStore,
fields: {
a: () => numStore.numA, // 可以在函数形式中再次进行计算等操作
b: () => numStore.numB,
s: () => numStore.sum
},
actions: ['update']
}
})
pages/index/index.wxml
<view>
<!-- 需要使用 key 声明 numA -->
<view>{{ a }} + {{ b }} = {{ s }} </view>
<button type="primary" plain bind:tap="update">更新</button>
</view>
3. actions 的对象写法 --- 映射形式
pages/index/behaviors.js
import {
BehaviorWithStore
} from 'mobx-miniprogram-bindings'
import {
numStore
} from '../../stores/indexStore'
export const indexBehavior = BehaviorWithStore({
storeBindings: {
store: numStore,
fields: {
a: () => numStore.numA,
b: () => numStore.numB,
s: () => numStore.sum
},
// 使用映射形式获取 Store 中的 action 名字
actions: {
// key 自定义,为当前组件中调用的方法
// 值为 store 中对应的 action 名字
buttonTap: 'update'
}
}
})
pages/index/index.wxml
<view>
<view>{{ a }} + {{ b }} = {{ s }} </view>
<!-- 使用 actions 中的 key 来调用对应的 store 中的方法 -->
<button type="primary" plain bind:tap="buttonTap">更新</button>
</view>
7. 绑定多个 store 及 命名空间解决冲突
在实际开发中,一个页面或者组件可能会绑定多个 Store ,如果多个 Store 中存在相同的数据和方法,会造成冲突, 解决方法:
1. 方法一: 将 fields 和 actions 换成对象方式写法
方法二: 添加命名空间配置项, 但命名空间只对数据有效, 方法同样需要换成对象方式写法
pages/index/behaviors.js
import { BehaviorWithStore } from 'mobx-miniprogram-bindings'
import { numStore } from '../../stores/numstore'
export const indexBehavior = BehaviorWithStore({
storeBindings: [ // 将 storeBindings 换成数组
{
namespace: 'numStore', // 如果多个 store 中的数据或者方法冲突, 则需要加命名空间
store: numStore,
fields: ['numA', 'numB', 'sum'],
actions: ['update'],
}
]
})
pages/index/index.wxml
<!-- 在页面使用需要加上命名空间 -->
<view>{{ numStore.numA }} + {{ numStore.numB }} = {{numStore.sum}}</view>
6. 页面间通信
如果一个页面通过 wx.navigateTo 打开一个新页面, 这两个页面之间将建立一条数据通道
pages/index/index.wxml
<text>index</text>
<button type="primary" plain bind:tap="toListPage">跳转列表页</button>
pages/list/list.wxml
<text>list</text>
1. 在 wx.navigateT0() 的 success 回调中 通过 EventChannel.emit() 发射事件并携带数据
pages/index/index.js
Page({
data: {
num: 10
},
toListPage() {
// 跳转页面
wx.navigateTo({
url: '/pages/list/list',
success: (res) => {
// 发射自定义事件,并传递数据
res.eventChannel.emit("indexToList", this.data.num)
}
})
}
})
2. 在 被打开的页面可以通过 this.getOpenerEventChannel() 方法来获得一个 EventChannel.on() 进行监听、或者使用 EventChannel.emit() 发射事件
pages/list/list.js
Page({
onLoad(options) {
// 接收首页发来的数据
const EventChannel = this.getOpenerEventChannel()
EventChannel.on("indexToList",(res) => {
console.log("获取index页面传递的数据",res);
})
// 发送数据给首页
EventChannel.emit("listToIndex",{name: "我是 list 页面发送的数据"})
},
})
3. wx.navigateTo() 方法中可以定义 events 配置项接收被打开页面发射的事件
pages/index/index.js
Page({
data: {
num: 10
},
toListPage() {
wx.navigateTo({
url: '/pages/list/list',
// events 中配置被打开页面发射的自定义事件和其回调函数
events:{
listToIndex: (res) =>{
console.log(res);
}
},
success: (res) => {
res.eventChannel.emit("indexToList", this.data.num)
}
})
}
})
4. 先触发 发送数据页面中配置的接收被发送数据页面的自定义事件及数据,然后再触发接收页面中配置的自定义事件及数据
9. 使用 Component 构造页面
Component 方法是用于创建自定义组件的方法,小程序的页面也可以视为自定义组件,因此页面也可以使用 Component 方法创建,从而实现复杂的页面逻辑开发,但是有如下注意项:
- json 文件中必须有 usingComponents 这个配置项
- 里面的配置项需要和 Component 中的配置项一致
- 页面中 Page 方法中定义的钩子函数、事件监听API,都必须放在 methods 配置项中
- url携带的参数可以通过 onLoad(options) 来接收,也可以通过 properties 接收
为什么要使用 Component 方法构造页面
这是因为 Component 方法 比 Page 方法 的功能要强大很多,可以实现更加复杂的页面逻辑开发
10. 复用机制 --- behaviors
小程序的 behaviors 方法是一种代码复用的方式, 可以将一些通用的逻辑和方法提取出来,然后在多个组件中服用,从而减少代码冗余,提高代码的可维护性。
如果需要使用 behavior 复用代码, 需要使用 Behavior() 方法, 每个 behavior 可以包含一组属性、数据、生命周期函数和方法,组件引用它时, 它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用
1. 组件中新建 behavior.js 文件
coonst behavior = Behavior({
properties:{
label: String,
value: "我已同意该协议"
},
data:{
name: "Tom",
obj: {
name: "Anay",
age: 16
}
},
methods:{
updateName(){
this.setData({
name: "Jerry"
})
}
},
lifetimes: {
attached(){
console.log("我是组件的生命周期函数");
}
}
})
export default behavior
2. 组件中使用
import behavior from './behavior'
Component({
behaviors: [behavior] // 注册 behavior
// 如果组件中有和 behavior 中定义的属性、方法、生命周期函数会有以下不同的情况
// 1. 如果存在相同名字的 properties ,优先使用组件内部的
// 2. 如果存在相同名字的 data, 如果是对象类型,相同名字的对象会合并,其他类型优先使用组件内部的
// 3. 如果存在相同名字的 方法 ,优先使用组件内部的
// 4. 如果存在相同名字的 生命周期函数 ,优先执行 behavior 中定义的生命周期函数,然后再执行组件内部的
})
11. 计算属性 和 监听器
小程序框架没有提供计算属性相关的 api ,但是官方为开发者提供了拓展工具库 miniprogram-computed。该工具库提供了两个功能:
-
计算属性 computed
-
监听器 watch
如果需要在组件中使用计算属性功能,需要 miniprogram-computed 库中导入 ComponentWithComputed 方法, 在使用时:需要将 Component 方法替换成 ComponentWithComputed 方法. 在替换以后,就可以新增 computed 以及 watch 配置项。
1. 安装
安装 miniprogram-computed, 在安装以后,需要点击 工具 => 构建 npm,进行本地构建
npm install miniprogram-computed
2. 计算属性 computed
components/custom01/custom01.js
import { ComponentWithComputed } from 'miniprogram-computed'
ComponentWithComputed({
data: {
a: 1,
b: 1
},
computed: {
total(data) {
// computed 函数中不能使用 this 访问 data 中的数据,通过形参 data 来访问
// 这个函数的返回值会被设置到 this.data.sum 字段中
// 计算属性具有缓存,计算属性方法只会执行一次, 只要计算属性所依赖的数据没有发生改变, 即使后续多次使用计算属性,返回的始终是第一次执行后的记过
console.log('~~~~~')
return data.a + data.b
}
}
})
components/custom01/custom01.wxml
<view> {{ a }} + {{ b }} = {{ total }}</view>
<view>{{ total }}</view>
<view>{{ total }}</view>
3. 监听器 watch
在使用时:需要将 Component 方法替换成 ComponentWithComputed 方法 ,在替换以后,就可以新增 computed 以及 watch 配置项;
import { ComponentWithComputed } from 'miniprogram-computed'
ComponentWithComputed({
data: {
a: 1,
b: 1,
c: ''
},
watch: {
// key:需要监听的数据
// value:是回调函数,回调函数有一个形参,形参就是最新的、改变后的数据
a:function(a){
console.log(a)
}
// 同时对 a 和 b 进行监听
'a, b': function (a, b) {
this.setData({
c: a + b
})
}
},
})
12. store 与 计算属性 同时使用
两个框架扩展提供的 ComponentWithStore 与 ComponentWithComputed 方法无法结合使用。如果需要在一个组件中既想使用 mobx-miniprogram-bindings 又想使用 miniprogram-computed, 解决方案是:
-
使用旧版 API(全部都是用旧的api写, 自定义组件仍然使用 Component 方法构建组件,将两个扩展依赖包的使用全部改为旧版 API
-
使用兼容写法:(取其中任一一个写成旧api就行了,另一个照常写就行了)即要么使用 ComponentWithStore 方法构建组件,要么使用 ComponentWithComputed 方法构建组件, 如果使用了 ComponentWithStore 方法构建组件,计算属性写法使用旧版 API, 如果使用了 ComponentWithComputed 方法构建组件,Mobx写法使用旧版 API
兼容写法
1.如果使用了 ComponentWithStore 方法构建组件,计算属性使用旧版 API
import { ComponentWithComputed } from 'miniprogram-computed'
// 导入计算属性
const computedBehavior = require('miniprogram-computed').behavior
ComponentWithStore({ // 替换为 ComponentWithStore
behaviors: [computedBehavior], // 注册
data: {
a: 1,
b: 1,
sum: 2
},
watch: {
'a, b': function (a, b) {
this.setData({
total: a + b
})
}
},
computed: {
total(data) {
return data.a + data.b + data.sum
}
},
// 实现组件和 Store 的关联
storeBindings: {
store: numStore,
fields: ['numA', 'numB', 'sum'],
actions: ['update']
}
})
2.使用了 ComponentWithComputed 方法构建组件,store使用旧版 API
import { ComponentWithComputed } from 'miniprogram-computed'
// 导入 storeBindingsBehavior 方法实现组件和 Store 的关联
import { storeBindingsBehavior } from "mobx-miniprogram-bindings"
// 导入 Store
import { numStore } from '../../stores/numstore'
ComponentWithComputed({
behaviors: [storeBindingsBehavior],
data: {
a: 1,
b: 1,
sum: 2
},
watch: {
'a, b': function (a, b) {
this.setData({
total: a + b
})
}
},
computed: {
total(data) {
return data.a + data.b + data.sum
}
},
// 实现组件和 Store 的关联
storeBindings: {
store: numStore,
fields: ['numA', 'numB', 'sum'],
actions: ['update']
}
})

浙公网安备 33010602011771号