手百小程序-自定义组件

自定义组件的创建和使用

开发者可以将页面内的功能模块抽象成自定义组件,在智能小程序的各个页面中进行使用,提升代码复用度,节省开发成本。

解释:

一个自定义组件由4个文件 (.swan .css .js .json) 组成, 例如包含自定义组件 custom 的项目结构:

// 包含自定义组件custom的项目结构
├── app.js
├── app.json
├── project.swan.json
└── components
    └── custom
        ├── custom.swan
        ├── custom.css
        ├── custom.js
        └── custom.json

要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(在 json 文件中将 component 字段设为 true 可将这一组文件设为自定义组件):

// 自定义组件配置 (custom.json)
{
  "component": true
}

同时,类似于页面开发。开发自定义组件,可以在 swan 文件中编写组件模板,在 css 文件中引入样式,它们的写法和页面的写法类似。

示例代码

<!-- 自定义组件内部的模板 (custom.swan) -->
<view class="name">
    {{name}}
</view>
/* 自定义组件的css,在该自定义组件内部生效 (custom.css) */
.name {
    color: red;
}

在自定义组件的 js 文件中,需要使用 Component() 来注册组件,并提供组件的属性定义内部数据自定义方法

组件的属性值和内部数据将被用于组件 swan 模板的渲染,其中,属性是可以由组件外部传入的。

示例代码

// 自定义组件逻辑 (custom.js)
Component({
    properties: {
        // 定义了name属性,可以在使用组件时,由外部传入。此变量可以直接在组件模板中使用
        name: {
            type: String,
            value: 'swan',
        }
    },
    data: {
        age: 1
    },
    methods: {
        tap: function(){}
    }
})
<!-- 自定义组件内部的模板 (custom.swan) -->
<view class="name" bindtap="tap">
    {{name}}{{age}}
</view>

使用自定义组件

使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。除了在页面使用自定义组件,你还可以在自定义组件引用自定义组件,类似于页面引用自定义组件。
以下举例页面级(pages/home/home)的使用场景:

// 项目目录结构
├── app.js
├── app.json
├── project.config.json
└──  pages
    └── home
        ├── home.swan
        ├── home.css
        ├── home.js
        └── home.json
└── components
    └── custom
        ├── custom.swan
        ├── custom.css
        ├── custom.js
        └── custom.json

首先需要提供每个自定义组件的标签名与其对应的自定义组件文件路径。

提示:

1.自定义组件文件路径: 自定义组件swan、css、js、json文件所在路径 + 该类文件的basename, 例如以上项目目录结构,该路径即是/components/custom/custom

2.创建自定义组件,推荐内层的文件(swan、css、js、json)与其自定义组件目录保持同名。

示例代码

// 页面json配置 home.json
{
    "usingComponents": {
        "custom": "/components/custom/custom"
    }
}

这样,在页面的 swan 文件中,就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名节点属性即传递给组件的属性值。

示例代码

<!-- 页面模板 (home.swan) -->
<view>
    <!-- 在页面中对自定义组件进行引用 -->
    <custom name="swanapp"></custom>
</view>

自定义组件的 swan 节点结构在与数据结合之后,将被插入到引用位置内。

说明:

  • 自定义组件只能在1.10.13以上的 swan.js 中使用;
  • 同一页面引用的自定义组件,不同路径的自定义组件暂时不能使用相同的自定义组件名字;
  • 在页面级的配置(json文件)中不能添加 "component": true,因为将page当做自定义组件使用是不允许的;
  • 对于自定义组件中的资源引用路径,使用相对路径目前针对的是自定义组件SWAN文件所对应的目录层级,故暂时推荐使用绝对路径。

组件模板和样式

解释: 类似于页面,自定义组件拥有自己的 swan 模板和 css 样式。

组件模板

组件模板的写法与页面模板相同。组件模板与组件数据结合后生成的节点树,将被插入到组件的引用位置上。

在组件模板中可以提供一个 <slot> 节点,用于承载组件引用时提供的子节点。

示例代码

<!-- 组件内部模板 -->
<view class="wrapper">
    <view>组件内部节点</view>
    <slot></slot>
</view>
<view>
    <custom-component>
        <view>这里是插入到组件slot中的内容</view>
    </custom-component>
</view>

模板数据绑定

与普通的 SWAN 模板类似,可以使用数据绑定,这样就可以向子组件的属性传递动态数据。

<view>
    <custom-component prop-a="{{dataFieldA}}" prop-b="{{dataFieldB}}">
        <view>这里是插入到组件slot中的内容</view>
    </custom-component>
</view>

在以上例子中,组件的属性 propA 和 propB 将收到页面传递的数据。页面可以通过 setData 来改变绑定的数据字段。

组件的slot

解释:
在组件的视图模板中可以通过 slot 声明一个插槽的位置,其位置的内容可以由外层组件或者页面定义。

示例代码

<!-- 组件中的定义 -->
<view>
    <slot></slot>
</view>
<!-- 使用组件的页面或者组件 -->
<view>
    <custom-component>
        <view>我是slot中插入的节点</view>
    </custom-component>
</view>

通过 name 属性可以给 slot 命名。一个视图模板的声明可以包含一个默认 slot 和多个命名 slot。外层组件或页面的元素通过 slot="name" 的属性声明,可以指定自身的插入点。

示例代码

<view>
    <slot name="slot1"></slot>
    <slot name="slot2"></slot>
</view>
<view>
    <custom-component>
        <view slot="slot1">我会被插入到组件上方</view>
        <view slot="slot2">我会被插入到组件下方</view>
    </custom-component>
</view>

slot指令应用

解释:
在 slot 声明时应用 if 或 for 指令,可以让插槽根据组件数据动态化。 示例代码**

<view>
    <slot s-if="!visible" name="subcomponent"></slot>
</view>

数据环境

解释:
插入 slot 部分的内容,其数据环境为声明时的环境。

示例代码

<!-- custom-component自定义组件 -->
<view class="component-range">
    <slot name="inner"></slot>
</view>
Component({
    data: {
        name: 'swan-inner'
    }
});
<!-- 使用组件的页面或者组件 -->
<view>
    <custom-component>
        <view>{{name}}</view>
    </custom-component>
</view>
Page({
    data: {
        name: 'swan-outer'
    }
});

渲染结果:

<view>
    <view class="component-range">
        <view>swan-outer</view>
    </view>
</view>

scoped 插槽

解释:

如果 slot 声明中包含 s-bind 或 1 个以上 var- 数据前缀声明,该 slot 为 scoped slot。scoped slot 具有独立的 数据环境。
scoped slot 通常用于组件的视图部分期望由 外部传入视图结构,渲染过程使用组件内部数据。

示例代码

<!-- custom-component自定义组件 -->
<view class="component-range">
    <slot name="inner" var-name="name"></slot>
</view>
Component({
    data: {
        name: 'swan-inner'
    }
});
<!-- 使用组件的页面或者组件 -->
<view>
    <custom-component>
        <view>{{name}}</view>
    </custom-component>
</view>
Page({
    data: {
        name: 'swan-outer'
    }
});

渲染结果:

<view>
    <view class="component-range">
        <view>swan-inner</view>
    </view>
</view>

组件样式

组件的样式,可以在组件的 css 文件中编写,并且只对当前组件内节点生效。使用时,需要注意以下几点:

  1. 只可以使用 class 选择器,其他的选择器,请改为 class 选择器实现;
  2. 组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用;
  3. 继承样式,如 font 、 color,会从组件外继承到组件内。

外部样式类

解释:
当组件希望接受外部传入的样式类(类似于 view 组件的 hover-class 属性)时,可以在 Component 中用 externalClasses 定义段定义若干个外部样式类。

注意:在同一个节点上使用普通样式类和外部样式类时,请避免出现两个类的优先级是未定义的情况。

示例代码

/* 组件 custom-component.js */
Component({
    externalClasses: ['external-class']
});
<!-- 组件 custom-component.swan -->
<view class="external-class">这段文本的颜色由组件外的 class 决定</view>

组件的使用者可以像使用其他属性一样,指定这个样式类对应的 class 。

<!-- 使用组件的页面或者组件 -->
<custom-component external-class="red-text" />
.red-text {
    color: red;
}

全局样式类

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

示例代码

/* 组件 custom-component.js */
Component({
    options: {
        addGlobalClass: true,
    }
});
<!-- 组件 custom-component.swan -->
<text class="global-class">这段文本的颜色由组件外的 class 决定</text>
/* 组件外的样式定义 */
.global-class {
    color: red;
}

// JSON 文件里引用
{
    "usingComponents": {
        "custom": "/components/custom/custom"
    }
}
// /components/custom/custom的样式
.cus { color: red; }

那么 custom的样式会转变为

.custom__cus { color: red; }

所以当新样式的优先级高于加了前缀的样式时,才能覆盖。 

Component构造器

定义段与示例方法

解释:

Component构造器可用于定义组件,调用Component构造器时可以指定组件的属性、数据、方法等。

字段类型是否必填描述最低版本
properties Object Map 组件的对外属性,是属性名到属性设置的映射表,属性设置中可包含三个字段, type 表示属性类型、 value 表示属性初始值、 observer 表示属性值被更改时的响应函数  
data Object 组件的内部数据,和 properties 一同用于组件的模板渲染  
methods Object 组件的方法,包括事件响应函数和任意的自定义方法,关于事件响应函数的使用,参见 组件事件  
behaviors Array. 类似于mixins和traits的组件间代码复用机制,参见 behaviors  
created Function 组件生命周期函数,在组件实例进入页面节点树时执行,注意此时不能调用setData,参见 组件生命周期  
attached Function 组件生命周期函数,在组件实例进入页面节点树时执行,参见 组件生命周期  
ready Function 组件生命周期函数,在组件布局完成后执行,此时可以获取节点信息,参见 组件生命周期  
detached Function 组件生命周期函数,在组件实例被从页面节点树移除时执行,参见 组件生命周期  
externalClasses Array. 组件接受的外部样式类,参见组件模板和样式 1.13.27
options Object Map 一些选项(文档中介绍相关特性时会涉及具体的选项设置,这里暂不列举) 1.13.27
lifetimes Object 组件生命周期声明对象,组件的生命周期:created、attached、ready、detached将收归到lifetimes字段内进行声明,原有声明方式仍旧有效,如同时存在两种声明方式,则lifetimes字段内声明方式优先级最高,参见 组件生命周期 1.13.27
pageLifetimes Object 组件所在页面的生命周期声明对象,目前仅支持页面的show和hide两个生命周期,参见 组件生命周期 1.13.27
definitionFilter Function 定义段过滤器,用于自定义组件扩展,参见自定义组件扩展 1.13.27

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

属性名类型描述最低版本
is String 组件的文件路径 1.13.27
id String 节点id 1.13.27
dataset String 节点dataset 1.13.27
data Object 组件数据,包括内部数据和属性值  
properties Object 组件数据,包括内部数据和属性值(与data一致)。
方法名参数描述最低版本
setData Object newData 设置data并执行视图层渲染  
hasBehavior Object 检查组件是否具有 behavior (检查时会递归检查被直接或间接引入的所有behavior) 1.13.27
triggerEvent String name, Object detail 触发事件,参见 组件事件  
createSelectorQuery   创建一个 SelectorQuery 对象,选择器选取范围为这个组件实例内, 与 swan.createSelectorQuery().in(this) 是等效 2.5.3
createIntersectionObserver Object options 创建一个 IntersectionObserver 对象,选择器选取范围为这个组件实例内 2.5.3
selectComponent String selector 使用选择器选择组件实例节点,返回匹配到的第一个组件实例对象(会被 swan://component-export 影响),在生命周期 onReay 开始时生效  
selectAllComponents String selector 使用选择器选择组件实例节点,返回匹配到的全部组件实例对象组成的数组  
groupSetData Function callback 立刻执行 callback ,其中的多个 setData 之间不会触发界面进行重复绘制 2.10.7

 示例代码

// 自定义组件js
Component({
    properties: {
        propName: { // 属性名
            type: String, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
            value: 'val', // 属性初始值(必填)
            observer: function(newVal, oldVal) {
                // 属性被改变时执行的函数(可选)
            }
        }
    },

    data: {}, // 私有数据,可用于模板渲染

    // 生命周期函数,可以为函数,或一个在methods段中定义的方法名
    attached: function () {},

    detached: function () {},

    methods: {
        onTap: function () {
            this.setData({
               // 更新属性和数据的方法与更新页面数据的方法类似
            });
        }
    }
});

注意:
在 properties 定义段中,属性名应该采用驼峰写法(propsName);在 swan 模板中,指定属性值时则对应使用连字符写法( component-tag-name props-name="props value" )。

使用 Component 构造器构造页面

解释:
事实上,一个自定义组件也可以视为一个页面,故页面也可以使用 Component 构造器构造,拥有与普通组件一样的定义段与实例方法,其必要配置项(json)与正常自定义组件一致,即需要有component: true字段。

关于页面传参:
从其它页面跳转到由自定义组件构造的页面时,如跳转到页面 /components/custom/custom?paramA=123&paramB=xyz ,你可以在由custom组件构造的页面onLoad生命周期中获取传递的query字段。

注意:
页面的生命周期方法(即 on 开头的方法),应写在 methods 定义段中。

示例代码
{
    "component": true,
    "usingComponents": {}
}
/* /components/custom/custom.js */
Component({
    methods: {
        onLoad: function(options) {
            console.log(options.paramA); // 123
            console.log(options.paramB); // xyz
        }
    }
});

说明:

  • 使用 this.data 可以获取内部数据和属性值,但不要直接修改它们,应使用 setData 修改
  • 属性名应避免以 data- 开头,因为在 SWAN 中,data-name='swan' 会被作为节点 dataset 来处理
  • 属性名应避免以 prop- 开头 在处理过程中会将该前缀删除;
  • 属性名应避免包含 “ _ ” 字符, 因为在渲染过程中将会以 “ _ ” 字符进行拆分。

组件间通信与事件

组件间通信

组件间的基本通信方式有以下几种:

  • 在父组件中可以通过设置子组件的properties来向子组件传递数据;
  • 在父组件中定义messages对象,对子组件dispatch方法进行拦截,从而达到子组件向上通信;
  • 子组件可以通过triggerEvent方法触发父组件的自定义事件进行传参;
  • 如果以上几种方式不足以满足需要,父组件还可以通过 this.selectComponent 方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。

监听事件

解释:

事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。

监听自定义组件事件的方法与监听基础组件事件的方法完全一致:

示例代码

<!-- 父组件模板 -->
<component-tag-name bindmyevent="onMyEvent" />

通过dispatch方法与父组件通信

通过 dispatch 方法,子组件可以向组件树的上层派发消息。消息将沿着组件树向上传递,直到遇到第一个处理该消息的组件,则停止。
通过 messages 可以声明组件要处理的消息,messages 是一个对象,key 是消息名称,value 是消息处理的函数,接收一个包含 target(派发消息的组件) 和 value(消息的值) 的参数对象。

示例代码

/* 父组件逻辑 */
Component({
    messages: {
        'childmessage': function (e) {
            console.log('childmessage', e);
        }
    }
});
/* 子组件逻辑 */
Component({
    created() {
        this.dispatch('childmessage', {
            name: 'swan'
        });
    }
});

通过triggerEvent方法与父组件通信

解释:
自定义组件触发事件时,需要使用 triggerEvent 方法,指定事件名和detail对象:

<button bindtap="onTap">点击这个按钮将触发 myevent 事件</button>

示例代码

/* 组件逻辑 */
Component({
    properties: {},
    methods: {
        onTap: function() {
            var myEventDetail = {} // detail对象,提供给事件监听函数
            this.triggerEvent('myevent', myEventDetail);
        }
    }
});
/* 页面逻辑 */
Page({
    onMyEvent: function (e) {}
})

注意:

  • 对于 triggerEvent 方法,在基础库版本 2.0.3 之前(不包含2.0.3)只支持传递类型为object的数据,从 2.0.3 开始支持传递其它数据类型(不包括function和undefined),其它低版本请做好兼容
  • 对于很多UI组件库需要实现组件间关系,实际上组件间通信同样可以满足此需求。(之前组件间通信无法在存在 slot 环境使用,我们将于基础版本库 3.110.14 修复此问题)详细内容

组件生命周期

组件的主要生命周期

解释:自定义组件的生命周期,指的是组件自身的一些可自执行的方法,这些方法会在特殊的时间点或遇到一些特殊页面行为时被自动触发而执行。

组件的生命周期:created、 attached、 ready 、detached, 这些方法包含了一个组件实例生命流程的主要时间点,具体的使用场景如下:

  • created:组件实例刚刚被创建好时, created 生命周期被触发,通常情况下,这个生命周期只应该用于给组件 this 添加一些自定义属性字段;
  • attached:在组件完全初始化完毕、进入页面节点树后, attached 生命周期被触发。此时, this.data 已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行;
  • ready:在attached生命被触发之后,组件的ready生命周期会被触发;
  • detached:在组件离开页面节点树后, detached 生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则 detached 方法会被触发。

定义生命周期方法

生命周期方法可以直接定义在 Component 构造器的第一级参数中。

除了以上声明方式,还可以在 lifetimes 字段内进行声明(推荐使用这种方式进行管理,其优先级最高)。

示例代码

// 自定义组件js文件
Component({
    // ...
    lifetimes: {
        attached: function() {
            // 在组件实例进入页面节点树时执行
        },
        detached: function() {
            // 在组件实例被从页面节点树移除时执行
        }
    }
    // ...
});

在内置 behaviors 中也可以编写生命周期方法,但不会与其他 behaviors 中的同名生命周期相互覆盖。

当前自定义自身可用的全部生命周期及其描述如下表所示:

生命周期参数描述最低版本
created 在组件实例刚刚被创建时执行 1.10.13
attached 在组件实例进入页面节点树时执行 1.10.13
ready 在组件在视图层布局完成后执行 1.10.13
detached 在组件实例被从页面节点树移除时执行 1.10.13

组件所在页面的生命周期

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

生命周期参数描述最低版本
show 组件所在的页面被展示时执行 1.13.27
hide 组件所在的页面被隐藏时执行 1.13.27

 
示例代码
// 自定义组件js文件
Component({
    // ...
    pageLifetimes: {
        show: function() {
        // 组件所在的页面被展示时触发
        },
        hide: function() {
        // 组件所在的页面被隐藏时触发
        }
    }
    // ...
});

常用的就是这么多了,高级的内容比较坑,官方的Demo运行之后还有报错,等以后有时间深入研究一下再发出来。

原文地址:https://www.cnblogs.com/sauronblog/p/11837095.html