文章目录
微信小程序-自定义组件
概述
小程序目前已经支持组件化开发,可以将页面中的功能模块抽取成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。
创建和使用
创建流程:
一、在根目录下新建 components 文件夹。
二、在 components 文件夹内,新建组件的文件夹,再新文件内新建组件,文件夹名与组件名保持一致。
三、组件可分2种:
- 公共组件:
- 将功能模块抽象成自定义组件,方便在不同页面中重复使用。
- 建议放在根目录下的 components 文件夹中。
- 页面组件:
- 将复杂的页面拆分成多个低耦合的模块,方便代码维护。
- 建议放在对应的页面目录下的 components 文件夹中。
四、IDE 会自动在 json 文件中配置 component 选项:
{
"component": true
}
五、IDE 会自动在 js 文件中定义 Component() 函数:
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
}
})
使用流程;
-
全局注册:在 app.json 文件中配置 usingComponents 选项,可以全局调用。
{ "usingComponents": { "my-custom-checkbox": "./components/my-custom-checkbox/my-custom-checkbox" } }
-
局部注册:在页面的 json 文件中配置 usingComponents 选项,只能在当前页面中使用。
{ "usingComponents": { "my-custom-checkbox": "../../components/my-custom-checkbox/my-custom-checkbox" } }
数据、方法和属性
Component 包含3个属性:
- data:组件内部的数据。
- methods:组件中的事件处理。
- properties:组件的对外属性
- 用于接收外部传递的数据。
- 支持类型:String / Number / Boolean / Object / Array / null(不限制类型)
定义组件的WXML:
<view class="custom-checkbox-container">
<view class="custom-check-box {{position ==='right'?'right':'left'}}">
<checkbox class="custom-checkbox" checked="{{isChecked}}" bindtap="updateChecked" />
<view>
<text>{{label}}</text>
</view>
</view>
</view>
定义组件的JS:
Component({
properties: {
// 标题
// 可简写为 label: String
label: {
type: String,
value: "",
},
// 文字显示位置
position: {
type: String,
value: "right",
},
},
data: {
isChecked: false,
},
methods: {
updateChecked() {
this.setData({
isChecked: !this.data.isChecked,
})
console.log(this.data.isChecked)
},
},
})
使用:
<my-custom-checkbox label="我已阅读并同意 用户协议 和 隐私协议" position="right" />
<my-custom-checkbox label="匿名提交" position="left" />
效果图:
slot 插槽
默认插槽
在使用基础组件时,可以给组件传递子节点传递内容,从而将内容展示到页面中,自定义组件也可以接收子节点内容。
只不过在组件模板中需要定义 <slot />
节点,用于承载组件引用时提供的子节点。
定义默认插槽组件:
<view>
<text>hello</text>
<slot />
<text>world</text>
</view>
使用默认插槽:
<my-custom01>这是一个默认插槽</my-custom01>
效果如下:
具名插槽
默认情况下,一个组件的 wxml 中只能有一个 slot 。需要使用多 slot 时,可以在组件 js 中声明启用。
同时需要给 slot 添加 name 来区分不同的 slot,给子节点内容添加 slot 属性来将节点插入到 对应的 slot 中。
开启多插槽支持:
Component({
options: {
// 开启多slot支持
multipleSlots: true,
}
})
定义具名插槽:
<view>
<slot name="slot-top" />
<text>hello slot</text>
<slot />
<slot name="slot-bottom" />
</view>
使用具名插槽:
<my-custom02>
<text slot="slot-top">自定义头部</text>
这是一个具名插槽
<text slot="slot-bottom">自定义尾部</text>
</my-custom02>
效果如下:
组件样式
类似于页面,自定义组件拥有自己的 wxss
样式,组件对应 wxss
文件的样式,只对组件wxml内的节点生效。
注意项
- 不推荐使用标签名选择器,会影响页面和全部组件。
- 不能使用 id 选择器、属性选择器。
- 避免使用后代选择器、子元素选择器,可能出现非预期效果。
- 建议使用 class 选择器。
- 继承样式,如 font、color,会从组件外继承到组件内。
- 全局样式、页面样式对自定义组件无效。
样式隔离
默认情况下,自定义组件的样式只受到自定义组件的 wxss 影响。
以下2种情况则受影响:
-
全局样式、页面样式使用标签选择器,这种情况下不推荐使用。
-
指定样式隔离选项:
Component({ options: { styleIsolation: 'isolated' } })
styleIsolation 选项支持以下取值:
isolated
:默认值,开启样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响。apply-shared
:表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面。shared
:表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了apply-shared
或shared
的自定义组件。
说明:
定义组件和组件样式:
<text class="label">这是一个组件样式</text>
.label {
color: green;
font-size: 30rpx;
}
定义页面和页面样式:
<my-custom03 />
<text class="label">这是一个页面样式</text>
.label{
color:red;
font-size: 50rpx;
}
当设置 styleIsolation: 'isolated'
,会开启组件隔离,效果如下:
当设置 styleIsolation: 'shared'
,会开启样式共享,效果如下:
数据监听
可以通过 observers 选项监听数据和属性的变化。
<view>num:{{num}}</view>
<view>count:{{count}}</view>
<view>
arr:<text wx:for="{{arr}}" wx:key="index">{{item}}</text>
</view>
<view>user:{{user.name}}-{{user.age}}</view>
<button bindtap="updateData">修改数据</button>
Component({
data: {
num: 1,
count: 10,
arr: [1, 2, 3],
user: { name: "小明", age: 18 },
},
observers: {
// 监听数据
num: function (newNumber) {
console.log("监听num " + newNumber)
},
// 监听多个数据
"num,count": function (newNum, newCount) {
console.log(`监听多个数据 ${newNum} ${newCount}`)
},
// 监听数组
"arr[0]": function (newVal) {
console.log(`监听数组 ${newVal}`)
},
// 监听对象
"user.age": function (newAge) {
console.log(`监听对象属性 ${newAge}`)
},
// 监听所有属性变化,使用通配符
"user.**": function (obj) {
console.log(`监听所有属性变化 ${obj.name} ${obj.age}`)
},
},
methods: {
// 修改数据
updateData() {
this.setData({
num: this.data.num + 1,
count: this.data.count + 1,
"arr[0]": this.data.arr[0] + 1,
"user.age": this.data.user.age + 1,
"user.name": this.data.user.name + this.data.num,
})
},
},
})
组件间通信
父传子
- 在父组件 wxml 中使用数据绑定的方式向子组件传递数据。
- 在子组件中使用 properties 接收父组件的数据。
定义子组件:
Component({
properties: {
propName: {
type: String,
value: "",
},
propAge: {
type: Number,
value: 0,
},
},
data: {
user: { name: "未命名", age: 0 },
},
observers: {
propName: function (newName) {
this.setData({
"user.name": newName,
})
},
propAge: function (newAge) {
this.setData({
"user.age": newAge,
})
},
},
})
<text>{{user.name}} - {{user.age}}</text>
在父组件中使用:
<my-son prop-name="小白" prop-age="18" />
子传父
- 子组件通过 triggerEvent 发送事件。
- 通过 bind 方法监听事件。
定义子组件:
<text>{{user.name}} - {{user.age}}</text>
<button bindtap="sendData">子组件发送数据</button>
Component({
methods: {
sendData() {
// 第一个参数:事件名
// 第二个参数:传递的数据
this.triggerEvent("myevent", "hello world " + Date.now())
},
},
})
定义父组件:
// 绑定事件方法
<my-son bind:myevent="getData" prop-name="小白" prop-age="18" />
<view>父组件接收数据:{{msg}}</view>
Page({
data: { msg: "123" },
getData(event) {
this.setData({
// 通过detail获取数据
msg: event.detail,
})
},
})
获取子组件实例
在父组件中可以通过 selectComponent() 获取子组件的实例。
<my-son id="child" bind:myevent="getData" prop-name="小白" prop-age="18" />
<button bindtap="getChild">获取子组件实例</button>
Page({
getChild() {
const child = this.selectComponent("#child")
console.log(child.data.user) // {name: "小白", age: 18}
},
})
生命周期
组件的生命周期
组件的生命周期,指的是组件自身的一些函数,这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发。
其中,最重要的生命周期是 created
attached
detached
,包含一个组件实例生命流程的最主要时间点。
组件生命周期:
- created:组件被创建时执行,此时不能调用 setData()。
- attached:组件实例进入页面节点树时执行。
- ready:组件布局完成后执行。
- moved:组件被移到到另一个位置时执行。
- detached:组件被移除时执行。
Component({
lifetimes: {
created() {
console.log("Component created")
},
attached() {
console.log("Component attached")
},
ready() {
console.log("Component ready")
},
moved() {
console.log("Component moved")
},
detached() {
console.log("Component detached")
},
},
})
组件所在页面的生命周期
主要用于组件内部监听父组件的展示、隐藏状态,从而方便组件内部执行一些业务逻辑的处理。
组件所在页面的生命周期有 4 个: show、 hide、 resize、 routeDone,需要在 pageLifetimes
字段内进行声明。
组件所在页面的生命周期:
- show:页面处于显示状态时执行。如后台切换。
- hide:页面处于隐藏状态时执行。如前台切换。
Component({
pageLifetimes: {
show() {
console.log("Page show()")
},
hide() {
console.log("Page hide()")
},
},
})
App、Page与Component生命周期对比
冷启动
保留当前页面和关闭当前页面跳转
切换后台和切换前台
Component构建页面
Component 可以用于构建页面,适合更复杂的页面逻辑开发。
注意事项
- json 文件必须包含 usingComponent 选项。
- Page 中的一些生命周期方法,必须放在 Component 的 methods 中。
- 组件的属性 Properties 可以用于接收页面的参数,在 onLoad() 中可以通过 this.data 拿到对应的页面参数
Component({
properties: {
name: String,
age: Number,
},
data: {
name: "",
age: 0,
},
methods: {
onLoad(options) {
console.log(this.data.name)
console.log(this.data.age)
console.log(this.properties.name)
console.log(this.properties.age)
},
updateData() {
this.setData({
age: this.data.age + 1,
})
},
},
})
使用
Component({
properties: {
name: String,
age: Number,
},
data: {
name: "",
age: 0,
},
methods: {
onLoad(options) {
console.log(this.data);
console.log(this.data.name)
console.log(this.data.age)
console.log(this.properties.name)
console.log(this.properties.age)
},
updateData() {
this.setData({
age: this.data.age + 1,
})
},
},
})
<navigator url="/pages/detail/detail?name=小花&age=20">跳转detail页面</navigator>
behaviors
behaviors 方法是一种代码复用的方式,可以将一些通用的逻辑和方法提取出来,然后在多个组件中复用,从而减少代码冗余,提高代码的可维护性。
创建behaviors并导出:
const behavior = Behavior({
properties: {
label: {
type: String,
value: "hell world",
},
},
data: {
name: "张三",
age: 28,
},
methods: {
updateName() {
this.setData({
name: "李四",
})
},
},
})
export default behavior
引入并使用:
import behavior from "../../utils/behaviors.js"
Component({
behaviors: [behavior]
})
修改组件默认样式
样式覆盖
方法:查找官方文档,找到案例,使用审查元素查看对应的类名。
可以看到3个信息:
- .wx-checkbox-input:复选框默认样式。
- .wx-checkbox-input-checked:复选框选中样式。
- .wx-checkbox-input.wx-checkbox-input-checked:before:复选框前面✔的样式。
需要先开启 styleIsolation: "shared"
选项。
.wx-checkbox-input {
border-radius: 50% !important;
border: 1px solid blue !important;
}
.wx-checkbox-input-checked {
border: 1px solid red !important;
}
.wx-checkbox-input.wx-checkbox-input-checked:before {
color: red !important;
}
外部样式类
默认情况下,组件和组件使用者之间如果存在相同的类名不会相互影响。
开发者如果想修改组件的样式,需要就解除样式隔离,在极端情况下,会产生样式冲突。
使用外部样式类步骤:
- 开启 externalClasses 选项,可以定义多个外部样式类。
- 在自定义组件中使用 class 属性绑定一个样式类,属性值是传递的类名。
- 在父组件中使用样式类。
定义组件:
Component({
// 定义外部样式类
externalClasses: ["extend-class"],
})
// 属性绑定
<view class="extend-class">
hello world 外部样式
</view>
使用外部样式:
// 使用样式
<my-external-classes extend-class="my-box" />
.my-box {
border: 2rpx solid red !important;
}
效果如下: