微信-小程序开发基础知识笔记
绑定事件的方法:
1.bindtab和catchtab,catchtab可以阻止事件冒泡
<view bindtap='onClick'></view> <view catchtap='onClick'></view>
2.互斥事件绑定 mut-bind
一个 mut-bind 触发后,如果事件冒泡到其他节点上,其他节点上的 mut-bind 绑定函数不会被触发,但 bind 绑定函数和 catch 绑定函数依旧会被触发。
在想要规定冒泡区间时可以用到。
<view mut-bind:tap='onClick'></view>
在基础库版本 2.7.1 以上,可以使用 mark 来识别具体触发事件的 target 节点
<view mark:myMark="last" bindtap="bindViewTap"> <button mark:anotherMark="leaf" bindtap="bindButtonTap">按钮</button> </view>
在上述 WXML 中,如果按钮被点击,将触发 bindViewTap 和 bindButtonTap 两个事件,事件携带的 event.mark 将包含 myMark 和 anotherMark 两项。
Page({ bindViewTap: function(e) { e.mark.myMark === "last" // true e.mark.anotherMark === "leaf" // true } })
mark 和 dataset 很相似,主要区别在于:
1.mark可以冒泡,如果存在同名的 mark ,父节点的 mark 会被子节点覆盖。
2.在自定义组件中接收事件时, mark 不包含自定义组件外的节点的 mark。
3.不同于 dataset ,节点的 mark 不会做连字符和大小写转换。
.wxs文件的应用
个人理解 .wxs文件就相当于cocos的prefab。执行起来比js性能要快。但是wxs是一门语言,平行于JavaScript。
有频繁用户交互的效果在小程序上表现是比较卡顿的,这时建议使用wxs,为什么?
因为小程序分为视图层和逻辑层,比如需要拖动的功能,touchmove事件从视图层抛到逻辑层,逻辑层经过处理,通过this.setData到视图层。
1. 一次 touchmove 的响应需要经过 2 次的逻辑层和渲染层的通信以及一次渲染,通信的耗时比较大。
2. 此外 setData 渲染也会阻塞其它脚本执行,导致了整个用户交互的动画过程会有延迟。
WXS 函数的除了纯逻辑的运算,还可以通过封装好的ComponentDescriptor 实例来访问以及设置组件的 class 和样式,对于交互动画,设置 style 和 class 很方便。
var wxsFunction = function(event, ownerInstance) { var instance = ownerInstance.selectComponent('.classSelector') // 返回组件的实例 instance.setStyle({ "font-size": "14px" // 支持rpx }) instance.getDataset() instance.setClass(className) // ... return false // 不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault }
自定义组件
上边在分析复杂交互时我们知道,频繁的调用this.setData会使页面卡顿,甚至导致小程序僵死。那么不想写或者说不会写wxs的开发者该怎么办呢?
此时可以通过将页面的 setData 改为 自定义组件 中的 setData 来提升性能。
原因:自定义组件中的setData不会进行深复制。(深复制会在这个值被组件间传递时才发生)
自定义组件的规范
1.在组件wxss中不应使用ID选择器、属性选择器和标签名选择器,就只使用class选择器准没错。
2.在自定义组件的 js 文件中,需要使用 Component() 来注册组件。
3.使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。
4.自定义组件和页面所在项目根目录名不能以“wx-”为前缀,否则会报错。
5.<slot></slot>
相当于react的this.props.children。
<!-- component-tag-name组件 --> <view class="wrapper"> <view>这里是组件的内部节点</view> <slot></slot> </view>
<!-- 引用组件的页面模板 --> <view> <component-tag-name> <!-- 这部分内容将被放置在组件 <slot> 的位置上 --> <view>这里是插入到组件slot中的内容</view> </component-tag-name> </view>
6.默认情况下,一个组件的wxml中只能有一个slot。需要使用多slot时,可以在组件js中声明启用。
Component({ options: { multipleSlots: true // 在组件定义时的选项中启用多slot支持 }, properties: { /\* ... \*/ }, methods: { /\* ... \*/ } })
此时,可以在这个组件的wxml中使用多个slot,以不同的 name 来区分。
<!-- 组件模板 --> <view class="wrapper"> <slot name="before"></slot> <view>这里是组件的内部细节</view> <slot name="after"></slot> </view>
使用时,用 slot 属性来将节点插入到不同的slot上。
<!-- 引用组件的页面模板 --> <view> <component-tag-name> <!-- 这部分内容将被放置在组件 <slot name="before"> 的位置上 --> <view slot="before">这里是插入到组件slot name="before"中的内容</view> <!-- 这部分内容将被放置在组件 <slot name="after"> 的位置上 --> <view slot="after">这里是插入到组件slot name="after"中的内容</view> </component-tag-name> </view>
7.设置自定义组件的捕获和冒泡机制需要使用 triggerEvent 方法。
// 组件 my-component.js Component({ methods: { onTap: function(){ this.triggerEvent('customevent', {}, { bubbles: true, composed: true }) } } })
组件中的behaviors
个人理解:多个页面可能会共用一个功能,这个功能抽象后称为组件。
多个组件共用一个方法或者多个方法,这类方法的集合称为behaviors。就tm理解成高阶组件就完了。
组件间关系relations
官方说:有时需要实现这样的组件:
<custom-ul> <custom-li> item 1 </custom-li> <custom-li> item 2 </custom-li> </custom-ul>
说custom-ul 和 custom-li 都是自定义组件,它们有相互间的关系,相互间的通信往往比较复杂。具体怎么个复杂需要单独拎出来一个ralations属性来处理,咱也不知道。
使用方法:
// path/to/custom-ul.js Component({ relations: { './custom-li': { type: 'child', // 关联的目标节点应为子节点 linked: function(target) { // 每次有custom-li被插入时执行,target是该节点实例对象,触发在该节点attached生命周期之后 }, linkChanged: function(target) { // 每次有custom-li被移动后执行,target是该节点实例对象,触发在该节点moved生命周期之后 }, unlinked: function(target) { // 每次有custom-li被移除时执行,target是该节点实例对象,触发在该节点detached生命周期之后 } } },
// path/to/custom-li.js Component({ relations: { './custom-ul': { type: 'parent', // 关联的目标节点应为父节点 linked: function(target) { // 每次被插入到custom-ul时执行,target是custom-ul节点实例对象,触发在attached生命周期之后 }, linkChanged: function(target) { // 每次被移动后执行,target是custom-ul节点实例对象,触发在moved生命周期之后 }, unlinked: function(target) { // 每次被移除时执行,target是custom-ul节点实例对象,触发在detached生命周期之后 } } } })
注意:必须在两个组件定义中都加入relations定义,否则不会生效。
还有一种情况,如果你两个自定义组件都用了相同的behaviors,你可以使用这个behavior来代替组件路径作为关联的目标节点。
// path/to/custom-form.js var customFormControls = require('./custom-form-controls') Component({ relations: { 'customFormControls': { type: 'descendant', // 关联的目标节点应为子孙节点 target: customFormControls } } })
组件中的observers
官方定义他叫数据监听器,呵呵。
使用方法:
Component({ attached: function() { this.setData({ numberA: 1, numberB: 2, }) }, observers: { 'numberA, numberB': function(numberA, numberB) { // 在 numberA 或者 numberB 被设置时,执行这个函数 this.setData({ sum: numberA + numberB }) } } })
如果需要监听所有子数据字段的变化,可以使用通配符 。
Component({ observers: { 'some.field.**': function(field) { // 使用 setData 设置 this.data.some.field 本身或其下任何子数据字段时触发 // (除此以外,使用 setData 设置 this.data.some 也会触发) field === this.data.some.field }, }, attached: function() { // 这样会触发上面的 observer this.setData({ 'some.field': { /* ... */ } }) // 这样也会触发上面的 observer this.setData({ 'some.field.xxx': { /* ... */ } }) // 这样还是会触发上面的 observer this.setData({ 'some': { /* ... */ } }) } })
特别地,仅使用通配符 可以监听全部 setData 。**
纯数据字段
就是局部变量,不参与渲染,也不会传递。
官方说这样声明后再用能提高性能,要不我才不用。
使用方式:
Component({ options: { pureDataPattern: /^_/ // 指定所有 _ 开头的数据字段为纯数据字段 }, data: { a: true, // 普通数据字段 _b: true, // 纯数据字段 }, methods: { myMethod() { this.data._b // 纯数据字段可以在 this.data 中获取 this.setData({ c: true, // 普通数据字段 _d: true, // 纯数据字段 }) } } })
抽象节点
又一个新名词,呵呵呵。
说白了就是有一个父容器组件A,因为条件不同有可能A中会渲染组件B,也可能渲染组件C。举个例子,当页面需要单选和多选组件的时候,方法1是按条件引用两个封装好的组件(<单选/>,</多选>),方法2是你也可以只引用一个组件<啦啦啦/>,只不过这个<啦啦啦/>组件去帮你按需渲染<单选/>或者<多选/>。
需要在父容器组件A的.json文件声明:
{ "componentGenerics": { "selectable": true } }
在使用组件时,必须指定父组件具体是渲染哪个子组件:
<啦啦啦 generic:selectable="单选" />
<啦啦啦 generic:selectable="多选" />
在页面的.json文件<啦啦啦/>,<单选/>,<多选/>都要引用。代码
//page下页面的.json文件中 { "usingComponents": { "啦啦啦": "path/*/*", "多选": "*/checkbox", "单选": "*/radio" } }
当然,你也可以在容器组件.json中指定默认用哪个组件:
{ "componentGenerics": { "selectable": { "default": "*/checkbox"// 多选 } } }
计算属性
今天真是开眼了,学到了这么多新词汇。。
( 计算属性的作用):是为了解决HTML代码中复杂的js代码(HTML代码中可以嵌套js代码),把复杂的js代码通过计算属性来解决
这是计算属性的应用??? 听着词这么厉害干这事真是大才小用了。
计算属性会使用缓存机制,如果这个数据的值没有改变,则计算属性将不会调用方法
这点应该是它实际有价值的地方。
实现原理很简单,就是对已有的 setData 进行二次封装,在每次 setData 的时候计算出 computed 里各字段的值,这期间可以增加缓存机制,属性值没有变化的复用。
自定义组件拓展
在react中想拓展一个组件怎么办,会用高阶组件。
小程序中,自然是使用behaviors。
// 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 } })
Behavior() 构造器提供了新的定义段 definitionFilter ,用于支持自定义组件扩展。