2、创建第一个组件:组件文件结构、Component构造器、组件的生命周期

好,咱们正式开始动手写组件了。

这一节我会带你从零创建一个微信小程序组件。说白了,就是搞清楚三件事:文件怎么放、代码怎么写、生命周期怎么用。这三件事搞明白了,后面所有组件开发都是换汤不换药。

2.1 组件文件结构:别小看这四件套

先说说文件结构。我个人习惯把组件都放在 components/ 目录下,每个组件一个独立文件夹。比如我要写一个「用户卡片」组件,结构是这样的:

components/
└── user-card/
    ├── user-card.json      // 组件配置文件
    ├── user-card.wxml      // 组件模板
    ├── user-card.wxss      // 组件样式
    └── user-card.js        // 组件逻辑

嗯,这里要注意:四个文件缺一不可。我在项目中遇到过有人只写了三个文件,结果编译报错找了半天——其实就是少了 .json 文件。

关键点:组件的 .json 文件里必须声明 "component": true,否则小程序不认它是组件。

来看 user-card.json 怎么写:

{
  "component": true,
  "usingComponents": {}
}

就这么简单。第一行告诉小程序「我是组件」,第二行是组件里如果还要嵌套其他组件,就在这里注册。

2.2 Component 构造器:组件的灵魂

组件的逻辑写在 .js 文件里。和页面用 Page() 不同,组件用的是 Component() 构造器。

为什么不用 Page()?你想想看,页面是独立的一整屏,组件是要被复用的零件。两者的生命周期、数据传递方式都不一样。我刚开始学的时候也犯过混,直接用 Page() 写组件,结果发现 onLoad 根本不触发——因为组件根本没有 onLoad

来看一个完整的组件代码:

// components/user-card/user-card.js
Component({
  // 组件的属性列表(外部传入的数据)
  properties: {
    username: {
      type: String,
      value: '默认用户'
    },
    avatar: {
      type: String,
      value: '/images/default-avatar.png'
    },
    level: {
      type: Number,
      value: 1
    }
  },

  // 组件的内部数据
  data: {
    showBadge: false
  },

  // 组件的方法
  methods: {
    onTapCard() {
      // 触发自定义事件,通知父组件
      this.triggerEvent('cardtap', {
        username: this.properties.username
      })
    }
  },

  // 组件的生命周期
  lifetimes: {
    created() {
      console.log('组件被创建了')
    },
    attached() {
      console.log('组件被挂载到页面了')
    },
    ready() {
      console.log('组件渲染完成了')
    },
    detached() {
      console.log('组件被移除了')
    }
  }
})

个人经验:我习惯在 attached 里做数据初始化,而不是 created。因为 created 时组件还没挂载到页面,拿不到一些上下文信息。曾经有个项目,我在 created 里调用了 wx.getSystemInfoSync(),结果某些机型上报错——后来改到 attached 就没事了。

2.3 组件的生命周期:什么时候该干什么事

组件的生命周期比页面简单,但有几个关键节点必须掌握。我整理了一张表:

生命周期 触发时机 常用场景
created 组件实例刚被创建 初始化一些只读数据,不要操作 setData
attached 组件被挂载到页面节点树 发起网络请求、订阅事件、初始化数据
ready 组件布局渲染完成 获取节点信息(如 wx.createSelectorQuery
detached 组件从页面被移除 清理定时器、取消事件订阅

这里有个坑,我曾经踩过:created 里调用 setData。为什么不行?因为 created 时组件还没和页面建立联系,setData 不会生效,而且会报 warning。如果你需要在创建时设置数据,要么在 properties 里给默认值,要么等到 attached 再设。

警告:不要在 created 里使用 this.setData()。我曾经在一个项目中这样写了,结果数据一直不更新,排查了半小时才发现是生命周期用错了。

2.4 组件生命周期与页面生命周期的关系

组件是嵌在页面里的,所以两者的生命周期有先后顺序。举个例子:

  1. 页面 onLoad 触发
  2. 页面开始渲染,遇到组件标签
  3. 组件 createdattachedready
  4. 页面 onReady 触发

也就是说,组件的 ready 一定在页面的 onReady 之前。这个顺序在实战中很有用。比如你要在页面加载后获取组件的数据,可以在页面的 onReady 里通过 this.selectComponent() 去拿,这时候组件肯定已经准备好了。

2.5 完整示例:跑起来看看

最后,咱们把上面讲的串起来,写一个能用的组件。假设页面 pages/index/index 要使用 user-card 组件:

第一步:在页面的 .json 里注册组件

// pages/index/index.json
{
  "usingComponents": {
    "user-card": "/components/user-card/user-card"
  }
}

第二步:在页面的 .wxml 里使用组件

<!-- pages/index/index.wxml -->
<user-card 
  username="张三" 
  level="5"
  bind:cardtap="onCardTap"
/>

第三步:在页面的 .js 里处理组件事件

// pages/index/index.js
Page({
  onCardTap(e) {
    console.log('点击了用户卡片:', e.detail.username)
  }
})

总结一下:创建组件就是三步走——建好四件套文件、用 Component() 写逻辑、在页面里注册并使用。生命周期记住 attached 做初始化、detached 做清理,基本不会出大问题。

下一节咱们会深入组件的 propertiesdata,讲讲数据怎么传、怎么改。到时候我会分享一个我在电商项目里踩过的数据传递大坑——保证让你印象深刻。