【愚公系列】《微信小程序与云开发从入门到实践》030-关于自定义组件的高级用法

举报
愚公搬代码 发表于 2024/12/29 11:44:21 2024/12/29
【摘要】 标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。近期荣誉2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主等。博客内容.NET、...
标题 详情
作者简介 愚公搬代码
头衔 华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。
近期荣誉 2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主等。
博客内容 .NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
欢迎 👍点赞、✍评论、⭐收藏

🚀前言

在微信小程序的开发中,自定义组件是实现灵活、可复用代码的重要工具。随着小程序生态的不断发展,开发者对于组件的需求也日益增长,从基础的组件使用到高级的技巧与模式,掌握自定义组件的高级用法已成为提升开发效率和用户体验的关键所在。

本文将深入探讨微信小程序中自定义组件的高级用法,包括组件之间的通信、生命周期管理、插槽机制、样式封装等内容。我们将通过实际案例和最佳实践,帮助您更好地理解和应用这些高级功能,从而为您的项目带来更高的灵活性和可维护性。

无论您是希望优化已有小程序的开发者,还是渴望学习新技术的爱好者,相信本文都将为您提供丰富的知识和实用的技巧,让您在小程序开发的道路上走得更远、更稳。

让我们一起深入探索,让自定义组件的高级用法为您的开发之旅增添无限可能!

🚀一、关于自定义组件的高级用法

目前我们已经对自定义组件的使用有了大致的体验,其实关于自定义组件,还有许多高级的特性供开发者进行使用,本节将向大家介绍这些特性。

🔎1.自定义组件的模板和样式

🦋1.1 自定义组件的 WXML 和 WXSS 文件

☀️1.1.1 WXML 文件

  • WXML 文件用于定义组件的模板,描述组件内部的结构。
  • 组件的模板可以使用插槽(<slot> 标签),插槽类似于接口,允许组件的使用方向组件中传入内容。

☀️1.1.2 WXSS 文件

  • WXSS 文件用于定义组件的样式。组件内的样式只会作用于组件内部的元素,而不会影响外部页面或其他组件。
  • 在编写组件的 WXSS 样式时,需要注意以下几点:
    • 不使用:ID选择器、属性选择器和标签名选择器,推荐使用类选择器。
    • 避免使用:子选择器和后代选择器,因为某些情况下会导致异常。
    • 组件样式有继承关系,外部样式可以影响组件,组件的样式也会影响内部元素。

🦋1.2 插槽的使用

插槽是一种允许外部内容插入组件的机制。组件可以通过插槽定义一个或多个内容区,从而使得组件更加灵活。

☀️1.2.1 默认插槽

  • 插槽没有指定名称时,被称为“默认插槽”。
  • 默认插槽会接收组件标签内部的内容,并把它插入到 <slot> 标签所在的位置。

示例:

<!-- component1.wxml -->
<view>
  <view style="text-align: center;">{{title}}</view>
  <slot></slot> <!-- 默认插槽 -->
</view>

<!-- 使用组件时 -->
<component1 title="按钮">
  <button type="primary" size="mini">按钮1</button>
</component1>

在这个示例中,<button> 标签会被插入到 <slot></slot> 标签的位置。

☀️1.2.2 具名插槽

  • 如果需要多个插槽,可以通过设置 options: { multipleSlots: true } 来启用多插槽功能。
  • 每个插槽都可以通过 name 属性进行命名,以区分不同插槽的位置。

示例:

<!-- component1.wxml -->
<view>
  <view style="text-align: center;">{{title}}</view>
  <slot></slot> <!-- 默认插槽 -->
  <view style="display: flex; flex-direction: row; justify-content: space-between;">
    <slot name="left"></slot>  <!-- 左侧插槽 -->
    <slot name="right"></slot> <!-- 右侧插槽 -->
  </view>
</view>

<!-- 使用组件时 -->
<component1 title="按钮组">
  <button type="primary" size="mini">按钮1</button>
  <button type="primary" size="mini">按钮2</button>
  <button type="primary" size="mini">按钮3</button>
  <view slot="left">左视图</view> <!-- 左插槽 -->
  <view slot="right">右视图</view> <!-- 右插槽 -->
</component1>

在这个例子中,<view slot="left"> 会插入到 <slot name="left"> 插槽中,<view slot="right"> 会插入到 <slot name="right"> 插槽中,而没有指定 slot 的内容会插入到默认插槽中。


🦋1.3 组件样式隔离与外部样式管理

☀️1.3.1 样式隔离

为了避免组件的样式影响到页面的其他部分,或页面样式影响到组件,可以通过配置 styleIsolation 来进行样式隔离。

styleIsolation 选项的可选值:

  • isolated:启用样式隔离,组件内部的样式与外部页面样式不会互相影响。
  • apply-shared:页面的样式会影响到组件内部,但组件内的样式不会影响页面。
  • shared:组件与页面共享样式,组件内部的样式与外部页面样式可以相互影响。

示例:

// component1.js
Component({
  options: {
    styleIsolation: 'isolated'  // 启用样式隔离
  }
});

☀️1.3.2 接收外部样式

为了让组件的样式更加灵活,组件可以通过 externalClasses 选项接受外部样式。使用外部类名可以让外部的样式覆盖组件的默认样式。

示例:

// component1.js
Component({
  externalClasses: ['title-class']  // 允许外部传入 'title-class' 样式
});
<!-- component1.wxml -->
<view>
  <view style="text-align: center;">
    <text class="title-class">{{title}}</text> <!-- 使用外部传入的样式 -->
  </view>
  <slot></slot>
  <view style="display: flex; flex-direction: row; justify-content: space-between;">
    <slot name="left"></slot>
    <slot name="right"></slot>
  </view>
</view>

在使用组件时,可以通过传入自定义的样式来覆盖组件的默认样式:

<!-- 使用组件时 -->
<component1 title="按钮组" title-class="title"></component1>

<!-- 在 customComponent.wxss 中 -->
.title {
  color: red;  /* 修改标题颜色为红色 */
}

在这里插入图片描述

🦋1.4 总结

  1. WXML 和 WXSS 文件:

    • WXML 用于定义组件的结构,支持插槽(默认插槽和具名插槽)。
    • WXSS 用于定义组件的样式,推荐使用类选择器并避免使用 ID、属性选择器等。
  2. 插槽的使用:

    • 默认插槽:没有 name 属性的 <slot> 标签。
    • 具名插槽:通过 name 属性区分多个插槽。
  3. 样式管理:

    • 使用 styleIsolation 配置样式隔离模式。
    • 通过 externalClasses 让组件能够接收外部传入的样式。

通过这些机制,组件的设计可以更加灵活和模块化,方便开发者进行复用和定制化。

🔎2.组件间的通信

🦋2.1 组件间通信方式概述

在小程序中,组件间的通信主要分为以下几种情况:

  1. 父组件(页面)向子组件传递数据
  2. 子组件向父组件传递数据
  3. 父组件通过子组件实例直接获取子组件的数据或调用子组件的方法

我们将重点讨论第2种和第3种应用场景。


🦋2.2 父组件向子组件传递数据

父组件向子组件传递数据通常通过 properties 外部属性进行实现。父组件在使用子组件时,通过设置子组件的 properties 属性来传递数据。这种方式是相对简单和直接的,且在上文已经有过讨论,这里不再重复。


🦋2.3 子组件向父组件传递数据

子组件向父组件传递数据一般通过 自定义事件 来实现。子组件通过 triggerEvent 方法触发自定义事件,将需要传递的数据作为参数传递给父组件。

☀️2.3.1 触发自定义事件

  1. 在子组件的 WXML 文件中绑定事件:

    <!-- component1.wxml -->
    <text class="title-class" bindtap="tapTitle">{{title}}</text>
    
  2. 在子组件的 JS 文件中定义触发事件的方法:

    // component1.js
    Component({
      properties: {
        title: {
          type: String,
          value: '默认标题'
        }
      },
      methods: {
        tapTitle: function() {
          // 触发自定义事件,将数据传递给父组件
          this.triggerEvent('titleTapEvent', {
            title: this.properties.title
          });
        }
      }
    });
    
  3. 父组件在使用子组件时绑定事件:

    <!-- pages/customComponent/customComponent.wxml -->
    <component1 title="按钮组" bindtitleTapEvent="tapEvent" title-class="title"></component1>
    
  4. 在父组件的 JS 文件中监听子组件的事件:

    // customComponent.js
    Page({
      tapEvent: function(event) {
        console.log(event.detail.title);  // 输出:按钮组
      }
    });
    

在这个例子中,父组件通过 bindtitleTapEvent="tapEvent" 来监听子组件的 titleTapEvent 事件,子组件通过 this.triggerEvent 将数据传递给父组件,父组件可以在回调中获取事件的数据。

☀️2.3.2 triggerEvent 的第三个参数

triggerEvent 方法有一个可选的第三个参数,可以设置事件的一些额外选项,包括事件是否冒泡、是否可以穿越组件边界等。第三个参数的选项包括:

属性名 类型 说明
bubbles 布尔值 事件是否冒泡
capturePhase 布尔值 事件是否拥有捕获阶段
composed 布尔值 事件是否可以穿越组件边界(默认为 false)

示例:

this.triggerEvent('titleTapEvent', { title: this.properties.title }, { bubbles: true, composed: true });

🦋2.4 父组件通过子组件实例直接调用子组件方法

在某些情况下,父组件可能需要获取子组件的实例,以便直接调用子组件中的方法或访问子组件的数据。这可以通过 selectComponent 方法来实现。

☀️2.4.1 获取子组件实例

  1. 在父组件的 WXML 文件中为子组件添加类名:

    <!-- pages/customComponent/customComponent.wxml -->
    <component1 class="my-component" title="按钮组" bindtitleTapEvent="tapEvent" title-class="title"></component1>
    
  2. 在父组件的 JS 文件中通过 selectComponent 获取子组件实例:

    // customComponent.js
    Page({
      onShow: function() {
        // 获取子组件实例
        let component = this.selectComponent('.my-component');
        // 获取子组件中的数据
        console.log(component.properties.title);  // 输出:按钮组
        // 调用子组件的方法
        component.tapTitle();  // 调用子组件的 tapTitle 方法
      }
    });
    

通过 selectComponent,父组件可以访问到子组件的实例,从而获取其数据或调用其方法。

☀️2.4.2 通过 component-export Behavior 导出组件方法

有时开发者不希望让外部直接访问子组件的内部数据和方法,这时候可以使用 component-export Behavior 来导出子组件的部分数据和方法。

  1. 在子组件中使用 component-export:

    // component1.js
    Component({
      behaviors: ['wx://component-export'],
      properties: {
        title: { type: String, value: '默认标题' }
      },
      methods: {
        tapTitle: function() {
          this.triggerEvent('titleTapEvent', { title: this.properties.title });
        }
      },
      export: function() {
        return {
          outData: '暴露到外部的数据',
          outFunction: function() {
            console.log('暴露给外部的方法');
          }
        };
      }
    });
    
  2. 在父组件中获取导出的内容:

    // customComponent.js
    Page({
      onShow: function() {
        // 获取子组件实例
        let component = this.selectComponent('.my-component');
        // 获取导出的数据和方法
        console.log(component.outData);  // 输出:暴露到外部的数据
        component.outFunction();  // 输出:暴露给外部的方法
      }
    });
    

通过 component-export,父组件只能访问到子组件明确暴露的数据和方法,避免了直接访问子组件的内部实现,从而增强了组件的封装性。


🦋2.5 总结

  1. 父组件向子组件传递数据:使用 properties 外部属性。
  2. 子组件向父组件传递数据:使用自定义事件 triggerEvent 触发事件,将数据传递给父组件。
  3. 父组件通过子组件实例直接操作子组件:通过 selectComponent 获取子组件实例,访问其数据或调用其方法。
  4. 组件方法的封装:使用 component-export Behavior 来按需暴露子组件的方法和数据,保护内部实现。

通过这些通信方式,小程序中的组件化开发变得更加灵活,能够有效地管理父子组件之间的交互,提升应用的可维护性和扩展性。

🔎3.组件间的依赖关系

🦋3.1 组件间依赖关系的定义

在小程序中,复杂的自定义组件可能由多个子组件构成,且这些子组件之间可能存在依赖关系。例如,想要开发一个列表组件,它至少需要两个子组件:一个是列表框架(customList),另一个是列表项(customItem)。在这种情况下,customItem 必须是 customList 的子组件,这就创建了组件之间的依赖关系。

☀️3.1.1 组件结构示例

假设你要开发一个自定义列表组件(customList)和列表项组件(customItem),项目结构如下:

components/
  └── component2/
      ├── customList/
      │   ├── customList.wxml
      │   ├── customList.js
      │   ├── customList.json
      │   └── customList.wxss
      └── customItem/
          ├── customItem.wxml
          ├── customItem.js
          ├── customItem.json
          └── customItem.wxss

🦋3.2 组件间关系配置

通过配置 relations 来定义组件之间的关系。在你提到的案例中,customItem 是 customList 的子组件,可以通过 relations 选项进行关联。

☀️3.2.1 在 customList 组件中定义关系

在 customList.wxml 中定义组件结构,使用 <slot></slot> 插入子组件内容:

<!-- component/component2/customList.wxml -->
<view>
  <view>header</view>
  <slot></slot>
</view>

在 customList.js 中,使用 relations 来设置与 customItem 的关系:

// component/component2/customList.js
Component({
  relations: {
    './customItem': {
      type: 'child',  // 指定 customItem 为 child 组件
      linked: function(target) {
        console.log('Item组件被插入');
      },
      linkChanged: function(target) {
        console.log('Item组件发生变化');
      },
      unlinked: function(target) {
        console.log('Item组件被移除');
      }
    }
  }
});
  • type: 'child' 表示 customItem 是 customList 的子组件。
  • linked、linkChanged 和 unlinked 是回调函数,分别在子组件插入、位置变化和移除时触发。

☀️3.2.2 组件间的依赖关系说明

在上述代码中,父组件 customList 与子组件 customItem 之间的关系被通过 relations 来定义。当 customItem 被插入到 customList 时,linked 回调会触发;当子组件移除时,unlinked 回调会触发;当子组件在 DOM 树中位置发生变化时,linkChanged 会触发。

☀️3.2.3 定义祖先和后代组件关系

除了父子关系,还可以定义组件的祖先(ancestor)和后代(descendant)关系。在你的示例中,还提到了列表头部(customHeader)和尾部(customFooter)与列表容器(customList)的关系。

// component/component2/customHeader.js
var custom = require('./custom');
Component({
  behaviors: [custom],
  relations: {
    './customList': {
      type: 'ancestor',  // customList 为祖先节点
      linked: function(target) {
        console.log('Header 插入');
      }
    }
  }
});
// component/component2/customFooter.js
var custom = require('./custom');
Component({
  behaviors: [custom],
  relations: {
    './customList': {
      type: 'ancestor',  // customList 为祖先节点
      linked: function(target) {
        console.log('Footer 插入');
      }
    }
  }
});

🦋3.3 使用 Behavior 进行多组件关联

通过 Behavior 可以实现一对多的组件关系定义,简化多个子组件的关系管理。你可以定义一个空的 Behavior 来将多个组件与 customList 关联。

☀️3.3.1 定义 Behavior

在 custom.js 中定义一个空的 Behavior:

// component/component2/custom.js
module.exports = Behavior({});

然后在 customHeader 和 customFooter 组件中引入该 Behavior:

// component/component2/customHeader.js
var custom = require('./custom');
Component({
  behaviors: [custom],
  relations: {
    './customList': {
      type: 'ancestor',
      linked: function(target) {
        console.log('Header 插入');
      }
    }
  }
});
// component/component2/customFooter.js
var custom = require('./custom');
Component({
  behaviors: [custom],
  relations: {
    './customList': {
      type: 'ancestor',
      linked: function(target) {
        console.log('Footer 插入');
      }
    }
  }
});

🦋3.4 通过 Behavior 进行目标节点管理

在 customList 组件中,你可以通过 target 选项,利用 Behavior 来管理所有拥有该 Behavior 的节点。

// component/component2/customList.js
var custom = require('./custom');
Component({
  relations: {
    'custom': {
      type: 'descendant',
      target: custom  // 目标为 custom Behavior
    }
  }
});

这样,所有拥有 custom Behavior 的组件(如 customHeader 和 customFooter)都会被自动关联到 customList 组件。

🦋3.5 总结

  1. 关系定义:使用 relations 来定义组件之间的依赖关系。可以定义父子关系(child)、祖先后代关系(ancestor 和 descendant)。
  2. 回调函数:通过 linked、linkChanged 和 unlinked 回调函数,父组件可以感知子组件的插入、更新和移除。
  3. Behavior:通过 Behavior,可以将一类组件的共同行为集中管理,简化多组件间的关系定义。
  4. 目标节点管理:通过 target 选项,可以将 Behavior 作为目标节点,自动关联所有包含该 Behavior 的组件。

🔎4.Behaviors 的应用

🦋4.1 Behavior 的作用

Behavior 是一种提高代码复用性的编程方式。通过 Behavior,可以将多个组件中重复的功能提取成一个独立的模块,然后通过引入 Behavior 来实现功能的复用。这种方法可以让多个自定义组件共享相同的属性、数据、方法和生命周期回调,避免重复代码,提高开发效率。

🦋4.2 使用 Behavior 实现代码复用

假设你有两个组件,customHeader 和 customFooter,它们有一些共同的功能。你可以将这些功能提取到 Behavior 中,并让这两个组件都引入它,从而复用这些功能。

☀️4.2.1 定义 Behavior

首先,你需要在 custom.js 文件中定义一个 Behavior:

// component/component2/custom.js
module.exports = Behavior({
  properties: {
    "title": {
      type: String
    }
  },
  data: {
    behaviorData: "behaviorData"
  },
  methods: {
    log: function(value) {
      console.log("自定义的打印方法:" + value);
    }
  },
  created: function() {
    console.log("Behavior created");
  }
});

这里定义了:

  • properties: 定义了 Behavior 的外部属性,组件引入该 Behavior 后,就可以使用 title 属性。
  • data: 定义了 Behavior 内部的数据,behaviorData。
  • methods: 定义了 Behavior 中的方法,log 方法可以被组件调用。
  • lifecycle: 定义了生命周期回调函数(如 created),在 Behavior 创建时会调用。

☀️4.2.2 组件中引入 Behavior

然后在 customHeader 和 customFooter 组件中引入这个 Behavior:

// component/component2/customHeader.js
var custom = require('./custom');
Component({
  behaviors: [custom],
  relations: {
    './customList': {
      type: 'ancestor',
      linked: function(target) {
        console.log('Header 插入');
      }
    }
  }
});
// component/component2/customFooter.js
var custom = require('./custom');
Component({
  behaviors: [custom],
  relations: {
    './customList': {
      type: 'ancestor',
      linked: function(target) {
        console.log('Footer 插入');
      }
    }
  }
});

☀️4.2.3 在 WXML 中使用 Behavior 属性

组件 customHeader 和 customFooter 都引入了 custom Behavior,因此它们都能够使用 Behavior 中定义的 title 属性和 log 方法。在组件的 WXML 文件中,我们可以直接使用 title 属性:

<!-- component/component2/customHeader.wxml -->
<text>{{title}}</text>
<!-- component/component2/customFooter.wxml -->
<text>{{title}}</text>

☀️4.2.4 使用组件时传递属性

在页面中使用这两个组件时,直接为它们传递 title 属性即可:

<!-- pages/customComponent/customComponent.wxml -->
<custom-list>
  <custom-header title="头部"></custom-header>
  <custom-footer title="尾部"></custom-footer>
</custom-list>

当页面渲染时,customHeader 和 customFooter 组件都会正确显示传入的 title 属性。

🦋4.3 Behavior 的属性与覆盖规则

☀️4.3.1 Behavior 对象的属性

Behavior 中可以定义的属性有以下几种:

属性 意义 对应组件中的功能
properties 组件的外部属性,类似于组件的 properties 通过 Behavior 定义的外部属性,组件引入后可使用
data 组件的内部数据,类似于组件的 data 通过 Behavior 定义的内部数据,组件引入后可使用
methods 组件的方法,类似于组件的 methods 通过 Behavior 定义的方法,组件引入后可调用
behaviors 引入其他 Behavior 对象的数组 允许一个 Behavior 引入其他 Behavior,实现多级复用
created Behavior 的生命周期方法(创建时调用) 在 Behavior 创建时调用
attached Behavior 的生命周期方法(挂载时调用) 在 Behavior 挂载时调用
moved Behavior 的生命周期方法(移动时调用) 在 Behavior 移动时调用
detached Behavior 的生命周期方法(卸载时调用) 在 Behavior 卸载时调用

☀️4.3.2 覆盖规则

当组件同时定义了自己的属性、数据、方法与生命周期回调,同时又引入了 Behavior,会有以下覆盖和组合规则:

  • properties:如果组件和 Behavior 中都定义了相同的属性,组件内部定义的属性优先级更高。如果组件中没有定义该属性,则会从 Behavior 中继承。
  • methods:如果组件和 Behavior 中都定义了相同的方法,组件内部的方法优先级更高。如果组件没有定义该方法,则会从 Behavior 中继承。
  • data:如果组件和 Behavior 中都有同名的数据,并且数据的类型为对象,则会合并数据;否则,组件中的数据优先级更高。
  • 生命周期方法:组件和 Behavior 中的生命周期方法不会互相覆盖,会依次执行。先执行父 Behavior 的生命周期方法,再执行子 Behavior 的方法,最后执行组件本身的生命周期方法。后引入的 Behavior 会优先于前一个 Behavior 执行。

🦋4.4 内置 Behavior

小程序框架还提供了一些内置的 Behavior,可以直接在自定义组件中使用,常见的内置 Behavior 有:

Behavior 名 意义
wx://form-field 为自定义组件增加表单控制能力,使 form 组件可以识别到自定义组件内部的所有表单控件
wx://form-field-group 为自定义组件增加表单控件组,使 form 组件可以识别到自定义组件内部的按钮
wx://form-field-button 为自定义组件增加表单按钮控件支持,使 form 组件可以识别到自定义组件内部的按钮
wx://component-export 使自定义组件支持 export 定义段,允许其他组件引用该组件的属性和方法

这些内置 Behavior 可以帮助你在自定义组件中实现常见的功能,比如表单字段控制、按钮控制等,避免重复造轮子。

🦋4.5 总结

  1. 提高代码复用性:Behavior 允许将组件中共享的功能提取到一个独立模块,通过引入 Behavior,可以在多个组件中复用相同的属性、数据、方法和生命周期回调。
  2. 组件与 Behavior 的关系:组件可以通过 behaviors 配置引入一个或多个 Behavior,并继承其中定义的功能。组件可以覆盖 Behavior 中的同名字段,或者与其合并。
  3. 内置 Behavior:小程序提供了内置 Behavior,如表单控制等,可以直接引入,提高开发效率。
  4. 适用场景:Behavior 特别适用于多个组件具有相同功能或行为的场景,避免重复定义,提高代码的可维护性和复用性。

🔎5.数据监听器

🦋5.1 数据监听器概述

小程序中的组件可以使用 数据监听器 来监控属性或数据字段的变化,当这些数据发生变化时,数据监听器会触发指定的回调函数,从而执行一些逻辑操作。

数据监听器通过 observers 选项定义,支持对多个数据字段进行监听。当任一字段发生变化时,监听函数都会被触发。

🦋5.2 基本使用

假设有一个组件 customHeader,它引入了 Behavior,并拥有 title 属性和 behaviorData 数据字段。你可以在 observers 选项中设置监听器,监听这些字段的变化。

示例:监听 title 和 behaviorData 的变化

// component/component2/customHeader.js
var custom = require('./custom');

Component({
  behaviors: [custom],
  observers: {
    "title,behaviorData": function(title, behaviorData) {
      console.log("title 或 behaviorData 被设置", title, behaviorData);
    }
  }
});

在这个示例中,当 title 或 behaviorData 发生变化时,监听器函数会被调用,console.log 会输出变化后的 title 和 behaviorData。

🦋5.3 监听对象内部的属性

如果你想监听一个对象内部某个属性的变化,可以像下面这样使用 observers:

示例:监听对象属性 obj.name 的变化

// component/component2/customHeader.js
var custom = require('./custom');

Component({
  behaviors: [custom],
  data: {
    obj: {
      name: "name",
      id: "1"
    }
  },
  attached: function() {
    // 修改 obj 的数据
    this.setData({
      obj: {
        name: "huishao",
        id: "1"
      }
    });
  },
  observers: {
    "obj.name": function(name) {
      console.log("obj.name 被设置为", name);
    }
  }
});

在这个例子中,监听器会监控 obj.name 的变化,当 obj.name 更新时,回调函数会被触发,并打印出新的 name 值。

🦋5.4 监听对象数据中所有属性的变化

如果需要监听对象中所有属性的变化,可以使用通配符 ** 来实现。例如,如果你想监听整个 obj 对象的变化,可以这样做:

示例:监听整个对象 obj 的变化

// component/component2/customHeader.js
var custom = require('./custom');

Component({
  behaviors: [custom],
  data: {
    obj: {
      name: "name",
      id: "1"
    }
  },
  observers: {
    "obj.**": function(obj) {
      console.log("obj 中的任意字段发生变化", obj);
    }
  }
});

使用 "obj.**" 作为监听字段,意味着你可以监听 obj 对象内任意属性的变化。当 obj 内的任何字段发生变化时,监听器都会被触发,打印出变化后的 obj 数据。

🦋5.5 注意事项

  1. 数据监听器只能监听通过 setData 设置的数据字段:
    数据监听器仅能监听通过 setData 方法更新的数据字段。如果你直接修改数据对象的值,而没有使用 setData,那么数据监听器是不会被触发的。

  2. 避免无限循环:
    在数据监听器中使用 setData 设置相同的字段可能会导致无限循环。例如,如果你在监听器中修改了 title 或 behaviorData 的值,且修改的数据依然与之前相同,那么 setData 会触发监听器再次执行,形成无限循环。因此,应该避免在监听器内直接修改监听字段,除非你有办法判断是否需要更新。

示例:避免无限循环

observers: {
  "title": function(title) {
    console.log("title 被设置为", title);
    // 如果 title 没有变化,避免再次调用 setData
    if (title !== this.data.title) {
      this.setData({
        title: title
      });
    }
  }
}

在上面的代码中,首先通过条件判断 title 是否发生变化,只有在 title 确实发生了变化时,才调用 setData。这样可以避免因 setData 调用导致的无限循环。

🦋5.6 总结

  1. 数据监听器 用于监听组件的属性和数据字段的变化,当数据变化时,可以触发回调函数执行一些操作。
  2. 监听多个数据字段:可以通过 observers 对多个字段进行合并监听,多个字段的变化都会触发同一个监听函数。
  3. 对象属性监听:可以使用点语法监听对象中的单个属性,或使用 ** 通配符监听对象的所有属性变化。
  4. 注意监听的条件:数据监听器只能监听通过 setData 更新的数据字段,且避免在监听器中修改同一数据字段,否则可能会引起无限循环。

这样,你就可以灵活地监控和处理组件内的数据变化,在小程序开发中实现动态更新和响应。

🔎6.关于纯数据字段

🦋6.1 纯数据字段概述

在小程序中,组件中的数据通常会参与页面渲染,但有时候并不需要所有的数据都渲染到页面上。为了优化性能,你可以将某些 不需要渲染到页面的字段 定义为纯数据字段。这些字段依然会存在于 data 中,但不会参与页面的渲染。

纯数据字段的作用

  • 提高性能:通过将不需要渲染的字段定义为纯数据字段,可以减少页面的重新渲染,提高性能。
  • 数据监听:虽然纯数据字段不会直接影响界面的渲染,但它们的变化仍然会被数据监听器监听到。通过监听纯数据字段的变化,你可以在不直接渲染它们的情况下,更新需要渲染的其他数据。

🦋6.2 配置纯数据字段

要将某些数据字段设置为纯数据字段,你需要在组件的 options 配置中设置 pureDataPattern 字段,并通过正则表达式规则来指定哪些字段是纯数据字段。所有符合该规则的字段都会被视为纯数据字段。

步骤:配置 pureDataPattern

  1. 配置 options:你可以在组件的 options 选项中设置 pureDataPattern,它是一个正则表达式,用于定义哪些字段是纯数据字段。
  2. 定义纯数据字段:在 data 中定义的字段,只要符合正则表达式规则,就会被自动解析为纯数据字段。

示例:设置纯数据字段

// component/component2/customHeader.js
var custom = require('./custom');

Component({
  behaviors: [custom],
  options: {
    // 通过正则表达式,定义所有以下划线开头的字段作为纯数据字段
    pureDataPattern: /^_/
  },
  data: {
    data1: "渲染字段",   // 该字段会参与页面渲染
    _data2: "纯数据字段"  // 该字段是纯数据字段,不会参与页面渲染
  }
});

在这个例子中,pureDataPattern 被设置为 ^_,这意味着所有以 _ 开头的字段会被视为纯数据字段。因此,_data2 被当作纯数据字段,而 data1 仍然是正常的数据字段,会参与页面渲染。

🦋6.3 纯数据字段的注意事项

  1. 不能参与页面渲染:

    • 纯数据字段被定义后,不能再参与页面的渲染。如果在 WXML 中引用了纯数据字段,它将不会显示任何内容。
    • 例如,如果在 WXML 中使用了 _data2,不会有任何渲染效果,因为它是纯数据字段,不参与页面的渲染。
  2. 依然能被数据监听器监听:

    • 纯数据字段虽然不会直接影响页面渲染,但它们的变化仍然可以被数据监听器监听到。
    • 你可以根据纯数据字段的变化,来更新其他渲染字段或执行其他逻辑操作,从而间接影响页面的渲染。

示例:监听纯数据字段的变化

observers: {
  "_data2": function(newData) {
    console.log("纯数据字段 _data2 发生变化", newData);
    // 你可以在此根据 _data2 的变化更新页面需要渲染的字段
    this.setData({
      data1: "新渲染字段"
    });
  }
}

在上面的代码中,当 _data2 变化时,监听器会被触发。你可以在监听器中执行一些操作,比如更新其他数据字段,进而影响页面的渲染。

🦋6.4 如何优化页面性能

通过合理地使用纯数据字段,可以有效减少不必要的数据变化对页面渲染的影响,特别是在数据量较大的情况下。这能显著提升页面的性能,尤其是在频繁更新数据的场景中,避免不必要的渲染和性能消耗。

🦋6.5 总结

  1. 纯数据字段:是指那些存储在组件 data 中,但不直接参与页面渲染的数据字段。它们的变化可以被监听,但不会影响页面渲染。
  2. 配置 pureDataPattern:通过在 options 配置中设置 pureDataPattern 字段,使用正则表达式来指定哪些字段是纯数据字段。
  3. 性能优化:通过将不需要渲染的字段定义为纯数据字段,可以减少页面更新次数,从而提升渲染性能。
  4. 数据监听:纯数据字段依然可以通过数据监听器进行监听,根据其变化来更新其他渲染字段。

示例总结

// component/component2/customHeader.js
var custom = require('./custom');

Component({
  behaviors: [custom],
  options: {
    pureDataPattern: /^_/  // 所有以 "_" 开头的字段都为纯数据字段
  },
  data: {
    data1: "渲染字段",   // 正常数据字段,会参与页面渲染
    _data2: "纯数据字段"  // 纯数据字段,不参与页面渲染
  },
  observers: {
    "_data2": function(newData) {
      console.log("纯数据字段 _data2 发生变化", newData);
      // 可以通过监听纯数据字段的变化来更新渲染字段
      this.setData({
        data1: "更新后的渲染字段"
      });
    }
  }
});

通过这种方式,你可以优化小程序的性能,尤其是在处理复杂数据时,可以减少不必要的渲染操作,提高页面响应速度。

🔎7.关于抽象节点

🦋7.1 抽象节点概述

抽象节点并不代表一个具体的组件类型,它更多的是一种占位符,允许自定义组件的调用者决定如何渲染某些节点内容。在自定义组件中,有时候某些节点不由组件本身决定,而是交给调用者来定义,这时就可以使用抽象节点。

🦋7.2 配置抽象节点

要使用抽象节点,首先需要在组件的 JSON 配置文件 中进行配置。具体来说,在组件的 customList.json 文件中,通过配置 componentGenerics 来定义抽象节点。

示例:配置抽象节点

假设我们有一个自定义组件 customList,其中我们希望标题部分交给调用者定义。我们将这个标题部分定义为一个抽象节点 outtitle。

// customList.json
{
  "component": true,
  "componentGenerics": {
    "outtitle": true
  }
}

在这个配置中,componentGenerics 用于定义组件中的抽象节点。这里我们配置了一个名为 outtitle 的抽象节点,调用者可以决定这个节点的具体渲染内容。

🦋7.3 在 WXML 中使用抽象节点

一旦抽象节点在配置文件中定义,就可以在组件的模板文件中(例如 customList.wxml)使用它。

在 customList.wxml 中使用抽象节点

<!-- customList.wxml -->
<view>
  <outtitle title="标题"></outtitle>
  <slot></slot>
</view>

在上面的模板中,<outtitle> 是我们定义的抽象节点,调用者可以提供自己的组件来替代这个节点,从而自定义标题部分的内容。<slot> 是插槽,用于插入调用者传递的内容。

🦋7.4 调用组件时传递具体组件

调用 customList 组件时,调用者可以指定一个具体的组件来替代抽象节点。假设我们有一个 component1 组件,支持 title 属性,我们可以将 component1 作为 outtitle 节点的内容。

示例:在调用方的 WXML 文件中传递组件

<!-- pages/customComponent/customComponent.wxml -->
<custom-list generic:outtitle="component1"></custom-list>

在这个例子中,我们通过 generic:outtitle="component1" 将 component1 组件绑定到 customList 组件中的 outtitle 抽象节点。这样,在渲染时,outtitle 会被替换成 component1 组件。

🦋7.5 设置默认组件

由于我们无法保证调用方每次都按照预期提供组件,组件开发者可以为抽象节点设置一个默认的组件。这样,如果调用者没有提供自定义的组件,组件会使用默认的组件。

示例:为抽象节点设置默认组件

// customList.json
{
  "component": true,
  "usingComponents": {},
  "componentGenerics": {
    "outtitle": {
      "default": "../component1/component1"
    }
  }
}

在这个配置中,outtitle 的默认组件设置为 ../component1/component1。如果调用者没有传递具体的组件,customList 会默认使用 component1 组件来渲染 outtitle。

🦋7.6 完整示例

  1. 自定义组件 customList 的配置:

    // customList.json
    {
      "component": true,
      "usingComponents": {},
      "componentGenerics": {
        "outtitle": {
          "default": "../component1/component1"
        }
      }
    }
    
  2. 自定义组件 customList 的模板:

    <!-- customList.wxml -->
    <view>
      <outtitle title="标题"></outtitle>
      <slot></slot>
    </view>
    
  3. 调用 customList 组件时,指定具体的 outtitle 组件:

    <!-- pages/customComponent/customComponent.wxml -->
    <custom-list generic:outtitle="component1"></custom-list>
    
  4. 如果调用者未指定 outtitle,则默认使用 component1 组件:

    <!-- 如果调用时没有指定 generic:outtitle,则会默认渲染 component1 -->
    <custom-list></custom-list>
    

🦋7.7 总结

  1. 抽象节点:允许自定义组件的调用者决定某些节点的渲染内容,而不是由组件本身决定。
  2. 配置抽象节点:在组件的 JSON 配置文件中通过 componentGenerics 配置抽象节点,并通过正则或指定节点名称来定义。
  3. 调用方传递组件:在调用自定义组件时,通过 generic 属性传递具体的组件,用来替换抽象节点。
  4. 默认组件:可以为抽象节点设置一个默认的组件,以确保即使调用者没有提供自定义组件,组件也能正确渲染。

抽象节点的使用能够提升自定义组件的灵活性和可复用性,让组件开发者不需要关心某些具体细节,而是交给调用者来定制。

🔎8.自定义组件的性能测试

🦋8.1 组件性能与页面刷新

  1. 性能瓶颈:

    • 在小程序中,页面的性能很大程度上取决于组件的刷新操作。每次调用 setData 时,小程序会触发一次页面更新,而这种更新操作会产生一定的性能开销。
    • 对于组件来说,频繁的页面刷新操作会增加性能消耗,因此,开发者在编写自定义组件时需要尽量避免不必要的刷新,尤其是多次变更时,要合并成一次刷新操作。
  2. 优化策略:

    • 合并更新:对于多个字段的变更,尽量将它们合并为一次 setData 调用,避免多次触发页面刷新。
    • 避免频繁刷新:只有在数据真正变化并且需要更新页面时,才调用 setData,避免无意义的刷新操作。

🦋8.2 性能监控:使用 setUpdatePerformanceListener 方法

为了更好地了解每次 setData 刷新操作的性能开销,小程序提供了 setUpdatePerformanceListener 方法,允许开发者设置 更新性能回调。通过这个回调,我们可以实时获取页面刷新时的性能数据,帮助我们分析和优化性能。

☀️8.2.1 设置性能回调

在组件的 attached 生命周期方法中,可以调用 setUpdatePerformanceListener 来监听页面更新性能。

// customHeader.js
Component({
  attached: function() {
    this.setUpdatePerformanceListener(
      { withDataPaths: true }, // 配置回调时是否传递引起页面刷新的数据字段
      (res) => {
        console.log(res); // 打印更新的性能数据
      }
    );
    
    // 设置组件数据,触发页面更新
    this.setData({
      obj: { name: "huishao", id: "1" }
    });
  }
});

☀️8.2.2 回调函数参数

setUpdatePerformanceListener 方法会触发一个回调,回调函数的参数包含了具体的性能数据。以下是回调函数参数中的常见属性及其意义:

属性名 类型 说明
updateProcessId 数值 当前更新过程的唯一标识,标记此次更新的不同阶段
parentUpdateProcessId 数值 如果是子更新,则返回父更新的 updateProcessId
isMergedUpdate 布尔值 是否是合并更新(即多次数据更新合并为一次刷新)
dataPaths 数组 引起此次更新的数据字段路径
pendingStartTimestamp 数值 更新进入等待队列时的时间戳
updateStartTimestamp 数值 更新开始运算的时间戳
updateEndTimestamp 数值 更新运算结束的时间戳

☀️8.2.3 性能指标解释

  • isMergedUpdate:当为 true 时,表示这次更新是合并更新,即多个字段的变更被合并成一次刷新操作,这样可以减少刷新频次,提升性能。
  • dataPaths:该属性记录了具体引起更新的数据字段路径。如果我们在一次更新中变更了多个字段,dataPaths 会列出所有字段的路径。
  • 时间戳(pendingStartTimestamp, updateStartTimestamp, updateEndTimestamp):这些时间戳可以帮助开发者计算更新的耗时,从更新请求发出到更新结束所消耗的时间。

🦋8.3 如何分析和优化

通过收集性能数据,开发者可以更好地分析每次刷新操作的开销,并根据数据进行优化。以下是一些具体的优化建议:

  1. 合并更新:根据 isMergedUpdate 属性,检查是否多个更新合并成了一次更新。如果没有合并,可以考虑手动合并 setData 调用,减少刷新次数。

  2. 优化 setData 调用:通过 dataPaths 了解哪些字段导致了更新。若是无关紧要的字段变化,避免将这些字段纳入更新中。

  3. 计算时间开销:通过比较 updateStartTimestamp 和 updateEndTimestamp 的差值,可以计算每次更新操作的耗时,进一步优化长时间更新的部分。

  4. 减少等待队列时间:如果 pendingStartTimestamp 的时间较长,可以优化数据更新的时机,减少页面更新进入等待队列的时间。

🦋8.4 总结

  1. 组件性能优化:在小程序中,组件的性能直接影响页面的刷新速度和响应时间,合理地使用 setData 进行数据更新,合并多个变更操作,避免频繁的页面刷新是优化的关键。

  2. 使用 setUpdatePerformanceListener:通过这个方法,可以获取到页面刷新过程中的详细性能数据,包括是否合并更新、更新字段、时间戳等,从而帮助开发者分析性能瓶颈。

  3. 性能分析和优化:

    • 关注 isMergedUpdate 属性,避免多次单独更新。
    • 利用 dataPaths 和时间戳信息,分析并减少不必要的更新开销。
  4. 优化实践:通过合理合并 setData 调用、减少不必要的刷新、控制更新时机等手段,可以显著提高页面性能,提升用户体验。

示例代码总结

Component({
  attached: function() {
    this.setUpdatePerformanceListener(
      { withDataPaths: true }, // 配置是否传递字段路径
      (res) => {
        console.log(res); // 输出性能数据
        // 根据性能数据进行分析和优化
      }
    );
    
    // 设置组件数据,触发页面更新
    this.setData({
      obj: { name: "huishao", id: "1" }
    });
  }
});

通过使用性能回调,开发者能够实时掌握页面更新的详细信息,并依据这些信息进行性能优化。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。