阅读 595

小程序(三)自定义组件与组件化

这是我参与更文挑战的第4天,活动详情查看: 更文挑战

前言

自定义组件能够帮我们更好的复用代码和重构简化代码复杂度。从小程序基础库版本 1.6.3 开始,小程序支持简洁的组件化编程。所有自定义组件相关特性都需要基础库版本 1.6.3 或更高。开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。

自定义组件

小程序自定义组件的开发细节的核心点:

  • 自定义组件的资源管理;

-组件的生命周期;

  • 组件间的通信流程。

在这个过程中会结合组件化思想,以及 Web Components 规范辅助理解。Web Components 规范是 W3C 推出的一套用于封装具有复用性、互动性前端组件的技术规范,旨在提供一种标准的组件化模式。 目前流行的几个前端框架( Vue / React / Angular )都在一定程度上遵循了这套规范,微信小程序的自定义组件也是如此,而且,微信小程序渲染自定义组件使用的是 Shadow DOM,这项技术是 Web Components 规范的一部分。 作为一名前端开发者,你可能经历过 BootStrap 盛行的年代,也可能是从更悠久的 ExtJS 时代一路走来,就算你不了解这两个框架,肯定不可避免地使用过 React、Vue 或 Angular 这三种框架。React / Vue / Angular 与它们的前辈 BootStrap / ExtJS 有一点是共通的:它们都是前端组件化的推崇者。 可以把“前端组件化”理解为“面向对象编程思想在前端 UI 领域的具体实践,将部分 UI 内容抽离为独立的可复用的组件”,这样做有这样 3 点优势:

提高代码可复用性

这是组件化最直接的优点,如果不用组件,每次遇到相同的业务场景都需要重新编写代码,而被抽离后的组件可以在其适用的场景内被重复使用,很大程度上降低了开发耗时。

降低代码维护难度

想象一下,假如一个页面的所有 UI 源码都集中在同一个 HTML 文件中,当页面中的导航栏出现 Bug,你需要在上千行甚至上万行的 HTML 文件中找到导航栏对应的 HTML 标签。如果将导航栏抽离为一个组件,那么你仅仅需要在这个组件内寻找。这类案例在工作中普遍存在,通过这个例子可以充分说明组件化在降低代码维护难度方面的优势。

降低系统重构难度

《重构:改善既有代码的设计》讲了:重构并不是当系统复杂度提升到一定程度难以维护时的一次性行为,而是一种高频的、小规模的日常行为。直白点儿说就是:应该不断通过重构来改善系统,不管重构的范围有多小。但是这种实践方式对于代码的可维护性有很大的挑战,这也间接说明了组件化对重构工作的正面影响:通过提高代码的可维护性,间接降低了系统的重构难度。

自定义组件的用法

一、Component概念

Component像页面一样由wxml、wxss、js和json4个文件组成,且需要把这4个文件放在同一个目录中。与页面不一样的是,Component中的构造函数(也可以称构造器)是Component({}),而页面中的构造函数是Page({})。要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(将component字段设为true可这一组文件设为自定义组件):

{
  "component": true
}
复制代码

Component的slot(slot意思是插槽),主要是让你在外部的wxml可以自由的在你的Component的wxml里插入模块。默认情况下,一个组件的wxml只可能有一个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>
复制代码

组件样式编写注意事项

组件和引用组件的页面不能使用id选择器(#a)、属性选择器([a])和标签名选择器,请改用class选择器。组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用。子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。

继承样式,如 font 、 color ,会从组件外继承到组件内。除继承样式外, app.wxss 中的样式、组件所在页面的的样式对自定义组件无效 (小程序频繁报大量的警告)。

#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */
复制代码

外部样式类

使用外部样式类可以让组件使用指定的组件外样式类,如果希望组件外样式类能够完全影响组件内部,可以将组件构造器中的options.addGlobalClass字段置为true。

/* 组件 custom-component.js */
Component({
  externalClasses: ['my-class']
})
<!-- 组件 custom-component.wxml -->
<custom-component class="my-class">这段文本的颜色由组件外的 class 决定</custom-component>
/* 组件外的样式定义 */
.red-text {
  color: red;
}
复制代码

创建一个组件

<!--components/component/component.wxml-->
<view class="inner">
    {{innerText}}
</view>
<slot></slot>
复制代码

编写JS文件,组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的

// components/component/component.js
Component({
    /**
     * 组件的属性列表
     */
    properties: {
        innerText: {
            type: String,
            value: 'hello world'
        },
        myProperties:String
    },

    /**
     * 组件的初始数据
     */
    data: {

    },

    /**
     * 组件的方法列表
     */
    methods: {

    }
})
复制代码

设置字体的颜色

/* components/component/component.wxss */
.inner{color: red;}
复制代码

完成对组件的初始化,包括设置属性列表,初始化数据,以及设置相关的方法。

使用自定义组件

使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:

{
  "usingComponents": {
    "component": "/components/component/component"
  }
}
复制代码

在page页面下添加声明过的自定义组件:

// <component></component>

<view>
  <component>
    <!-- 这部分内容将被放置在组件 <slot> 的位置上 -->
    <view>这里是插入到组件slot中的内容</view>
  </component>
</view>
复制代码

上方的是一个最简单的自定义组件。在开发微信小程序自定义组件的三个核心环节中我们需要注意以下几个细节:

自定义组件的资源管理

创建微信小程序自定义组件需要使用 Component 构造器,这是微信小程序结构体系内最小粒度的构造器,外层是 Page 构造器,最外层的是 App 构造器,三者的关系如下图:

image.png 从外到内依次是 App > Page > Component,每次递进是 1:N 的关系:

  • 1 个 App(也就是 1 个小程序)可包含 N( N >= 1 )个 Page;

  • 1 一个 Page 可包含N(N>=1)个 Component。

每个自定义组件的资源必须包括四个基本文件:

  • 用于描述组件结构的 wxml 文件;

  • 用于描述组件样式的 wxss 文件;

  • 用于描述组件行为的 js 文件;

  • 用于声明组件配置的 json 文件。

跟传统前端开发相比,小程序自定义组件的 wxml 和 wxss 文件的编写方式与 HTML 和 CSS 编写基本类似,不要特别关注,差异性主要体现在 js 和 json 文件上,在 json 文件中必须通过 component 字段声明此组件为自定义组件,如下:

{
  "component": true
}
复制代码

js 文件中通过 Component 构造器创建组件的逻辑实体,如下:

Component({
  behaviors:[],
  properties:{},
  data: {},
  lifetimes: {},
  pageLifetimes: {},
  methods: {}
});
复制代码

对照 Vue 和 React 讲解 Component 构造器的几个属性更容易理解: behaviors 类似于 Vue 和 React 中的 mixins,用于定义多个组件之间的共享逻辑,可以包含一组 properties、data、lifetimes 和 methods 的定义;

properties 类似于 Vue 和 React 中的 props ,用于接收外层(父组件)传入的数据;

data 类似于 Vue 中的 data 以及 React 中的 state ,用于描述组件的私用数据(状态);

lifetimes 用于定义组件自身的生命周期函数,这种写法是从小程序基础库 2.2.3 版本引入的,原本的写法与 Vue 和 React 类似,都是直接挂载到组件的一级属性上(下一小节我们将详细讲解生命周期函数的相关知识);

pageLifetimes 是微信小程序自定义组件独创的一套逻辑,用于监听此组件所在页面的生命周期。一般用于在页面特定生命周期时改变组件的状态,比如在页面展示时(show)把组件的状态设置为 A,在页面隐藏时(hide)设置为 B;

methods 与 Vue 的 methods 类似,用于定义组件内部的函数。

除 4 个基础文件以外,自定义组件还可以包含一些其他必要的资源,比如图片,下图展示的是自定义组件 chatroom 的资源列表:你可以看到,除了 wxml/wxss/js/json 文件以外,还有两个图片文件,在 wxml 中可以直接使用相对目录引用,

<image src="./photo.png"></image>
复制代码

自定义组件的生命周期

而对一个组件来说,生命周期指的是这个组件从被创建到销毁的过程,在这个过程中的里程碑阶段暴露出一些钩子函数,方便开发者针对不同阶段编写逻辑,这些函数就是所谓的“生命周期函数”。微信小程序自定义组件的生命周期函数有以下几个:

image.png 跟 Vue 和 React 相比,小程序自定义组件的生命周期更贴近 Web Components 规范。所以接下来我们结合 Web Components 规范来理解小程序自定义组件的生命周期。 Web Components 规范引入了一个概念:自定义 HTML 元素。目的跟小程序类似,都是为了创建一种自定义的 UI 组件。浏览器环境中,每个 HTML 标签都存在一个对应的类(Class),比如段落节点 对应 HTMLParagraphElement 类,继承这个类所创建的元素便是自定义 HTML 元素,如下代码:

// 创建自定义元素
class MyCustomParagraphElement extends HTMLParagraphElement {
   //...
}
// 注册自定义元素
customElements.define('custom-p', MyCustomParagraphElement);
复制代码

自定义元素必须被注册(或者叫作定义)之后才可以被使用,上述代码的最后一行便是注册逻辑,第一个参数是该元素被注册后的 HTML 标签名称。注册成功后便可以直接在 HTML 中使用该元素,如下:

<custom-p></custom-p>
复制代码

这个流程与微信小程序的自定义组件非常相似,只不过注册组件的行为是由小程序底层处理的,开发者仅需要编写组件本身的代码就可以了。 Web Components 规范对于自定义 HTML 元素的生命周期描述为下图所示的流程:

image.png 对比 Web Components 规范和小程序自定义组件的生命周期,两者有一定相似之处但并不完全一致,总结出这样几点:

  • 小程序自定义组件的 attached 和 detached 函数分别对应 Web Components 规范的connectedCallback 和 disconnectedCallback,功能上是一致的;

  • 小程序自定义组件的 moved 函数与 Web Components 规范的 adoptedCallback 类似但作用并不完全相同。由于小程序不支持 iframe,所以不存在组件在文档范畴上的迁移,只能在同一个文档的不同父节点之间迁移。所以也就不存在 adopted 状态,moved 函数可以理解为adopted 的一种变体;

  • 小程序自定义组件独有的生命周期函数,created、ready 和 error;

  • Web Components 规范独有的生命周期函数,attributeChangedCallback。

image.png

可见小程序自定义组件与 Web Components 规范的主要差异体现在第 3 点和第 4 点。为什么会有这样的差异呢?

差异点一:为什么小程序的自定义组件没有attributeChangedCallback函数?

首先我们要明确 attributeChangedCallback 函数的触发时机,Web Components 规范对这个函数的描述为“当自定义元素的任一属性发生改变(包括新增、删除、更新)时触发”。而更新元素属性这种行为是传统 DOM 编程中常见的,在目前倡导数据驱动 UI 的背景下,绝大多数框架都是通过 VDOM 来间接操作 DOM,所以更新属性在目前的时代背景下非常少见。

微信小程序与 Vue/React 一样,同样不允许直接操作 DOM,从根本上就不可能发生 DOM 属性改变的情况。这就解释了为何小程序自定义组件的生命周期中没有 attributeChangedCallback 函数。

差异点二:Web Components 规范为何没有 created/ready/error 三个函数?

技术规范是一种指导方针,具体的实现方式往往需要根据现实情况决定;Web Components 规范同样如此,它脱离于业务,单纯从技术的角度提供了最基础的标准和参考,具体到实现层面,Vue/React 之类的框架有各自的理解,微信小程序同样也有独到之处。

之所以有差异,一方面是出于各框架开发者对规范的理解和延伸,另一方面是考虑到实际的业务需要,所以往往会有一些规范未覆盖的“创新”之处,最典型的就是 document.ready 事件。在DOMContentLoad 规范推出之前,jQuery 的 $(document).ready 事件已经在前端技术圈盛行了很久,这个事件发生了 window.onload 之前,此时的文档状态处于渲染未完成但是可交互,所以这个事件在优化网站性能的 FIT(First Load Time,提高加载速度)方面被频繁使用。

回到这个问题本身,小程序自定义组件的 created、ready 和 error 三个函数与 document.ready 有异曲同工之妙,都是结合框架本身特色以及业务需求所开发的超越标准规范之外的“创新”。

总的来说,以上两个差异点的核心原因可以概括为一句话:理论上的规范在实现的时候需要结合现实的客观条件。规范是上层实现的参考标准,但并没有限制和框定上层实现的具体模式。差异点一是由于小程序不存在操作 DOM 的情况,差异点二是由于created、ready 和 error 三个函数是超出规范之外、小程序根据自身技术特色的一种“创新”。 理解了自定义组件的资源管理和生命周期之后,你便可以开发出一个优秀的自定义组件了。但是正如上文提到的,一个 Page 中可能存在多个自定义组件,这些组件都是服务于同一个页面,难免会有一些数据上的流通。这时候就会遇到一个组件化领域非常典型的问题:各组件之间如何通信?

开发一个toast自定义组件

// toast.wxml
<view class="container {{mask?'containerShowMask':'containerNoMask'}}" hidden="{{!status}}" style="z-index:{{zIndex}}">
  <view class="loreal-bg-class toast-bg" wx:if="{{mask}}"></view>
  <view class="loreal-class toast toast-{{placement || 'bottom'}}" style="padding-top:{{(placement  || 'bottom')=== 'bottom' ?  image || icon ? '25rpx': '': ''}};position:relative;left:{{offsetX}}rpx;top:{{offsetY}}rpx;margin-bottom:{{distance}}px">
    <image class="loreal-image-class toast-icon" wx:if="{{image}}" src="{{image}}"/>
    <l-icon class="loreal-icon-class toast-icon toast-icon-{{icon === 'loading'?'loading':''}}" wx:elif="{{icon && !image}}" size="{{iconSize? iconSize : 60}}" color="{{iconColor? iconColor: icon === 'success'? '#00C292' :  icon === 'error' ? '#F4516C' : '#ffffff'}}" name="{{icon}}"/>
    <slot wx:else/>
    <text class="toast-text loreal-title-class toast-text-{{placement}}" style="{{placement || 'bottom' === 'bottom' ? icon || image? 'margin-top:10rpx' : '': '' }}">{{ title }}</text>
  </view>
</view>

// toast.js
import validator from "../behaviors/validator";
  Component({
      externalClasses: ["loreal-class", "loreal-label-class", "loreal-hover-class", "loreal-img-class", "loreal-icon-class"],
      behaviors: [validator],
      properties: {
          name: {
              type: String,
              value: "lin"
          },
          type: {
              type: String,
              value: "default",
              options: ["warning", "success", "error", "default"]
          },
          plain: Boolean,
          size: {
              type: String,
              value: "medium",
              options: ["medium", "large", "mini", "long"]
          },
          shape: {
              type: String,
              value: "circle",
              options: ["square", "circle", "semicircle"]
          },
          disabled: {
              type: Boolean,
              value: !1
          },
          special: {
              type: Boolean,
              value: !1
          },
          loading: {
              type: Boolean,
              value: !1
          },
          width: Number,
          height: Number,
          icon: String,
          image: String,
          bgColor: String,
          iconColor: String,
          iconSize: String,
          openType: String,
          appParameter: String,
          lang: String,
          hoverStopPropagation: Boolean,
          hoverStartTime: {
              type: Number,
              value: 20
          },
          hoverStayTime: {
              type: Number,
              value: 70
          },
          sessionFrom: {
              type: String,
              value: ""
          },
          sendMessageTitle: String,
          sendMessagePath: String,
          sendMessageImg: String,
          showMessageCard: Boolean,
          formType: String
      },
      methods: {
          handleTap() {
              if (this.data.disabled || this.data.loading) return !1;
              this.triggerEvent("lintap", {}, {
                  bubbles: !0,
                  composed: !0
              })
          },
          openTypeEvent(e) {
              this.triggerEvent(e.type, e.detail, {})
          }
      }
 });

// toast.wxss
.container{position:fixed}.containerNoMask{left:50%;top:50%;transform:translate(-50%,-50%)}.containerShowMask{height:100%;width:100%;top:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:999}.container .toast-bg{height:100%;width:100%;background:rgba(255,255,255,.5);position:absolute;top:0;left:0}.container .toast-top{flex-direction:column-reverse}.container .toast-right{flex-direction:row}.container .toast-bottom{flex-direction:column}.container .toast-left{flex-direction:row-reverse}.container .toast{display:flex;align-items:center;justify-content:center;max-width:400rpx;min-width:280rpx;min-height:88rpx;background:rgba(0,0,0,.7);border-radius:12rpx;color:#fff;font-size:28rpx;line-height:40rpx;box-sizing:border-box;padding:30rpx 50rpx;z-index:999}.container .toast .toast-icon{margin-top:20rpx;margin-bottom:20rpx}.container .toast .toast-icon-loading{animation:loading-fadein 1.5s linear 0s infinite}.container .toast .toast-text{display:inline-block;text-align:center}.container .toast .toast-text-right{display:inline-block;text-align:center;margin-left:20rpx}.container .toast .toast-text-left{display:inline-block;text-align:center;margin-right:20rpx}.container .toast .toast-text-top{margin-bottom:10rpx}@keyframes loading-fadein{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}
复制代码

除此之外,自定义组件还有一些特殊的生命周期,它们并非与组件有很强的关联,但有时组件需要获知,以便组件内部处理。这样的生命周期称为“组件所在页面的生命周期”,在 pageLifetimes 定义段中定义。其中可用的生命周期包括:

生成的组件实例可以在组件的方法、生命周期函数和属性 observer 中通过 this 访问。组件包含一些通用属性和方法。

image.png

image.png

组件传出数据到主页面

组件间交互的主要形式是自定义事件。组件通过 this.triggerEvent() 触发自定义事件,主页面在组件上 bind:myevent="onMyEvent" 来接收自定义事件。其中,this.triggerEvent() 方法接收自定义事件名称外,还接收两个对象,eventDetail 和 eventOptions。

<!-- 在自定义组件中 -->
<button bindtap="onTap">点击这个按钮将触发“myevent”事件</button>
Component({
  properties: {}
  methods: {
    // 子组件触发自定义事件
    ontap () {
    // 所有要带到主页面的数据,都装在eventDetail里面
    var eventDetail = {
            name:'sssssssss',
            test:[1,2,3]
    }
    // 触发事件的选项 bubbles是否冒泡,composed是否可穿越组件边界,capturePhase 是否有捕获阶段
    var eventOption = {
            composed: true
    }
    this.triggerEvent('myevent', eventDetail, eventOption)
    }
  }
})
复制代码

触发的事件包括:

监听事件

自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。监听自定义组件事件的方法与监听基础组件事件的方法完全一致:在Page事件中监听组件中传递过来的值。

Page({
  onMyEvent: function(e){
    e.detail // 自定义组件触发事件时提供的detail对象
  }
})
复制代码

behaviors

behaviors 是用于组件间代码共享的特性,类似于一些编程语言中的“mixins”或“traits”。每个 behavior 可以包含一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个 behavior 。behavior 也可以引用其他 behavior 。

// validator.js
module.exports = Behavior({
  behaviors: [],
  properties: {
    myBehaviorProperty: {
      type: String
    }
  },
  data: {
    myBehaviorData: {}
  },
  attached: function(){},
  methods: {
    myBehaviorMethod: function(){}
  }
})
复制代码

组件引用时,在 behaviors 定义段中将它们逐个列出即可。

// my-component.js
var myBehavior = require('my-behavior')
Component({
  behaviors: [myBehavior],
  properties: {
    myProperty: {
      type: String
    }
  },
  data: {
    myData: {}
  },
  attached: function(){},
  methods: {
    myMethod: function(){}
  }
})
复制代码

字段的覆盖和组合规则

组件和它引用的 behavior 中可以包含同名的字段,对这些字段的处理方法如下:如果有同名的属性或方法,组件本身的属性或方法会覆盖 behavior 中的属性或方法,如果引用了多个 behavior ,在定义段中靠后 behavior 中的属性或方法会覆盖靠前的属性或方法;如果有同名的数据字段,如果数据是对象类型,会进行对象合并,如果是非对象类型则会进行相互覆盖;生命周期函数不会相互覆盖,而是在对应触发时机被逐个调用。如果同一个 behavior 被一个组件多次引用,它定义的生命周期函数只会被执行一次。内置behavior 组件间关系

<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: function(target) {
        // 每次有custom-li被插入时执行,target是该节点实例对象,触发在该节点attached生命周期之后
      },
      linkChanged: function(target) {
        // 每次有custom-li被移动后执行,target是该节点实例对象,触发在该节点moved生命周期之后
      },
      unlinked: function(target) {
        // 每次有custom-li被移除时执行,target是该节点实例对象,触发在该节点detached生命周期之后
      }
    }
  },
  methods: {
    _getAllLi: function(){
      // 使用getRelationNodes可以获得nodes数组,包含所有已关联的custom-li,且是有序的
      var nodes = this.getRelationNodes('path/to/custom-li')
    }
  },
  ready: function(){
    this._getAllLi()
  }
})
// 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生命周期之后
      }
    }
  }
})
复制代码

组件间的通信流程

与 Vue/React 不同,小程序没有类似 Vuex 或 Redux 数据流管理模块,所以小程序的自定义组件之间的通信流程采用的是比较原始的事件驱动模式,即子组件通过抛出事件将数据传递给父组件,父组件通过 properties 将数据传递给子组件。

假设小程序的某个页面中存在两个组件,两个组件均依赖父组件(Page)的部分属性,这部分属性通过 properties 传递给子组件,如下图所示:

image.png

当组件 A 需要与组件 B 进行通信时,会抛出一个事件通知父组件 Page,父组件接收到事件之后提取事件携带的信息,然后通过 properties 传递给组件 B。这样便完成了子组件之间的消息传递。

除了事件驱动的通信方式以外,小程序还提供了一种更加简单粗暴的方法:父组件通过selectComponent 方法直接获取某个子组件的实例对象,然后就可以访问这个子组件的任何属性和方法了。随后将这个子组件的某个属性通过 properties传递个另外一个子组件。相较而言,事件驱动的方法更加优雅,在流程上也更加可控,所以通常建议使用事件驱动的通信方式。

总结

  • 1.因为WXML节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。
  • 2.自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用 usingComponents 字段)。
  • 3.自定义组件和使用自定义组件的页面所在项目根目录名不能以“wx-”为前缀,否则会报错。
  • 4.旧版本的基础库不支持自定义组件,此时,引用自定义组件的节点会变为默认的空节点。
文章分类
前端
文章标签