一、基础组件
框架为开发者提供了一系列基础组件(包括视图容器、内容、表单、操作反馈、导航、媒体、地图、画布等8类),开发者可以通过组合这些基础组件进行快速开发。详细介绍请参考组件文档。
什么是组件:
· 组件是视图层的基本组成单元。
· 组件自带一些功能与微信风格一致的样式。
· 一个组件通常包括 开始标签 和 结束标签,属性 用来修饰这个组件,内容 在两个标签之内。
<tagname property="value">Content goes here ...</tagname>
注意:所有组件与属性都是小写,以连字符-连接
属性类型
类型 | 描述 | 注解 |
---|---|---|
Boolean | 布尔值 | 组件写上该属性,不管是什么值都被当作 true;只有组件上没有该属性时,属性值才为false。 如果属性值为变量,变量的值会被转换为Boolean类型 |
Number | 数字 | 1, 2.5 |
String | 字符串 | "string" |
Array | 数组 | [ 1, "string" ] |
Object | 对象 | { key: value } |
EventHandler | 事件处理函数名 | "handlerName" 是 Page 中定义的事件处理函数名 |
Any | 任意属性 |
公共属性
所有组件都有以下属性:
属性名 | 类型 | 描述 | 注解 |
---|---|---|---|
id | String | 组件的唯一标示 | 保持整个页面唯一 |
class | String | 组件的样式类 | 在对应的 WXSS 中定义的样式类 |
style | String | 组件的内联样式 | 可以动态设置的内联样式 |
hidden | Boolean | 组件是否显示 | 所有组件默认显示 |
data-* | Any | 自定义属性 | 组件上触发的事件时,会发送给事件处理函数 |
bind* / catch* | EventHandler | 组件的事件 | 详见事件 |
特殊属性
几乎所有组件都有各自定义的属性,可以对该组件的功能或样式进行修饰,请参考各个组件的定义。
二、自定义组件
从小程序基础库版本 1.6.3 开始,小程序支持简洁的组件化编程。所有自定义组件相关特性都需要基础库版本 1.6.3 或更高。
开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。
1.创建自定义组件
类似于页面,一个自定义组件由 json wxml wxss js 4个文件组成。要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(将 component 字段设为 true 可这一组文件设为自定义组件):
{
"component": true
}
同时,还要在 wxml 文件中编写组件模板,在 wxss 文件中加入组件样式,它们的写法与页面的写法类似。具体细节和注意事项参见 组件模板和样式 。
代码示例:
<!-- 这是自定义组件的内部WXML结构 --> <view class="inner">{{innerText}}</view> <slot></slot>
/* 这里的样式只应用于这个自定义组件 */
.inner { color: red; }
注意:在组件wxss中不应使用ID选择器、属性选择器和标签名选择器。
在自定义组件的 js 文件中,需要使用 Component() 来注册组件,并提供组件的属性定义、内部数据和自定义方法。
组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的。更多细节参见 Component构造器 。
代码示例:
Component({ properties: { // 这里定义了innerText属性,属性值可以在组件使用时指定 innerText: { type: String, value: 'default value', } }, data: { // 这里是一些组件内部数据 someData: {} }, methods: { // 这里是一个自定义方法 customMethod() {} } })
2.使用自定义组件
使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:
{
"usingComponents": {
"component-tag-name": "path/to/the/custom/component"
}
}
这样,在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。
代码示例:
<view> <!-- 以下是对一个自定义组件的引用 --> <component-tag-name inner-text="Some text"></component-tag-name> </view>
自定义组件的 wxml 节点结构在与数据结合之后,将被插入到引用位置内。
3.细节注意事项
一些需要注意的细节:
· 因为 WXML 节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。
· 自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用 usingComponents 字段)。
· 自定义组件和页面所在项目根目录名不能以“wx-”为前缀,否则会报错。
注意,是否在页面文件中使用 usingComponents 会使得页面的 this 对象的原型稍有差异,包括:
· 使用 usingComponents 页面的原型与不使用时不一致,即 Object.getPrototypeOf(this) 结果不同。
· 使用 usingComponents 时会多一些方法,如 selectComponent 。
· 出于性能考虑,使用 usingComponents 时, setData 内容不会被直接深复制,即 this.setData({ field: obj }) 后 this.data.field === obj 。(深复制会在这个值被组件间传递时发生。)
如果页面比较复杂,新增或删除 usingComponents 定义段时建议重新测试一下。
4.组件模板和样式:
https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html
5.Component构造器:
https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/component.html
6.组件间通信与事件
1)组件间通信
组件间的基本通信方式有以下几种。
· WXML 数据绑定:用于父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容数据(自基础库版本 2.0.9 开始,还可以在数据中包含函数)。具体在 组件模板和样式 章节中介绍。
· 事件:用于子组件向父组件传递数据,可以传递任意数据。
· 如果以上两种方式不足以满足需要,父组件还可以通过 this.selectComponent 方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。
2)监听事件
事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。关于事件的基本概念和用法,参见 事件 。
监听自定义组件事件的方法与监听基础组件事件的方法完全一致:
代码示例:
<!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 --> <component-tag-name bindmyevent="onMyEvent" /> <!-- 或者可以写成 --> <component-tag-name bind:myevent="onMyEvent" />
Page({ onMyEvent(e) { e.detail // 自定义组件触发事件时提供的detail对象 } })
3)触发事件
自定义组件触发事件时,需要使用 triggerEvent 方法,指定事件名、detail对象和事件选项:
代码示例:
<!-- 在自定义组件中 -->
<button bindtap="onTap">点击这个按钮将触发“myevent”事件</button>
Component({ properties: {}, methods: { onTap() { const myEventDetail = {} // detail对象,提供给事件监听函数 const myEventOption = {} // 触发事件的选项 this.triggerEvent('myevent', myEventDetail, myEventOption) } } })
触发事件的选项包括:
选项名 | 类型 | 是否必填 | 默认值 | 描述 |
---|---|---|---|---|
bubbles | Boolean | 否 | false | 事件是否冒泡 |
composed | Boolean | 否 | false | 事件是否可以穿越组件边界,为false时,事件将只能在引用组件的节点树上触发,不进入其他任何组件内部 |
capturePhase | Boolean | 否 | false | 事件是否拥有捕获阶段 |
代码示例:
// 页面 page.wxml <another-component bindcustomevent="pageEventListener1"> <my-component bindcustomevent="pageEventListener2"></my-component> </another-component> // 组件 another-component.wxml <view bindcustomevent="anotherEventListener"><slot /></view> // 组件 my-component.wxml <view bindcustomevent="myEventListener"><slot /></view>
// 组件 my-component.js Component({ methods: { onTap() { this.triggerEvent('customevent', {}) // 只会触发 pageEventListener2 this.triggerEvent('customevent', {}, {bubbles: true}) // 会依次触发 pageEventListener2 、 pageEventListener1 this.triggerEvent('customevent', {}, {bubbles: true, composed: true}) // 会依次触发 pageEventListener2 、 anotherEventListener 、 pageEventListener1 } } })
7.组件生命周期
生命周期 | 参数 | 描述 |
---|---|---|
show | 无 | 组件所在的页面被展示时执行 |
hide | 无 | 组件所在的页面被隐藏时执行 |
resize | Object Size | 组件所在的页面尺寸变化时执行 |
1)组件的主要生命周期
组件的生命周期,指的是组件自身的一些函数,这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发。
其中,最重要的生命周期是 created attached detached ,包含一个组件实例生命流程的最主要时间点。
· 组件实例刚刚被创建好时, created 生命周期被触发。此时,组件数据 this.data 就是在 Component 构造器中定义的数据 data 。 此时还不能调用 setData 。 通常情况下,这个生命周期只应该用于给组件 this 添加一些自定义属性字段。
· 在组件完全初始化完毕、进入页面节点树后, attached 生命周期被触发。此时, this.data 已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。
· 在组件离开页面节点树后, detached 生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则 detached 会被触发。
2)定义生命周期方法
生命周期方法可以直接定义在 Component 构造器的第一级参数中。
自小程序基础库版本 2.2.3 起,组件的的生命周期也可以在 lifetimes 字段内进行声明(这是推荐的方式,其优先级最高)。
代码示例:
Component({ lifetimes: { attached() { // 在组件实例进入页面节点树时执行 }, detached() { // 在组件实例被从页面节点树移除时执行 }, }, // 以下是旧式的定义方式,可以保持对 <2.2.3 版本基础库的兼容 attached() { // 在组件实例进入页面节点树时执行 }, detached() { // 在组件实例被从页面节点树移除时执行 }, // ... })
在 behaviors 中也可以编写生命周期方法,同时不会与其他 behaviors 中的同名生命周期相互覆盖。但要注意,如果一个组件多次直接或间接引用同一个 behavior ,这个 behavior 中的生命周期函数在一个执行时机内只会执行一次。
可用的全部生命周期如下表所示。
生命周期 | 参数 | 描述 |
---|---|---|
created | 无 | 在组件实例刚刚被创建时执行 |
attached | 无 | 在组件实例进入页面节点树时执行 |
ready | 无 | 在组件在视图层布局完成后执行 |
moved | 无 | 在组件实例被移动到节点树另一个位置时执行 |
detached | 无 | 在组件实例被从页面节点树移除时执行 |
error | Object Error | 每当组件方法抛出错误时执行 |
3)组件所在页面的生命周期
还有一些特殊的生命周期,它们并非与组件有很强的关联,但有时组件需要获知,以便组件内部处理。这样的生命周期称为“组件所在页面的生命周期”,在 pageLifetimes 定义段中定义。其中可用的生命周期包括:
生命周期 | 参数 | 描述 |
---|---|---|
show | 无 | 组件所在的页面被展示时执行 |
hide | 无 | 组件所在的页面被隐藏时执行 |
resize | Object Size | 组件所在的页面尺寸变化时执行 |
代码示例:
Component({ pageLifetimes: { show() { // 页面被展示 }, hide() { // 页面被隐藏 }, resize(size) { // 页面尺寸变化 } } })
8.组件间关系
1)定义和使用组件间关系
有时需要实现这样的组件:
<custom-ul> <custom-li>item 1</custom-li> <custom-li>item 2</custom-li> </custom-ul>
这个例子中, custom-ul 和 custom-li 都是自定义组件,它们有相互间的关系,相互间的通信往往比较复杂。此时在组件定义时加入 relations 定义段,可以解决这样的问题。示例:
// path/to/custom-ul.js Component({ relations: { './custom-li': { type: 'child', // 关联的目标节点应为子节点 linked(target) { // 每次有custom-li被插入时执行,target是该节点实例对象,触发在该节点attached生命周期之后 }, linkChanged(target) { // 每次有custom-li被移动后执行,target是该节点实例对象,触发在该节点moved生命周期之后 }, unlinked(target) { // 每次有custom-li被移除时执行,target是该节点实例对象,触发在该节点detached生命周期之后 } } }, methods: { _getAllLi() { // 使用getRelationNodes可以获得nodes数组,包含所有已关联的custom-li,且是有序的 const nodes = this.getRelationNodes('path/to/custom-li') } }, ready() { this._getAllLi() } }) // path/to/custom-li.js Component({ relations: { './custom-ul': { type: 'parent', // 关联的目标节点应为父节点 linked(target) { // 每次被插入到custom-ul时执行,target是custom-ul节点实例对象,触发在attached生命周期之后 }, linkChanged(target) { // 每次被移动后执行,target是custom-ul节点实例对象,触发在moved生命周期之后 }, unlinked(target) { // 每次被移除时执行,target是custom-ul节点实例对象,触发在detached生命周期之后 } } } })
注意:必须在两个组件定义中都加入relations定义,否则不会生效。
2)关联一类组件
有时,需要关联的是一类组件,如:
<custom-form> <view> input <custom-input></custom-input> </view> <custom-submit>submit</custom-submit> </custom-form>
custom-form 组件想要关联 custom-input 和 custom-submit 两个组件。此时,如果这两个组件都有同一个behavior:
// path/to/custom-form-controls.js module.exports = Behavior({ // ... }) // path/to/custom-input.js const customFormControls = require('./custom-form-controls') Component({ behaviors: [customFormControls], relations: { './custom-form': { type: 'ancestor', // 关联的目标节点应为祖先节点 } } }) // path/to/custom-submit.js const customFormControls = require('./custom-form-controls') Component({ behaviors: [customFormControls], relations: { './custom-form': { type: 'ancestor', // 关联的目标节点应为祖先节点 } } })
则在 relations 关系定义中,可使用这个behavior来代替组件路径作为关联的目标节点:
// path/to/custom-form.js const customFormControls = require('./custom-form-controls') Component({ relations: { customFormControls: { type: 'descendant', // 关联的目标节点应为子孙节点 target: customFormControls } } })
3)relations 定义段
relations 定义段包含目标组件路径及其对应选项,可包含的选项见下表。
选项 | 类型 | 是否必填 | 描述 |
---|---|---|---|
type | String | 是 | 目标组件的相对关系,可选的值为 parent 、 child 、 ancestor 、 descendant |
linked | Function | 否 | 关系生命周期函数,当关系被建立在页面节点树中时触发,触发时机在组件attached生命周期之后 |
linkChanged | Function | 否 | 关系生命周期函数,当关系在页面节点树中发生改变时触发,触发时机在组件moved生命周期之后 |
unlinked | Function | 否 | 关系生命周期函数,当关系脱离页面节点树时触发,触发时机在组件detached生命周期之后 |
target | String | 否 | 如果这一项被设置,则它表示关联的目标节点所应具有的behavior,所有拥有这一behavior的组件节点都会被关联 |
9.自定义组件扩展
1)扩展后的效果
为了更好的理解扩展后的效果,先举一个例子:
// behavior.js module.exports = Behavior({ definitionFilter(defFields) { defFields.data.from = 'behavior' }, })
// component.js Component({ data: { from: 'component' }, behaviors: [require('behavior.js')], ready() { console.log(this.data.from) // 此处会发现输出 behavior 而不是 component } })
通过例子可以发现,自定义组件的扩展其实就是提供了修改自定义组件定义段的能力,上述例子就是修改了自定义组件中的 data 定义段里的内容。
2)使用扩展
Behavior() 构造器提供了新的定义段 definitionFilter ,用于支持自定义组件扩展。 definitionFilter 是一个函数,在被调用时会注入两个参数,第一个参数是使用该 behavior 的 component/behavior 的定义对象,第二个参数是该 behavior 所使用的 behavior 的 definitionFilter 函数列表。
以下举个例子来说明:
// behavior3.js module.exports = Behavior({ definitionFilter(defFields, definitionFilterArr) {}, }) // behavior2.js module.exports = Behavior({ behaviors: [require('behavior3.js')], definitionFilter(defFields, definitionFilterArr) { // definitionFilterArr[0](defFields) }, }) // behavior1.js module.exports = Behavior({ behaviors: [require('behavior2.js')], definitionFilter(defFields, definitionFilterArr) {}, }) // component.js Component({ behaviors: [require('behavior1.js')], })
上述代码中声明了1个自定义组件和3个 behavior,每个 behavior 都使用了 definitionFilter 定义段。那么按照声明的顺序会有如下事情发生:
1.当进行 behavior2 的声明时就会调用 behavior3 的 definitionFilter 函数,其中 defFields 参数是 behavior2 的定义段, definitionFilterArr 参数即为空数组,因为 behavior3 没有使用其他的 behavior 。
2.当进行 behavior1 的声明时就会调用 behavior2 的 definitionFilter 函数,其中 defFields 参数是 behavior1 的定义段, definitionFilterArr 参数是一个长度为1的数组,definitionFilterArr[0] 即为 behavior3 的 definitionFilter 函数,因为 behavior2 使用了 behavior3。用户在此处可以自行决定在进行 behavior1 的声明时要不要调用 behavior3 的 definitionFilter 函数,如果需要调用,在此处补充代码 definitionFilterArr[0](defFields) 即可,definitionFilterArr 参数会由基础库补充传入。
3.同理,在进行 component 的声明时就会调用 behavior1 的 definitionFilter 函数。
简单概括,definitionFilter 函数可以理解为当 A 使用了 B 时,A 声明就会调用 B 的 definitionFilter 函数并传入 A 的定义对象让 B 去过滤。此时如果 B 还使用了 C 和 D ,那么 B 可以自行决定要不要调用 C 和 D 的 definitionFilter 函数去过滤 A 的定义对象。
真实案例
下面利用扩展简单实现自定义组件的计算属性功能:
// behavior.js module.exports = Behavior({ lifetimes: { created() { this._originalSetData = this.setData // 原始 setData this.setData = this._setData // 封装后的 setData } }, definitionFilter(defFields) { const computed = defFields.computed || {} const computedKeys = Object.keys(computed) const computedCache = {} // 计算 computed const calcComputed = (scope, insertToData) => { const needUpdate = {} const data = defFields.data = defFields.data || {} for (const key of computedKeys) { const value = computed[key].call(scope) // 计算新值 if (computedCache[key] !== value) needUpdate[key] = computedCache[key] = value if (insertToData) data[key] = needUpdate[key] // 直接插入到 data 中,初始化时才需要的操作 } return needUpdate } // 重写 setData 方法 defFields.methods = defFields.methods || {} defFields.methods._setData = function (data, callback) { const originalSetData = this._originalSetData // 原始 setData originalSetData.call(this, data, callback) // 做 data 的 setData const needUpdate = calcComputed(this) // 计算 computed originalSetData.call(this, needUpdate) // 做 computed 的 setData } // 初始化 computed calcComputed(defFields, true) // 计算 computed } })
在组件中使用:
const beh = require('./behavior.js') Component({ behaviors: [beh], data: { a: 0, }, computed: { b() { return this.data.a + 100 }, }, methods: { onTap() { this.setData({ a: ++this.data.a, }) } } })
<view>data: {{a}}</view> <view>computed: {{b}}</view> <button bindtap="onTap">click</button>
实现原理很简单,对已有的 setData 进行二次封装,在每次 setData 的时候计算出 computed 里各字段的值,然后设到 data 中,已达到计算属性的效果。
此实现只是作为一个简单案例来展示,请勿直接在生产环境中使用。
10.behaviors
https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html
11.抽象节点
https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/generics.html
参考网址:
https://developers.weixin.qq.com/miniprogram/dev/component/
https://blog.csdn.net/qq_40016476/article/details/81082518
https://blog.csdn.net/unirrrrr/article/details/80724047
https://www.jianshu.com/p/8a2a730d9e60
转载请注明: ITTXX.CN--分享互联网 » 微信小程序之组件、自定义组件教程
最后更新:2020-04-18 00:24:46