4、自定义组件开发(上):Component 构造器、properties 与 data、组件生命周期、组件通信

好,咱们今天聊聊自定义组件开发的上半部分。说实话,小程序开发里最让我觉得「有成就感」的,就是封装组件。你想想看,一个写好的组件,能在项目里到处复用,多爽。

但组件开发也有不少坑。我刚开始做的时候,就经常搞混 propertiesdata 的用法。嗯,今天咱们就把这些基础打牢。

4.1 Component 构造器:组件的「身份证」

每个自定义组件,都得用 Component 构造器来注册。说白了,它就是告诉小程序:「嘿,这是一个组件,你要按组件的方式来对待它」。

// components/my-component/my-component.js
Component({
  behaviors: [],
  properties: {},
  data: {},
  lifetimes: {},
  methods: {}
})

我个人习惯,写组件时一定会把 methods 放在最后。为什么?因为 methods 里的方法最多,放最后不容易看花眼。

小提示: Component 构造器里的配置项,顺序其实无所谓。但建议你养成固定顺序的习惯,团队协作时别人看着也舒服。

4.2 properties 与 data:组件的「输入」和「状态」

这两个概念,我见过太多人搞混了。咱们用一句话说清楚:

  • properties:外部传进来的数据,是组件的「输入」
  • data:组件内部自己用的数据,是组件的「状态」

举个例子你就明白了:

// 组件定义
Component({
  properties: {
    title: {
      type: String,
      value: '默认标题'
    },
    count: {
      type: Number,
      value: 0
    }
  },
  data: {
    internalCount: 0,
    isVisible: true
  }
})

我在项目中遇到过一个问题:有人把 propertiesdata 用,直接在组件内部修改 properties 的值。结果呢?父组件的数据也跟着变了,整个页面乱套了。

注意: properties 是只读的!你可以在组件内部读取它,但千万别直接修改。想改?用 observers 监听变化,或者通过事件通知父组件去改。

4.3 组件生命周期:组件从生到死

组件的生命周期,说白了就是「组件从创建到销毁,中间经历了什么」。我刚开始学的时候,觉得生命周期很抽象。后来做多了才发现,它就是几个固定的时间点。

生命周期 触发时机 常见用途
created 组件实例刚被创建 初始化数据,但还不能操作界面
attached 组件被挂载到页面 可以获取节点信息,发起网络请求
ready 组件布局完成 可以获取节点宽高,做动画
moved 组件被移动 少用,但某些场景下有用
detached 组件被销毁 清理定时器、取消订阅

写生命周期的时候,我建议用 lifetimes 对象来定义:

Component({
  lifetimes: {
    created() {
      console.log('组件创建了');
      this.initData = { /* 初始化数据 */ };
    },
    attached() {
      console.log('组件挂载了');
      this.fetchData();
    },
    ready() {
      console.log('组件准备好了');
      this.getNodeInfo();
    },
    detached() {
      console.log('组件销毁了');
      clearInterval(this.timer);
    }
  }
})
重点: created 里不能调用 setData!因为此时组件还没挂载到页面。我见过有人在这里写 setData,结果数据没更新,排查了半天。

4.4 组件通信:组件之间的「对话」

组件不是孤岛,它们需要互相通信。小程序里,组件通信主要有三种方式:

4.4.1 父传子:properties

这个最简单,父组件通过属性传值:

<!-- 父组件使用子组件 -->
<my-component title="你好" count="{{pageCount}}" />

子组件用 properties 接收就行。嗯,这里要注意:如果传的是对象,记得用 observer 监听变化。

4.4.2 子传父:triggerEvent

子组件想告诉父组件「我这边发生了什么事」,就用 triggerEvent

// 子组件内部
methods: {
  onTap() {
    this.triggerEvent('myevent', { value: 123 });
  }
}
<!-- 父组件监听事件 -->
<my-component bind:myevent="onMyEvent" />

我曾经犯过一个错:事件名用了驼峰命名,比如 myEvent。结果在 WXML 里怎么都监听不到。后来才发现,小程序里事件名要用小写加连字符,比如 my-event

避坑指南: triggerEvent 的第二个参数是 detail 对象,父组件通过 event.detail 获取。这个细节很多人忽略,结果拿不到数据。

4.4.3 兄弟组件通信:通过父组件中转

两个兄弟组件之间怎么通信?我一般用父组件做「中间人」:

  1. 子组件A 触发事件给父组件
  2. 父组件更新数据
  3. 数据通过 properties 传给 子组件B

说白了就是「A告诉爸爸,爸爸告诉B」。虽然绕了点,但这是最稳妥的方式。

4.5 实战小技巧:组件开发的「肌肉记忆」

最后,分享几个我写组件时的习惯:

  • 命名规范:组件名用连字符,比如 my-component,别用驼峰
  • 数据隔离:组件内部的数据,尽量用 data 管理,别污染全局
  • 生命周期清理:在 detached 里一定要清理定时器和监听器,不然内存泄漏
  • 代码注释:properties 的每个字段都写上注释,别人用你组件时能看懂

好了,这一章的内容就到这。下一章咱们聊聊组件的高级用法,包括插槽、混入和组件间的复杂通信。到时候我会分享一个我踩过的「大坑」,保证让你印象深刻。