微信小程序:组件实践

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/eadio/article/details/79260004

前阶段有网友问了一个关于小程序组件的问题:

有一个列表,我点击编辑,出现一个弹窗修改组件,输入修改数值点击确定,对应的列表项的值就被更改过来,点击取消,不做任何操作。

据说能获取到文本框的值,可是列表值一直更改不了。我就抽空写了个demo发现并未出现此问题,因此萌生了写这篇文章。

那么什么是组件,为什么要用组件。相信不用我多说大家都很清楚,以前每个html静态页的头部和底部都得重复写,自从有了组件概念出现,再也不用做这些重复性工作了,开发效率是大大的提升,而且稍微有改动,只需改动一个地方。

那么,微信小程序的组件该如何使用呢?
假设我们的项目中有个组件B【包含B.js、B.json、B.wxml、B.wxss】,页面A【包含A.js、A.json、A.wxml、A.wxss】,结构如下:
这里写图片描述

首先,我们要了解下组件的一些基础特性以及用法。
1、想要使用组件,需先在B.json配置定义当前文件夹目录为组件目录模板:

{
  "component": true
}

然后在A.json做如下配置,引入组件模板B:

{
  "usingComponents": {
    "component-tag-name": "path/to/the/custom/component"//component-tag-name是A.wxml要引入自定义的组件节点名称,我们需遵循这样的命名规则
  }
}

2、组件模板B.wxml:
wxml组件页和一般的wxml页面语法基本相同,有个比较特殊地方是组件wxml页可以使用slot插槽,定义了slot节点后,页面上引用的组件节点内部填写的内容就会展示在slot插槽的位置上,默认一个组件只能有一个slot插槽,想要使用多个需要额外配置,具体参考官方文档

<!-- 组件模板 -->
<view class="wrapper">
  <view>这里是组件的内部节点</view>
  <slot><!--外部文本会显示在这里--></slot>
</view>

然后在A.wxml引用组件B

<!-- 引用组件的页面模版 -->
<view>
  <component-tag-name title="我是A传递的参数" id="BB" test-a="{{a}}" data-test-b="{{b}}">
    <!-- 这部分内容将被放置在组件 <slot> 的位置上 -->
    <view>这里是插入到组件slot中的内容</view>
  </component-tag-name>
</view>

3、B.wxss组件样式页:
写法基本和普通wxss页的没什么差别,只是有些需要注意的地方,例如不能使用id选择器、子元素选择器、元素选择器等,请自行查看官网

4、B.js组件逻辑页Component构造器:

属性 类型 是否必填 描述
properties Object Map 组件的对外属性,是属性名到属性设置的映射表,属性设置中可包含三个字段, type 表示属性类型、 value 表示属性初始值、 observer 表示属性值被更改时的响应函数[简单的说,就是从A页面传递参数给B组件]
data Object 组件的内部数据,和 properties 一同用于组件的模版渲染
methods Object 组件的方法,包括事件响应函数和任意的自定义方法,关于事件响应函数的使用,参见官网 组件事件
behaviors String Array 类似于mixins和traits的组件间代码复用机制,参见 behaviors
created Function 组件生命周期函数,在组件实例进入页面节点树时执行,注意此时不能调用 setData
attached Function 组件生命周期函数,在组件实例进入页面节点树时执行
ready Function 组件生命周期函数,在组件布局完成后执行,此时可以获取节点信息(使用 SelectorQuery )
moved Function 组件生命周期函数,在组件实例被移动到节点树另一个位置时执行
detached Function 组件生命周期函数,在组件实例被从页面节点树移除时执行
relations Object 组件间关系定义,参见 组件间关系
options Object Map 一些组件选项,请参见文档其他部分的说明

其他框架我不了解,但是最近有使用vue开发过项目,我觉得微信小程序的组件系统和vue的组件系统非常类似,平常的组件架构基本使用properties、data、attached、methods就够了。

这里写图片描述
4-1、对于created和attached生命周期有点小疑惑,为什么两个描述都是一样的呢?于是做了个小测试。

created生命周期
created生命周期不能使用setData。 但是我打印this.data可以得到properties和data中定义的数据。我尝试调用setData并设置参数值,确实会报错,但是如果我不设置数值函数又正常执行,只不过返回了undefined。然后打印了下this对象,可以看到构造函数都已经存在。由此是否可以猜测此生命周期组件已经实例化,而wxml节点树还未实例导致的。那么,在此生命周期中到底能处理什么事务呢?目前还是想不通。
attached生命周期
我在当前生命周期直接执行setData修改properties属性,发现wxml上的数据已经得到修改,由此是否可以猜测此生命周期中,实例化已经结束,节点树也实例了。因此才能时时渲染到页面上了。也就是说可以在此周期中获取数据并setData到data对象上,进而渲染到节点中。那么到底能不能操作节点呢?为了证实这个,我在attached和ready分别执行了下面的方法。
var query = wx.createSelectorQuery()
query.select('#BB').boundingClientRect()
query.selectViewport().scrollOffset()
query.exec(function (res) {
  console.log(res)
})

最终发现ready中第一个数组有返回自定义组件的一些基础信息,比如id、dataset、width等。而attached中第一个数组是null。那么是否可以进一步猜测,在attached中是实例了节点树而还未渲染到页面上?当然这一切还都是我自己猜测的,如果有得出不同的结论欢迎告知下,感谢感谢~

4-2、properties和dataset传参观察
通过官网可以得知从A页面传递参数给B组件有两种方式,一种是properties,另外一种是dataset方式。那么这两个有什么区别呢?
于是我在页面上分别传入test-a和data-test-b两个参数,然后在B.js的created分别打印this,观察到properties属性都会被重新赋值到data对象上,而data对象上的参数在properties也能找到,而dataset并未出现在这两个属性中,也就是说这两个是共用一个引用地址。而dataset是单独一个引用地址。
这里写图片描述

有趣的是,我在该阶段测试的时候,尝试在created生命周期打印dataset的值,竟然报错了,于是我又打印this.dataset,观察到该属性在此生命周期其实还未创建呢。而打印this.data并未出现该情况,但是我又尝试打印this.data.testA的值,发现也是null。而在attached并未有次状况,因此可以大胆猜测created其实只是初始化了组件实例,而接受父容器参数什么的都还未开始进行。那么created生命周期到底能处理些什么呢,名存实亡的感觉??

因此,我建议:如果不是要渲染到组件节点树上的单纯只是在逻辑层进行逻辑运算的,可以通过dataset方式传入。而需要渲染到节点树的则可以直接通过properties传递,这样就避免再一次在attached中setData。尽量初始化事务都在attached生命周期处理比较好。

4-3、组件事件
页面和组件之间的交互主要就是通过事件系统。自定义组件触发事件时,需要使用 triggerEvent 方法,指定事件名、detail对象和事件选项! 那么具体怎么使用事件系统。

我们在页面A.wxml引入自定义组件B节点名

<view bindtap="mappao">
<!-- 当自定义组件触发名为“myevent”的事件时,调用“onMyEvent”方法 -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- 或者可以写成 -->
<component-tag-name bind:myevent="onMyEvent" />
</view>

然后在页面A.js写好自定义事件onMyEvent

maopao(){
console.log('mao pao le.')
},
onMyEvent: function(e){
  e.detail // 自定义组件触发事件时提供的detail对象
}

然后组件B.wxml写入:

<!-- 在自定义组件中 -->
<button bindtap="onTap">点击这个按钮将触发“myevent”事件</button>

在组件B.js写入:

Component({
  properties: {}
  methods: {
    onTap: function(){
      var myEventDetail = {} // detail对象,提供给事件监听函数
      var myEventOption = {} // 触发事件的选项
      this.triggerEvent('myevent', myEventDetail, myEventOption);//通过triggerEvent指定A页面中的myevent是见面,从而触发页面A中的onMyEvent自定义事件
    }
  }
})

myEventOption属性控制这里就不做讨论了,为了防止冒泡之类的不可预期的事件产生,我都是直接用catch来绑定事件的。不知道为什么需要这么多参数控制,经测试后,发现只要是使用bind绑定的事件,不管有没有设置参数控制都会冒泡到引用组件的父节点树的事件上的,而且多层组件嵌套的话,最里层冒泡到引用组件的父容器上就会报错。不知道是否是个bug?经官方证实了,这不是bug。(tap事件是全局冒泡的(bubbles: true, composed: true),想要阻止冒泡,请使用catchtap。—-来自官方 @LastLeaf )的回答。【测试荣耀9安卓版微信版本号6.6.1、开发者工具v1.02.1801081、基础框架1.9.1】

我做了如下测试:
页面A:

<!-- 引用组件的页面模版 -->
<view bindtap="maopao">
  // 页面 page.wxml
<another-component bindcustomevent="pageEventListener1">
  <view>点C我试试啊</view>
  <my-component bindcustomevent="pageEventListener2"><view>点D我试试</view></my-component>
</another-component>
</view>
Page({
  data: {
  },
  pageEventListener2(e){
    console.log('pageEventListener2');
  },
  pageEventListener1(e){
    console.log('pageEventListener1')
  },
  maopao(e){
    console.log('mao pao le.')
  }
})

组件C

// 组件 another-component.wxml
<view bindtap="anotherEventListener">
  <slot />
</view>
Component({
methods: {
  anotherEventListener: function () {
    console.log('click c');
  }
}
})

组件D

// 组件 my-component.wxml
<view bindtap="myEventListener">
  <slot />
</view>
Component({
methods: {
  myEventListener: function () {
    console.log('click D');
    this.triggerEvent('customevent', {}) // 只会触发 pageEventListener2
    this.triggerEvent('customevent', {}, { bubbles: true }) // 会依次触发 pageEventListener2 、 pageEventListener1
    //this.triggerEvent('customevent', {}, { bubbles: false, composed: false, capturePhase: false }) // 会依次触发 pageEventListener2 、 anotherEventListener 、 pageEventListener1
  }
}
})

演示如下:
这里写图片描述

我如果把页面A引用组件的父容器maopao事件去掉就不会有这些情况;
或者是组件模板上改为catch绑定事件也不会有这类情况。因此是否可以大胆猜测官方当时设计这个的初衷并未考虑会在页面中用view等父容器包含组件的情况下表现的呢?不管怎么说组件内部用catch绑定事件总不会错的。这是个好习惯O(∩_∩)O~

behaviors和组件关系并未做深入研究,大致看了下官方文档,能做的事情太多了,这个具体还得结合项目来研究调试比较好。(其实是这货自己也不懂!!哭唧唧T^T)

最后,厚颜无耻的把写在文章前说的简单demo发上来了。
demo资源请求:http://download.csdn.net/download/eadio/10241974
演示如下:
这里写图片描述

以上是我自己对小程序组件的一些见解,如有不同的见解欢迎指出。祝食用愉快,感谢阅读~

猜你喜欢

转载自blog.csdn.net/eadio/article/details/79260004