【微信小程序篇-05】深入理解小程序的自定义组件
深入理解小程序的自定义组件
- 一,自定义组件
-
- 1,组件分类
- 2,自定义组件数据、方法和properties
- 3,自定义组件slot插槽
- 4,自定义组件样式
- 5,自定义组件样式隔离
- 6,自定义组件数据监听器observers
- 7,自定义组件通信
-
- 7.1,父向子通信传递数据
- 7.2,子向父进行通信传递数据
- 8,自定义组件的生命周期
-
- 8.1,组件的生命周期
- 8.2,页面的生命周期
- 9,Component构造页面
- 10,自定义组件复用机制behaviors
- 11,自定义组件外部样式类
- 12,案例的整体代码
一,自定义组件
本文主要讲解关于自定义组件的一些知识点,如何去自定义组件,实际开发操作的规范等。
1,组件分类
小程序中常用的组件主要有两种,分别是公共组件和页面组件,公共组件将页面的相同功能进行抽取,以便在不同的页面重复使用,页面组件将一个复杂的页面拆分成多个低耦合的模块,有助于代码维护。
- 公共组件需要全局注册,在app.json文件中配置usingComponents进行注册,注册后任意页面都能使用
- 局部注册需要在对应页面的json文件中配置usingComponents进行注册,注册后只能当前页面使用
注册局部页面的方式如下,一般放于Components目录下,在该目录下再创建Component
在usingComponents注册方式如下,需要提供自定义组件名和自定义组件文件路径,组件注册号之后,直接将组件当成一个组件标签使用即可。
\"usingComponents\": { \"navigation-bar\": \"/components/navigation-bar/navigation-bar\"}
2,自定义组件数据、方法和properties
组件数据和方法需要在组件.js的Components方法中定义,Components创建自定义组件
- data用于定义组件的内部数据
- methods用于组件中事件处理程序需要写到methods中才可以
- Properties是指组件的对外属性,主要用来接收组件使用者传递给组件内部的数据
在创建一个新的自定义组件之后,.js文件中的初始数据如下,通过Component关键字开头,里面定义了data、methods和properties,data中专门存数据,methods中专门存函数或者方法。
首先在Components目录下创建一个组件 custom-check ,然后将这个组件在app.js中注册成为全局自定义组件
\"usingComponents\": { \"custom-check\":\"/components/custom-check/custom-check\"}
custom-check.wxml文件改成一个单选框和文本
<view class=\"custom-checkbox-container\"> <view class=\"custom-checkbox-box {{position === \'right\' ? \'right\' : \'left\'}}\"> <checkbox class=\"custom-checkbox\" checked=\"{{isChecked}}\" bindtap=\"updateChecked\"></checkbox> <view> <text>{{label}}</text> </view> </view></view>
custom-check.scss中定义的样式如下
/* components/custom-check/custom-check.wxss */.custom-checkbox-container{ display: inline-block;}.custom-checkbox-box{ display: flex; align-items: center;}.custom-checkbox-box.left{ flex-direction: row-reverse;}.custom-checkbox-box.right{ flex-direction: row; }.custom-checkbox{ margin-left: 10rpx;}view{ height: 50rpx; display: flex; align-items: center; justify-content: center;}
custom-check.js中定义的properties、data和methods的内容如下
Component({ properties: { label:{ type:String, value:\'\' }, position:{ type:String, value:\'\' } }, data: { isChecked: false }, methods: { updateChecked(){ this.setData({ isChecked: !this.data.isChecked }) } }})
最后在index.wxml中将这个custom-check作为组件加载
<custom-check label=\"我已阅读并统一用户协议\" position=\"right\"/><view class=\"line\"></view><custom-check label=\"匿名提交\" position=\"left\"/>
最终实现的效果如下
3,自定义组件slot插槽
在自定义组件中,如果想给当前自定义结点中接收子结点信息,就需要定义slot结点,用于承载之间的子结点。一个组件只能有一个默认插槽,如果需要使用多个slot,那么就需要声明开启多个slot使用的支持,同时给每一个slot一个name名字进行区分。
options:{ multipleSlots: true}
默认插槽的使用如下,在自定义的组件中的.wxml文件下,定义一个插槽
<view> <slot></slot></view>
在使用这个自定义组件的wxml文件中,在组件内部插入一些文本,这样这个文本会自动的渲染到插槽中。
<custom-check> 我是一个插槽</custom-check>
如果需要使用多个插槽,那么首先还需要将custom-check自定义组件设置multipleSlots为true在对应的js文件中,随后再自定义组件的.wxml文件中定义多个slot,然后设置不同的name
<view> <slot name=\"top-slot\"></slot> <view></view> <slot name=\"bottom-slot\"></slot></view>
在使用这个custom-check子组件的index组件的.wxml内容如下,需要通过name绑定对应的slot
<custom-check> <text slot=\"top-slot\">我是顶部信息</text> <text slot=\"bottom-slot\">我是底部信息</text></custom-check>
4,自定义组件样式
自定义组件拥有自己的wxss样式,组件exss文件的样式,默认只对当前组件生效。需要注意的事项如下
1,组件和引用的页面不能直接使用id选择器,属性选择器和标签选择器,只能使用class选择器
.content{ ...}
2,子选择器只能用于view和子组件,用于其他组件样式可能失效
.content > .label{ ...}
3,如果使用的是子组件,那么子组件的样式都会被继承,例如color/font等
4,全局样式,组件所在页面的样式和文件中的样式都对自定义组件无效
5,不建议在全局样式以及父级页面之间使用标签选择器设置样式,如果是全局样式文件中设置样式,会影响项目中全部相同的组件,如果是在页面中设置样式,那么会影响当前页面所有相同的组件
6,组件和组件使用者如果使用了后代选择器,可能会出现一些非预期情况,需要设置别名
5,自定义组件样式隔离
主要是判断不同组件中相同类选择器的样式会不会相互影响,默认是隔离不会相互影响。需要在.js的options参数中开启这个styleIsolation属性:
- isolated:默认值,开启隔离样式,自定义组件和组件使用者存在相同类名时不会相互影响
- apply-shared :表示组件使用者和组件会相互影响,但是自定义组件样式不会影响组件使用者的样式
- shared:表示组件使用者和组件会相互影响,同时自定义组件样式会影响组件使用者的样式
options:{ // styleIsolation:\"apply-shared\", // styleIsolation:\"shared\", styleIsolation:\"isolated\"}
接下来还是修改上面这个 阅读并统一用户协议 和匿名提交 的案例,首先开启这个共享参数在custom-check.js中
options:{ styleIsolation: \"shared\"}
最后在custom-check.wxss中增加几个关于复选框的样式,修改选择前和选择后的样式
// 防止shared对其他页面产生影响.custom-checkbox .wx-checkbox-input{ width: 24rpx !important; height: 24rpx !important; border-radius: 50% !important; border: 1px solid #fda007 !important;}.custom-checkbox .wx-checkbox-input-checked{ background-color: #fda007 !important;}.custom-checkbox .wx-checkbox-input.wx-checkbox-input-checked:before{ font-size: 22rpx; color: #fff;}
最后的演示效果如下,分别是选择前,选择后和√的样式都发生了替换
6,自定义组件数据监听器observers
数据监听器主要用于监听和响应任何属性properties和数据data的变化,当数据发生变化时就会触发相应的回调,从而方便开发者进行业务逻辑的处理。主要是通过 observers 字段进行数据的监听
接下来还是在自定义组件中的custom-check.wxml 文件中定义以下组件
<view>{{id}}</view><view>{{obj.name}}</view><button type=\"warn\" bindtap=\"updateData\">更新数据</button>
在custom-check.js中定义以下内容,主要是通过这个 observers 来对数据监听
Component({ properties: { }, data: { id:1, obj:{\"name\":\"刘小丽\"} }, methods: { updateData(){ this.setData({ \'id\':this.data.id + 1, \'obj.name\':\'liuxiaoli111\' }) } }, observers:{ \'id\': function(newId){ console.log(\'id发生了变化,最新值为\' + newId) }, \'obj.name\': function(newName){ console.log(\'id发生了变化,最新值为\' + newName) } }})
其实现的对应效果如下,数据发生改变后,控制台也出现了相应的数据打印。
7,自定义组件通信
7.1,父向子通信传递数据
父向子通信传递数据比较简单,只需要父组件在wxml中使用组件时将数据动态绑定,然后子组件内部使用properties接收父组件的数据即可。
依旧是使用这个用户协议个匿名提交的这个案例,在用户统一给子组件的checked传true,匿名提交传false
<custom-check label=\"我已阅读并统一用户协议\" position=\"right\" checked=\"{{true}}\"/><view class=\"line\"></view><custom-check label=\"匿名提交\" position=\"left\" checked=\"{{false}}\"/>
custom-check.js的详细代码如下,需要在properties属性中接收这个checked数据,然后通过监听器监听这个checked最新的数据,然后再监听器中将数据赋值给data中的isChecked
Component({ options:{ styleIsolation: \"shared\" }, properties: {checked:{ type:Boolean, value:\'\' } }, data: { isChecked: false }, observers:{ checked:function(newData){ this.setData({ isChecked:newData }) } }})
其实现效果如下,刷新进入index页面后,自动的将用户协议勾选上了,匿名提交不勾选上。
7.2,子向父进行通信传递数据
子组件需要向父组件传递数据,可以通过小程序提供的事件系统实现,可以传递任意数据
- 首先自定义组件内部需要通过 triggerEvent 发射一个事件,并且在事件中可以携带参数
- 自定义组件标签上使用bind 方法监听,同时绑定事件处理函数,在事件处理函数中通过事件对象获取数据
子组件中绑定数据的方式如下,比如需要将子组件获取的isChecked再传给父组件,在methods中修改完isChecked数据之后,再通过triggerEvent发布一个事件,事件名称自定义,这里取值为currentIsChecked,参数就是data中的isChecked
methods: { updateChecked(){ this.setData({ isChecked: !this.data.isChecked }) this.triggerEvent(\'currentIsChecked\', this.data.isChecked) }}
在使用这个custom-check组件时,通过bind绑定这个子组件发布的事件,即bind:currentIsChecked,然后在value值中定义需要处理这个事件的函数,这里定义为dealIsChecked
<custom-check label=\"我已阅读并统一用户协议\" position=\"right\"bind:currentIsChecked=\"dealIsChecked\" checked=\"{{true}}\"/><view class=\"line\"></view><custom-check label=\"匿名提交\" position=\"left\"bind:currentIsChecked=\"dealIsChecked\" checked=\"{{false}}\"/>
在index.js文件中,直接定义这个处理事件即可,不需要定义在methods中
Page({ dealIsChecked(event){ console.log(event.detail) }})
其效果如下,这样点击勾选不和不勾选,父组件打印的值就会如下,就是父组件接收到的值
除了上面这种方式,父组件可以直接通过 this.selectComponent 方法获取子组件实例,这样父组件可以直接访问子组件的任意数据和方法。使用前需要再子组件中设置id或者class,比如设置class为child-check-box,
<custom-check label=\"我已阅读并统一用户协议\" position=\"right\"bind:currentIsChecked=\"dealIsChecked\" checked=\"{{true}}\" class=\"child-check-box\"/><view class=\"line\"></view><custom-check label=\"匿名提交\" position=\"left\"bind:currentIsChecked=\"dealIsChecked\" checked=\"{{false}}\" class=\"child-check-box\"/>
那么父组件就可以直接调用这个方法获取到子组件的所有数据和方法
Page({ dealIsChecked(event){ // console.log(event.detail) const res = this.selectComponent(\'.child-check-box\') console.log(res) }})
8,自定义组件的生命周期
8.1,组件的生命周期
自定义组件的生命周期指的是组件自身的一些钩子函数,这些函数会哦在一些特定的事件自动触发。什么周期函数需要再js文件中的lifetimes字段内进行声明。组件的生命周期主要有5个 created、attached、ready、moved、detached ,其中核心的有 created、attached、detached 这三个
新建一个custom1的自定义组件,并且将这个这个注册到app.js的全局文件中,在custom1.js文件的内容如下
Component({ // 组件的生命周期 lifetimes:{ created(){ console.log(\'created-组件创建成功\') }, attached(){ console.log(\'attached-组件挂载成功\') }, detached(){ console.log(\'detached-组件销毁成功\') } }})
需要注意的是,created方法里面不能直接通过setData修改属性值,需要在attached里面修改。但是created方法可以设置一些自定义属性,如设置一个test属性,那么其他的生命周期就可以用这个test属性
created(){ this.test = \'xxx\'}
8.2,页面的生命周期
组件除了上面那些固定的生命周期之外,还有一些比较特殊的生命周期,比如 show、hide等 ,在使用时,需要再pageLifetimes字段内声明
在custom1.js文件中,添加以下参数,来判断当前组件所在的页面是否被隐藏或者展示
pageLifetimes:{ show(){ console.log(\'组件所在的页面被展示\') }, hide(){ console.log(\'组件所在的页面被隐藏\') }}
9,Component构造页面
除了使用Page构造页面之外,还可以直接在Component构造文件,就是将.js文件的Page替换成Component
Component({...})
但是在使用自定义的Component构造页面时,需要注意如下注意事项
- .json文件中必须要包括 usingComponents 字段
- 里面的配置项一定要和Component中的配置项保持一致
- 页面中Page方法有一些钩子函数、事件监听方法等,必须写到methods对象中
- 组件的属性properties也可以接受页面传递过来的参数,可以直接通过this.data获取
Component方法功能会比Page方法强大,比如一些事件监听器等,从而可以实现更复杂的逻辑开发
10,自定义组件复用机制behaviors
就是一个简单的封装功能,将多个组件可能会用到的一些属性,data,methods,生命周期代码封装成一个公共的js文件,要用的时候导入这个文件即可。
再新注册一个custom2的组件,然后注册到全局文件中,在该目录下新建一个behavior.js文件,内容如下:之间这段就是公共部分,然后封装到一个Behavior中,最后export出去
const behavior = Behavior({ properties:{ label:{ type:String, value:\'\' } }, data:{ name:\'libai\', obj:{\'name\':\'zhansan\'} }, methods:{ updateName(){ this.setData({ name:\'sushi\' }) } }, lifetimes:{ attached(){ console.log(\'attached\') } }})export default behavior;
在customs2中使用时,其custom2.js文件内容如下
import behavior from \'./behavior\'Component({ behaviors:[behavior]})
custom2.wxml文件的内容如下
<view>{{name}}</view><view>{{obj.name}}</view>
需要值得注意的点有以下几点:
- 组件properties和behaviors存在相同属性,采用就近原则,使用组件内部的
- 组件data和behaviors存在相同属性,如果时对象类型,属性会进行合并,如果不是对象类型,就近原则展示
- 组件methods和behaviors存在相同方法,采用就近原则,使用组件内部的
- 组件生命周期和behaviors存在相同生命周期,两个都会执行
11,自定义组件外部样式类
在解除样式隔离的情况下,可能会出现极端的情况,会产生样式冲突,css嵌套太深等问题,从而给我们开发带来一定的麻烦,因此在使用组件时,组件使用者可以给组件传入一个css的类名,通过传入的类名修改组件的样式,并且在Component中需要使用 extermalClasses 定义若干个外部样式类
依旧使用这个custom2的这个组件,在index.wxml中使用这个custom2,在index.wxml中,
<custom2 exter-class=\"out-custom-css\"></custom2>
因此在
.out-custom-css{ color: coral;}
在custom2.js文件中,定义这个 externalClasses 数组,里面的value就是index使用者传的css的key
Component({ externalClasses:[\'exter-class\']})
在custom2.wxml文件中,直接写一段文本,加上传进来的class就能使用了
<view class=\"exter-class\">我是一段文本</view>
12,案例的整体代码
上面的案例代码可能只有部分代码,接下来这里提供完整的代码,其完整效果如下
index.wxml文件中的内容如下
<custom-check label=\"我已阅读并统一用户协议\" position=\"right\"bind:currentIsChecked=\"dealIsChecked\" checked=\"{{true}}\" class=\"child-check-box\"/><view class=\"line\"></view><custom-check label=\"匿名提交\" position=\"left\"bind:currentIsChecked=\"dealIsChecked\" checked=\"{{false}}\" class=\"child-check-box\"/>
index.wxss文件内容如下
view{ height: 50rpx; display: flex; align-items: center; justify-content: center;}.line{ height: 10rpx; background-color: aqua; margin-top: 40rpx; margin-bottom: 40rpx;}
custom-check.js的文件内容如下
Component({ options:{ styleIsolation: \"shared\" }, properties: { label:{ type:String, value:\'\' }, position:{ type:String, value:\'\' }, checked:{ type:Boolean, value:\'\' } }, data: { isChecked: false }, methods: { updateChecked(){ this.setData({ isChecked: !this.data.isChecked }) this.triggerEvent(\'currentIsChecked\', this.data.isChecked) } }, observers:{ checked:function(newData){ this.setData({ isChecked:newData }) } }})
custom.check.scss文件内容如下
/* components/custom-check/custom-check.wxss */.custom-checkbox-container{ display: inline-block;}.custom-checkbox-box{ display: flex; align-items: center;}.custom-checkbox-box.left{ flex-direction: row-reverse;}// 防止shared对其他页面产生影响.custom-checkbox .wx-checkbox-input{ width: 24rpx !important; height: 24rpx !important; border-radius: 50% !important; border: 1px solid #fda007 !important;}.custom-checkbox .wx-checkbox-input-checked{ background-color: #fda007 !important;}.custom-checkbox .wx-checkbox-input.wx-checkbox-input-checked:before{ font-size: 22rpx; color: #fff;}.custom-checkbox-box.right{ flex-direction: row; }.custom-checkbox{ margin-left: 10rpx;}view{ height: 50rpx; display: flex; align-items: center; justify-content: center;}
custom-check.wxml文件内容如下
<view class=\"custom-checkbox-container\"> <view class=\"custom-checkbox-box {{position === \'right\' ? \'right\' : \'left\'}}\"> <checkbox class=\"custom-checkbox\" checked=\"{{isChecked}}\" bindtap=\"updateChecked\"></checkbox> <view> <text>{{label}}</text> </view> </view></view>