目录
1. 创建-注册-使用组件
1.1 创建自定义组件
1.2 使用自定义组件
2. 自定义组件-数据和方法
3. 自定义组件-属性
4. 组件 wxml 的 slot
5. 组件样式以及注意事项
6. 组件样式隔离
7. 数据监听器
8. 组件间通信与事件
8.1 父往子传值
8.2 子往父传值
8.3 获取组件实例
9. 组件生命周期
10. 组件所在页面的生命周期
11. 小程序生命周期总结
12. 拓展:使用 Component 构造页面
13. 拓展:behaviors
14. 拓展:外部样式类
1. 创建-注册-使用组件
小程序目前已经支持组件化开发,可以将页面中的功能模块抽取成自定义组件,以便在不同的页面中重复使用;
也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护
开发中常见的组件有两种:
公共组件:将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用
页面组件:将复杂的页面拆分成多个低耦合的模块,有助于代码维护
如果是公共组件,建议将其放在小程序的目录下的
components
文件夹中如果是页面组件,建议将其放在小程序对应页面目录下,当然你也可以放到页面的
components
文件夹中
同时建议:一个组件一个文件夹,文件夹名称和组件名称保持一致
📌 注意事项:
自定义组件的需要在
json
文件中需要配置component
字段设为true
自定义组件通过
Component
构造器进行构建,在构造器中可以指定组件的属性、数据、方法等
1.1 创建自定义组件
创建组件的步骤很简单,以公共组件为例,创建的步骤如下:
在小程序的目录下新建
components
文件夹在
components
文件夹上,点击右键,选择新建文件夹
,然后输入文件夹名称,我们建议文件夹的名称和组件的名称保持一致,这样方便后期对组件进行维护。我们这里新的的组件名称叫做:custom-checkbox
在新建的组件文件夹上,点击右键,选择
新建 Component
,然后输入组件的名称,组件的名称建议和文件夹保持一致此时就已经创建了一个功能组件
1.2使用自定义组件
开发中常见的组件主要分为 公共组件 和 页面组件 两种,因此注册组件的方式也分为两种:
全局注册:在
app.json
文件中配置usingComponents
节点进行引用声明,注册后可在任意组件使用局部注册:在页面的
json
文件中配置usingComponents
节点进行引用声明,只可在当前页面使用
在配置 usingComponents
节点进行引用声明时,需要提供自定义组件的标签名和对应的自定义组件文件路径,语法如下:
{
"usingComponents": {
"自定义组件的标签名": "自定义组件文件路径"
}
}
这样,在页面的 wxml
中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。
2. 自定义组件-数据和方法
在组件的 .js 中,需要调用 Component
方法创建自定义组件,Component
中有以下两个属性:
data
数据:组件的内部数据
methods
方法:在组件中事件处理程序需要写到 methods
中才可以
Component({
/**
* 组件的初始数据:用来定义当前组件内部所需要使用的数据
*/
data: {
isChecked: false
},
/**
* 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中
*/
methods: {
// 更新复选框的状态
updateChecked () {
this.setData({
isChecked: !this.data.isChecked
})
console.log(this.data.isChecked)
}
}
})
3. 自定义组件-属性
属性 Properties 是指组件的对外属性,主要用来接收组件使用者传递给组件内部的数据,和 data 一同用于组件的模板渲染
📌 注意事项:
设置属性类型需要使用 type 属性,属性类型是必填项,value 属性为默认值
属性类型可以为 String、Number、Boolean、Object、Array ,也可以为 null 表示不限制类型
Component({
+ /**
+ * 组件的属性列表:组件的对外属性,主要用来接收组件使用者传递给组件内部的属性以及数据
+ */
+ properties: {
+ // 如果需要接收传递的属性,有两种方式:全写、简写
+ // label: String
+
+ label: {
+ // type 组件使用者传递的数据类型
+ // 数据类型:String、Number、Boolean、Object、Array
+ // 也可以设置为 null,表示不限制类型
+ type: String,
+ value: ''
+ },
+
+ // 文字显示位置
+ position: {
+ type: String,
+ value: 'right'
+ }
+ },
/**
* 组件的初始数据:用来定义当前组件内部所需要使用的数据
*/
data: {
isChecked: false
},
/**
* 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中
*/
methods: {
// 更新复选框的状态
updateChecked () {
this.setData({
isChecked: !this.data.isChecked,
+ // label: '在组件内部也可以修改 properties 中的数据'
})
+ // 在 JS 中可以访问和获取 properties 中的数据
+ // 但是一般情况下,不建议修改,因为会造成数据流的混乱
+ // console.log(this.properties.label)
// console.log(this.data.isChecked)
}
}
})
4. 组件 wxml 的 slot
在使用基础组件时,可以给组件传递子节点传递内容,从而将内容展示到页面中,自定义组件也可以接收子节点内容
只不过在组件模板中需要定义 <slot />
节点,用于承载组件引用时提供的子节点
默认情况下,一个组件的 wxml 中只能有一个 slot 。需要使用多 slot 时,可以在组件 js 中声明启用。
同时需要给 slot 添加 name 来区分不同的 slot,给子节点内容添加 slot 属性来将节点插入到 对应的 slot 中
<view>
<slot name="slot-top" />
<!-- slot 就是用来接收、承载子节点内容 -->
<!-- slot 只是一个占位符,子节点内容会将 slot 进行替换 -->
<!-- 默认插槽 -->
<view><slot /></view>
<slot name="slot-bottom" />
</view>
5. 组件样式以及注意事项
选择器使用注意事项:
类似于页面,自定义组件拥有自己的
wxss
样式,组件对应wxss
文件的样式,只对组件wxml内的节点生效。编写组件样式时,需要注意以下几点:
app.wxss 或页面的 wxss 中使用了标签名(view)选择器(或一些其他特殊选择器)来直接指定样式 这些选择器会影响到页面和全部组件,通常情况下这是不推荐的做法
组件的页面不能使用 id 选择器(#a)不生效)、属性选择器([a])不生效)和 标签名选择器(会有警告),请改用 class 选择器
组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下(组件样式隔离设置为
shared
)会有非预期的表现,如遇,请避免使用子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件(text等)可能会出现样式失效的问题(意思是父节点是view用子选择器就能样式生效,如果父节点是其他非view组件,子选择器就会导致样式不生效)。
继承样式,如 font 、 color ,会从组件外继承到组件内。
除继承样式外, 全局中的样式、组件所在页面的的样式对自定义组件无效 (除非更改组件样式隔离选项)
6. 组件样式隔离
默认情况下,自定义组件的样式只受到自定义组件 wxss 的影响。除非以下两种情况
-
app.wxss
或页面的wxss
中使用了标签名(view)选择器(或一些其他特殊选择器)来直接指定样式,这些选择器会影响到页面和全部组件。通常情况下这是不推荐的做法。 -
指定特殊的样式隔离选项
styleIsolation
Component({
options: {
styleIsolation: 'isolated'
}
})
styleIsolation
选项它支持以下取值:
isolated
表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);
apply-shared
表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;
shared
表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了apply-shared
或shared
的自定义组件。
7. 数据监听器
数据监听器可以用于监听和响应任何属性和数据字段的变化,有时,需要在一些数据字段被 setData 设置时,需要执行一些操作。那么就可以使用 observers
数据监听器来实现。语法如下:
Component({
data: {
num: 10,
count: 1,
obj: { name: 'Tom', age: 10 },
arr: [1, 2, 3]
},
observers: {
// key 是需要检测数据
// value 是一个函数,函数接收一个形参作为参数,是最新的值
num: function(newNum) {
console.log(newNum)
},
// 数据监听器支持监听属性或内部数据的变化,可以同时监听多个
'num, count': function (newNum, newCount) {
console.log(newNum, newCount)
}
// 监听器可以监听子数据字段
'obj.age': function(newAge) {
console.log(newAge)
},
// 如果需要监听所有子数据字段的变化,可以使用通配符 **
'obj.**': function(newAge) {
console.log(newAge)
},
'arr[0]': function (val) {}
}
})
8. 组件间通信与事件
8.1 父往子传值
父组件如果需要向子组件传递指定属性的数据,在 WXML 中需要使用数据绑定的方式
与普通的 WXML 模板类似,使用数据绑定,这样就可以向子组件的属性传递动态数据。
父组件如果需要向子组件传递数据,只需要两个步骤:
1.在父组件 WXML 中使用 数据绑定 的方式向子组件传递动态数据
2.子组件内部使用 properties 接收父组件传递的数据即可
<!-- 引用组件的页面模板 -->
<view>
<costom prop-a="{{ name }}" prop-b="{{ age }}" />
</view>
//在组件内部,需要在 `Component` 构造器中通过 `properties` 接收传递的数据,接收方式有两种:
Component({
/**
* 组件的属性列表 props
*/
properties: {
propA: {
type: String, // 传递的数据类型
value: '' // 默认值
},
propB: Number // 简化的定义方式
},
})
在子组件中也可以通过
this.setData()
对properties
中的数据进行修改,但是一般不建议修改 (因为会造成数据流的混乱)
Component({
//在子组件中也可以通过 `this.setData()` 对 `properties` 中的数据进行修改,但是一般不建议修改
/**
* 组件的方法列表
*/
methods: {
// 修改列表中的数据
updateProp () {
this.setData({
propB: this.properties.propB + 1
})
}
}
})
8.2 子往父传值
子组件如果需要向父组件传递数据,可以通过小程序提供的事件系统实现传递传递,可以传递任意数据。
事件系统是组件间通信的主要方式之一,自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件,流程如下:
-
自定义组件触发事件时,需要使用
triggerEvent
方法发射一个自定义的事件 -
自定义组件标签上通过 bind 方法监听发射的事件
举例代码:
触发事件:
<!-- 在自定义组件中 -->
<button type="primary" plain bindtap="sendData">传递数据</button>
// components/custom05/custom05.js
Component({
// 组件的初始数据
data: {
num: 666
},
// 组件的方法列表
methods: {
// 将数据传递给父组件
sendData () {
// 如果需要将数据传递给父组件
// 需要使用 triggerEvent 发射自定义事件
// 第二个参数,是携带的参数
this.triggerEvent('myevent', this.data.num)
}
}
})
监听事件:
<view>{{ num }}</view>
<!-- 需要在自定义组件标签上通过 bind 方法绑定自定义事件,同时绑定事件处理函数 -->
<custom05 bind:myevent="getData" />
Page({
data: {
num: ''
},
getData (event) {
// 可以通过事件对象.detail 获取子组件传递给父组件的数据
// console.log(event)
this.setData({
num: event.detail
})
}
})
8.3 获取组件实例
在父组件里调用 this.selectComponent()
,获取子组件的实例对象,就可以直接拿到子组件的任意数据和方法。调用时需要传入一个匹配选择器 selector
,如:this.selectComponent(".my-component")
。
<!-- 父组件 -->
<costom bind:myevent="getData" class="custom" />
<button bindtap="getChildComponent"></button>
// 父组件
Page({
data: {},
getChildComponent: function () {
const child = this.selectComponent('.custom')
console.log(child)
}
})
9. 组件生命周期
组件的生命周期:指的是组件自身的一些钩子函数,这些函数在特定的时间节点时被自动触发
组件的生命周期函数需要在 lifetimes
字段内进行声明
最重要的生命周期是 created、
attached、
detached
包含一个组件生命周期流程的最主要时间点
定义段 | 描述 |
---|---|
created | 在组件实例刚刚被创建时执行,注意此时不能调用 setData (还没有对模板解析) |
attached | 在组件实例进入页面节点树时执行 (模板已经解析完毕,并且挂载到页面上) |
ready | 在组件布局完成后执行 |
moved | 在组件实例被移动到节点树另一个位置时执行 |
detached | 在组件实例被从页面节点树移除时执行 (组件被销毁了) |
【组件实例刚刚被创建好时】,
created
生命周期被触发。此时,组件数据this.data
就是在Component
构造器中定义的数据data
。 此时还不能调用setData
。 通常情况下,这个生命周期只应该用于给组件this
添加一些自定义属性字段。【在组件完全初始化完毕】进入页面节点树后,
attached
生命周期被触发。此时,this.data
已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。3.【在组件离开页面节点树后】,
detached
生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则detached
会被触发。
Component({
lifetimes: {
created: function () {
// 在组件实例刚刚被创建时执行,注意此时不能调用 setData
// 一般用来为组件添加一些自定义属性字段。
},
attached: function() {
// attached 在组件完全初始化完毕、进入页面节点树后执行
// 模板已经解析完毕,并且挂载到页面上
// 一般都是在这里写对应的交互
},
detached: function() {
// 在组件实例被从页面节点树移除时执行
},
// coding...
}
// coding...
})
10. 组件所在页面的生命周期
组件还有一些特殊的生命周期,这类生命周期和组件没有很强的关联
主要用于组件内部监听父组件的展示、隐藏状态,从而方便组件内部执行一些业务逻辑的处理
组件所在页面的生命周期有 4 个: show、 hide、 resize、 routeDone,需要在 pageLifetimes
字段内进行声明
// components/custom06/custom06.js
Component({
// coding...
// 组件所在页面的生命周期
pageLifetimes: {
// 监听组件所在的页面展示(后台切前台)状态
show () {
console.log('组件所在的页面被展示')
},
// 监听组件所在的页面隐藏(前台切后台、点击 tabBar)状态
hide () {
console.log('组件所在的页面被隐藏')
}
}
})
11. 小程序生命周期总结
小程序冷启动,钩子函数执行的顺序
保留当前页面(navigate) 以及 关闭当前页面(redirect)
切后台 以及 切前台(热启动)
12. 拓展:使用 Component 构造页面
Component 方法用于创建自定义组件
小程序的页面也可以视为自定义组件,因此页面也可以使用 Component 方法进行创建,从而实现复杂的页面逻辑开发
📌 注意事项:
要求对应 json 文件中包含 usingComponents 定义段
页面使用 Component 构造器创建,需要定义与普通组件一样的字段与实例方法
页面 Page 中的一些生命周期方法(如 onLoad() 等以“on”开头的方法),在 Component 中要写在 methods 属性中才能生效
组件的属性 Properties 可以用于接收页面的参数,在 onLoad() 中可以通过 this.data 拿到对应的页面参数
举例代码:
Component({
// 为什么需要使用 Component 方法进行构造页面
// Component 方法功能比 Page 方法强大很多
// 如果使用 Component 方法构造页面,可以实现更加复杂的页面逻辑开发
// 小程序页面也可以使用 Component 方法进行构造
// 注意事项:
// 1. 要求 .json 文件中必须包含 usingComponents 字段
// 2. 里面的配置项需要和 Component 中的配置项保持一致
// 3. 页面中 Page 方法有一些钩子函数、事件监听方法,这些钩子函数、事件监听方法必须方法 methods 对象中
// 4. 组件的属性 properties 也可以接受页面的参数,在 onLoad 钩子函数中可以通过 this.data 进行获取
properties: {
id: String,
title: String
},
data: {
name: 'tom'
},
// onLoad () {
// console.log('页面加载 - 1')
// },
methods: {
// 更新 name
updateName() {
this.setData({
name: 'jerry'
})
},
onLoad (options) {
// console.log('页面加载 - 2')
// console.log(options)
console.log(this.data.id)
console.log(this.data.title)
console.log(this.properties.id)
},
}
})
13. 拓展:behaviors
小程序的 behaviors 方法是一种代码复用的方式,可以将一些通用的逻辑和方法提取出来,然后在多个组件中复用,从而减少代码冗余,提高代码的可维护性。
如果需要 behavior 复用代码,需要使用 Behavior() 方法,每个 behavior 可以包含一组属性、数据、生命周期函数和方法
组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。
注册 behavior:
如果需要注册一个 behavior
,需要借助 Behavior()
方法,接受一个 Object
类型的参数
// my-behavior.js
module.exports = Behavior({
behaviors: [],
properties: {
myBehaviorProperty: {
type: String
}
},
data: {
myBehaviorData: 'my-behavior-data'
},
created: function () {
console.log('[my-behavior] created')
},
attached: function () {
console.log('[my-behavior] attached')
},
ready: function () {
console.log('[my-behavior] ready')
},
methods: {
myBehaviorMethod: function () {
console.log('[my-behavior] log by myBehaviorMehtod')
},
}
})
使用 behavior:
// my-component.js
const myBehavior = require('my-behavior')
Component({
behaviors: [myBehavior]
// coding...
})
组件和它引用的 behavior 中可以包含同名的字段,对这些字段的处理方法如下:
如果有同名的属性或方法,采用 “就近原则”,组件会覆盖 behavior 中的同名属性或方法
如果有同名的数据字段且都是对象类型,会进行对象合并(属性不相同的合并,相同的属性还是会采用 “就近原则” 进行数据覆盖)
生命周期函数和 observers 不会相互覆盖,会是在对应触发时机被逐个调用,也就是都会被执行
详细的规则:同名字段的覆盖和组合规则
14. 拓展:外部样式类
默认情况下,组件和组件使用者之间如果存在相同的类名不会相互影响,组件使用者如果想修改组件的样式,需要就解除样式隔离,但是解除样式隔离以后,在极端情况下,会产生样式冲突、CSS 嵌套太深等问题,从而给我们的开发带来一定的麻烦。
外部样式类:在使用组件时,组件使用者可以给组件传入 CSS 类名,通过传入的类名修改组件的样式。
如果需要使用外部样式类修改组件的样式,在 Component 中需要用 externalClasses 定义若干个外部样式类。
外部样式类的使用步骤:
1.在 Component 中用 externalClasses 定义段定义若干个外部样式类
2.自定义组件标签通过属性绑定的方式提供一个样式类,属性是 externalClasses 定义的元素,属性值是传递的类名
3.将接受到的样式类用于自定义组件内部
📌注意事项:
在同一个节点上使用普通样式类和外部样式类时,两个类的优先级是未定义的
因此需要添加 !important 以保证外部样式类的优先级
➡️ custom09.js
// components/custom09/custom09.js
Component({
// 组件接受的外部样式类
externalClasses: ['extend-class']
})
➡️ custom09.wxml
<!-- 在同一个节点上,如果存在外部样式类 和 普通的样式类 -->
<!-- 两个类的优先级是未定义的 -->
<!-- 建议:在使用外部样式类的时,样式需要通过 !important 添加权重 -->
<view class="extend-class box">通过外部样式类修改组件的样式</view>
➡️ custom09.wxss
.box {
color: lightseagreen;
}
➡️ profile.wxml
<!-- 属性是在 externalClasses 里面定义的元素 -->
<!-- 属性值必须是一个类名 -->
<custom09 extend-class="my-class" />
➡️ profile.wxss
/* pages/index/index.wxss */
.my-class {
color: lightsalmon !important;
}