【微信小程序】 自定义组件
创建微信小程序组件
在小程序中创建组件:
1、项目根目录中创建【components】目录,存放自定义组件
2、进入components目录,给组件创建一个组件目录
3、右键组件目录,选择【创建Components】
4、之后回车,会自动生成组件对应的4个文件, 后缀名分别为.js, .json, .wxml和.wxss
一、全局组件和局部组件
局部页面引用组件:
在【页面的.json】配置文件中引用组件的方式,叫做“局部引用”。
示例代码如下:
{ "usingComponents": { "test1": "/components/test1/test1" } }
全局引用组件:
在【app.json】中配置组件信息
"usingComponents": { "test1": "/components/test1/test1" }
使用组件:
<!-- 全局可以在任意页面中使用引入的组件, 局部组件只允许在引入的页面中使用 --> <test1></test1>
组件和页面的区别
从表面来看,组件和页面都是由.js、 json、 .wxml 和.WXSS这四个文件组成的。
但是,组件和页面的js与.json文件有明显的不同:
● 组件的 json文件中需要声明"component": true属性
● 组件的 js文件中调用的是Component()函数
● 组件的事件处理函数需要定义到methods节点中
二、组件之间的样式隔离问题:
一、组件样式隔离
默认情况下,自定义组件的样式只对当前组件生效,不会影响到组件之外的UI结构
● 组件A的样式不会影响组件C的样式 ● 组件A的样式不会影响小程序页面的样式 ● 小程序页面的样式不会影响组件A和C的样式
好处:
① 防止外界的样式影响组件内部的样式 ② 防止组件的样式破坏外界的样式
二、组件样式隔离的注意点
● app.wxss中的全局样式对组件无效 ● 只有class选择器会有样式隔离效果, id选择器、属性选择器、标签选择器不受样式隔离的影响
建议:在组件和引用组件的页面中建议使用class选择器,不要使用id、属性、标签选择器!
三、修改组件的样式隔离选项
默认情况下,自定义组件的样式隔离特性能够防止组件内外样式互相干扰的问题。
但有时,我们希望在外界能够控制组件内部的样式,
此时,可以通过stylelsolation 修改组件的样式隔离选项,
用法如下:
// components/test1/test1.js Component({ options: { styleIsolation: 'isolated' } }) // components/test1/test1.json { "component": true, "usingComponents": {}, "styleIsolation": "isolated" }
四、 stylelsolation的可选值
isolated 表示启用样式隔离,在自定义组件内外,使用class指定的样式将不会相互影响 apply-shared 表示页面WXSs样式将影响到自定义组件,但自定义组件wxSS中指定的样式不会影响页面
shared 表示页面WxSs样式将影响到自定义组件, 自定义组件Wxss中指定的样式也会影响页面和其他设置了apply-shared或shared的自定义组件
三、Data属性 和 Method方法
和页面一样设置
/** * 组件的初始数据 */ data: { count: 0 },
方法是独立的一个对象存放
/** * 组件的方法列表 * 在这里编写自定义方法,和事件处理函数 * 自定义方法推荐使用下划线前缀标记 * 如: * addCount() // 事件执行方法 * _showCount() // 自定义方法 */ methods: { addCount() { this.setData({ count: this.data.count + 1}) this._showCount() }, _showCount() { wx.showToast({ icon: 'none', title: `count值:${this.data.count}` }) } }
四、Properties属性
/** * 组件的属性列表 * 用来接收外部页面或者其他组件传递进来的属性 */ properties: { // 和vue一样的写法 max: { type: Number, value: 10 // 默认值 } /** * 简化写法,不设置默认值 * max: Number */ },
组件使用时可以设置值
<test1 max="{{5}}"/>
使用propertis数据
等于max值时无法继续叠加
addCount() { if (this.data.count === this.properties.max) return this.setData({ count: this.data.count + 1}) this._showCount() },
五、data和properties的区别
在小程序的组件中,properties 属性和data数据的用法相同,它们都是可读可写的,只不过:
● data更倾向于存储组件的私有数据 ● properties更倾向于存储外界传递到组件中的数据
两者的地址一致:
this.data === this.properties // true
给properties属性赋值
// 为properties的数据重新赋值 this.setData({ max: this.properties.max + 1 })
六、数据监听器
监听器功能再页面不支持
/** * 组件的数据监听器 */ observers: { // 可以同时监听多个属性, 入参的参数对应属性个数,表示属性更新后的值 'attr1, attr2': function(newValue1, newValue2) { // todo ... } },
演示案例:
组件HTML代码:
<!--components/test2/test2.wxml--> <text>components/test2/test2.wxml</text> <view> {{n1}} + {{n2}} = {{sum}} </view> <button size="mini" type="primary" bindtap="addN1">n1增加</button> <button size="mini" type="primary" bindtap="addN2">n2增加</button>
组件JS代码:
// components/test2/test2.js Component({ /** * 组件的数据监听器 */ observers: { 'n1, n2': function(n1Value, n2Value) { this.setData({ sum: n1Value + n2Value }) } }, /** * 组件的属性列表 */ properties: { }, /** * 组件的初始数据 */ data: { n1: 0, n2: 0, sum: 0 }, /** * 组件的方法列表 */ methods: { addN1() { this.setData({ n1: this.data.n1 + 1 }) }, addN2() { this.setData({ n2: this.data.n2 + 1 }) } } })
演示案例2:
一个RGB颜色案例,HTML代码
<!--components/rgbView/rgbView.wxml--> <text>components/rgbView/rgbView.wxml</text> <view class="colorBox" style="background-color: rgb({{fullColor}});"> 颜色值:{{fullColor}} </view> <button size="mini" bindtap="changeR" >R</button> | <button size="mini" bindtap="changeG" >G</button> | <button size="mini" bindtap="changeB" >B</button> <view> {{rgb.r}}, {{rgb.g}}, {{rgb.b}} </view>
JS代码:
// components/rgbView/rgbView.js Component({ observers: { // 监听,然后重新赋值 'rgb.**': function(newRgb) { this.setData({ fullColor: `${newRgb.r}, ${newRgb.g}, ${newRgb.b}` }) } }, /** * 组件的属性列表 */ properties: { }, /** * 组件的初始数据 */ data: { rgb: { r: 0, g: 0, b: 0 }, fullColor: '0, 0, 0' }, /** * 组件的方法列表 */ methods: { changeR() { let rValue = this.data.rgb.r + 5 rValue = rValue > 255 ? 255 : rValue this.setData({ 'rgb.r': rValue }) }, changeG() { let gValue = this.data.rgb.g + 5 gValue = gValue > 255 ? 255 : gValue this.setData({ 'rgb.g': gValue }) }, changeB() { let bValue = this.data.rgb.b + 5 bValue = bValue > 255 ? 255 : bValue this.setData({ 'rgb.b': bValue }) } } })
RGB显示板样式:
/* components/rgbView/rgbView.wxss */ .colorBox { line-height: 200rpx; font-size: 24rpx; color: white; text-shadow: 0rpx 0rpx 2rpx black; text-align: center; }
七、纯数据字段
1.什么是纯数据字段
概念:纯数据字段指的是那些不用于界面渲染的data字段。
应用场景:例如有些情况下,某些data中的字段既不会展示在界面上,也不会传递给其他组件,仅仅在当前
组件内部使用。带有这种特性的data字段适合被设置为纯数据字段。
好处:纯数据字段有助于提升页面更新的性能。
2.使用规则
在Component构造器的options节点中,指定pureDataPattern为一个正则表达式,
字段名符合这个正则表达式的字段将成为纯数据字段,示例代码如下:
// components/rgbView/rgbView.js Component({ options: { // 自定义正则规则来匹配纯数据字段,演示为首字符下划线的属性 pureDataPattern: /^_/ } }
把之前的JS代码的rgb属性名改为_rgb使用即可:
// components/rgbView/rgbView.js Component({ options: { // 自定义正则规则来匹配纯数据字段,演示为首字符下划线的属性 pureDataPattern: /^_/ }, observers: { // 监听,然后重新赋值 '_rgb.**': function(newRgb) { this.setData({ fullColor: `${newRgb.r}, ${newRgb.g}, ${newRgb.b}` }) } }, /** * 组件的属性列表 */ properties: { }, /** * 组件的初始数据 */ data: { _rgb: { r: 0, g: 0, b: 0 }, fullColor: '0, 0, 0' }, /** * 组件的方法列表 */ methods: { changeR() { let rValue = this.data._rgb.r + 5 rValue = rValue > 255 ? 255 : rValue this.setData({ '_rgb.r': rValue }) }, changeG() { let gValue = this.data._rgb.g + 5 gValue = gValue > 255 ? 255 : gValue this.setData({ '_rgb.g': gValue }) }, changeB() { let bValue = this.data._rgb.b + 5 bValue = bValue > 255 ? 255 : bValue this.setData({ '_rgb.b': bValue }) } } })
八、组件的生命周期函数
全部的钩子函数
// components/rgbView/rgbView.js Component({ lifetimes: { // 创建完成后执行 created() {}, // 组件实例和页面完成dom绑定后执行,等同vue的mounted attached() {}, // 组件再页面的视图渲染完成 ready() {}, // 组件被更新后执行(组件实例移动到节点树的新位置) moved() {}, // 组件从页面的dom树中移除时触发 detached() {}, // 组件发生异常触发 error() {} } })
主要的三个函数
2.组件主要的生命周期函数
在小程序组件中,最重要的生命周期函数有3个,分别是created、attached、 detached。 它们各自的特点
如下:
① 组件实例刚被创建好的时候,
created生命周期函数会被触发 ● 此时还不能调 用setData ● 通常在这个生命周期函数中, 只应该用于给组件的this添加一些自定义的属性字段 ② 在组件完全初始化完毕、
进入页面节点树后,attached 生命周期函数会被触发
此时,this.data 已被初始化完毕 ● 这个生命周期很有 用,绝大多数初始化的工作可以在这个时机进行( 例如发请求获取初始数据)
③ 在组件离开页面节点树后,detached生命周期函数会被触发 ● 退出一个页面时,会触发页面内每个自定义组件的detached生命周期函数 ● 此时适合做一些清理性质的工作
九、所属页面的生命周期函数
1.什么是组件所在页面的生命周期
有时,自定义组件的行为依赖于页面状态的变化,此时就需要用到组件所在页面的生命周期。
// components/rgbView/rgbView.js Component({ pageLifetimes: { // 所引用的页面被展示时执行 show() {}, // 所引用的页面被隐藏时执行 hide() {}, // 所引用的页面被尺寸变化时执行, 可以获取size大小 resize(size) {}, } })
演示案例,在颜色组件中编写一个随机颜色生成的方法:
/** * 组件的方法列表 */ methods: { /** * 生成随机色彩 */ _randomColor() { this.setData({ _rgb: { r: Math.floor(Math.random() * 256), g: Math.floor(Math.random() * 256), b: Math.floor(Math.random() * 256) } }) }, }
在页面渲染展示的周期时刻调用:
pageLifetimes: { // 所引用的页面被展示时执行 show() { // 在引用页面展示时触发 this._randomColor() } }
十、Slot 组件插槽
单个插槽
编写一个组件
<!--components/test4/test4.wxml--> <text>components/test4/test4.wxml</text> <view> <view>这是组件的内部结构</view> <slot></slot> </view>
使用组件:
<test4> <text>这是嵌入组件插槽的部分</text> </test4>
多个插槽
<view> <view>这是组件的内部结构1</view> <slot name="before"></slot> <view>这是组件的内部结构2</view> <slot name="after"></slot> <view>这是组件的内部结构3</view> </view>
微信新版本需要追加JS配置项:
黑马教程演示没有,说明在老版本可以直接配置
// components/test4/test4.js Component({ options: { // 开启多插槽支持 multipleSlots: true, } }
使用时需要指明名称:
<test4> <text slot="before">这是嵌入组件插槽的部分1</text> <text slot="after">这是嵌入组件插槽的部分2</text> </test4>
十一、组件之间通信问题
1.父子组件之间通信的3种方式
①属性绑定 ●用于父组件向子组件的指定属 性设置数据,仅能设置JSON兼容的数据 ②事件绑定 ● 用于子组件向父组件传递数据,可以传递任意数据 ③获取组件实例 ● 父组件 还可以通过this.selectComponent()获取子组件实例对象 ● 这样就可以直 接访问子组件的任意数据和方法
一、父组件(页面) 向 子组件传递数据:
<father counter="{{val1}}" /> <view> father组件 counter值: {{val1}}</view>
Father组件接参:
// components/father/father.js Component({ /** * 组件的属性列表 */ properties: { counter: { type: Number } } })
二、子组件向父组件传递数据
3.事件绑定
事件绑定用于实现子向父传值,可以传递任何类型的数据。使用步骤如下:
① 在父组件的js中,定义一个函数,这个函数即将通过自定义事件的形式,传递给子组件 ② 在父组件的wxml中,通过自定义事件的形式,将步骤1中定义的函数引用,传递给子组件 ③ 在子组件的js中,通过调用this.triggerEvent(自定义事件名称',{/*参数对象*/ }) ,将数据发送到父组件 ④ 在父组件的js中,通过e.detail获取到子组件传递过来的数据
1、Father组件先追加事件和展示
<!--components/father/father.wxml--> <text>components/father/father.wxml</text> <view>展示Father组件 counter值:{{counter}}</view> <button bindtap="addCounter">+1</button>
JS代码,使用事件触发器配置事件和传递值:
/** * 组件的方法列表 */ methods: { addCounter() { this.setData({ counter: this.properties.counter + 1}) // 给父组件进行同步 this.triggerEvent('sync', { value: this.properties.counter}) } }
页面使用组件时,绑定对应的接收方法:
<father counter="{{val1}}" bind:sync="syncCounter" /> <view> father组件 counter值: {{val1}}</view>
方法编写:
syncCounter(event) { // 接收从子组件发送过来的参数 this.setData({ val1: event.detail.value }) },
三、直接获取组件实例实现调用
可在父组件里调用this.selectComponent("id或class选择器"),获取子组件的实例对象,
从而直接访问子组件的任意数据和方法。
调用时需要传入一-个选择器,例如this.selectComponent(".my-component")。
<father counter="{{val1}}" bind:sync="syncCounter" class="cf" id="componentFather" /> <view> father组件 counter值: {{val1}}</view> <button bindtap="getComponentFather"> 测试</button>
JS代码:
现版本拿不到组件的方法,data属性可以获取
/** * 直接获取组件实例 */ getComponentFather() { // 或者id选择器 this.selectComponent('#componentFather') // 或者类选择器 this.selectComponent('.cf') const father = this.selectComponent('.cf') console.log(father) console.log(father.data.counter) // 拿不到组件方法? 清空缓存,刷新页面再尝试 father.addCounter() },
十二、behaviors
1.什么是behaviors
behaviors是小程序中,用于实现组件间代码共享的特性,类似于Vue.js中的"mixins"
2. behaviors的工作方式
每个behavior可以包含一组属性、 数据、生命周期函数和方法。
组件引用它时,它的属性、数据和方法会被合并到组件中。
每个组件可以引用多个behavior, behavior 也可以引用其它behavior。
3.创建behavior
调用Behavior(Object object)方法即可创建一一个共享 的behavior实例对象,供所有的组件使用:
// /behaviors/bhvirs1.js module.exports = Behavior({ // 属性 properties: {}, // 数据 data: { msg: 'Hello Behaviors!'}, // 方法 methods: { } })
4.导入并使用behavior
在组件中,使用require()方法导入需要的behavior,
挂载后即可访问behavior中的数据或方法,
示例代码如下:
// components/father/father.js const b1 = require('../../behaviors/bhvirs1') Component({ behaviors: [b1], })
组件可以展示Behaviors中的值
<view>展示Behvior数据 {{msg}}</view>
对应的所有设置属性:
冲突问题,见官方文档描述
https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html