在微信小程序的自定义组件开发中,created、attached与ready是三个最基础也最容易混淆的生命周期函数。它们分别代表了组件从实例化到渲染完成的不同阶段,理解它们的触发时机对于编写稳定的组件逻辑至关重要。很多业务逻辑错误,比如获取不到节点尺寸、请求数据后视图未更新,往往就是因为把代码写在了错误的生命周期里。

三大生命周期函数的定义与底层触发机制
微信小程序在运行时会为每一个自定义组件创建一个独立的实例对象。当开发者在页面或另一个组件中引用该自定义组件时,框架首先通过构造函数生成实例,此时会同步调用created生命周期。在created阶段,组件自身的data已经初始化,属性properties也已接收来自父组件的传值,但组件对应的wxml节点还没有被真正创建并插入到页面节点树中。因此,在created里不能调用this.createSelectorQuery()去查询节点,因为节点根本不存在。
紧接着,框架会将组件节点挂载到页面渲染树上,这个过程完成后触发attached。从底层看,attached意味着组件已经拥有了自己的渲染上下文,可以访问this.data以及通过selectOwnerComponent找到父组件。不过需要注意的是,attached触发时子节点未必全部渲染结束,尤其是包含复杂列表或异步数据的结构。很多开发者误以为attached等同于网页里的DOMContentLoaded,其实它更接近于节点被插入文档,而非渲染完毕。
当组件自身及其子节点都完成首次渲染,用户即将看到完整界面时,框架最后调用ready。ready是三个钩子里最晚执行的,它保证布局计算已经结束,此时使用wx.createSelectorQuery().in(this)能够准确拿到节点的宽高、位置等信息。如果组件依赖第三方canvas或需要测量文本行数,就必须等待ready。从源码设计角度,小程序将创建、挂载、渲染分为三步,是为了让框架能在attached时就开始并行处理页面级任务,提升启动性能。
通过代码示例观察执行顺序与数据特征
为了直观验证三者的先后关系,我们可以在组件js文件中同时打印三个钩子,并观察控制台输出。以下代码展示了一个名为life-cycle的自定义组件,它在每个阶段输出当前可否查询节点:
Component({
lifetimes: {
created: function () {
console.log('created 触发');
console.log('此时节点是否存在:', this.is && false);
// 不能做节点查询
},
attached: function () {
console.log('attached 触发');
// 可以访问 properties
console.log('接收到的标题:', this.data.title);
},
ready: function () {
console.log('ready 触发');
const query = this.createSelectorQuery();
query.select('.inner').boundingClientRect();
query.exec(function (res) {
console.log('节点尺寸:', res[0]);
});
}
},
properties: {
title: String
}
});
将上述组件放入页面并刷新,控制台一定会依次出现 created、attached、ready 的日志,且ready内部的boundingClientRect回调能正确返回.inner元素的布局对象。如果把节点查询移到created中,得到的结果是null,因为渲染树尚未生成。这个实验说明执行顺序是严格串行的,不可被微任务或定时器打乱。
另外要注意,若组件被wx:if控制显隐,当条件从false变为true时,会重新走一遍attached和ready,但created只在实例第一次构造时调用。利用这一特性,我们可以在created中做一次性事件监听绑定,在attached中做与节点相关的重置,在ready中做依赖布局的初始化,从而避免重复开销。
常见误用场景与最佳实践建议
在实际项目中,最常见的错误是把网络请求放在created里然后立刻用返回数据操作节点。由于created阶段没有节点,即使数据到了也无法绑定视图,导致页面空白。正确做法是:在created中只做纯逻辑初始化,比如设置默认data字段;在attached中发起请求,并在setData回调或ready中处理依赖节点的渲染。这样分层既符合小程序调度机制,也方便后期维护。
另一个典型误区是认为attached和ready都会频繁触发。实际上,除非组件被销毁重建,否则ready只会执行一次。如果组件内部有observers监听属性变化并需要重新测量节点,应当把测量逻辑封装成方法,在属性变更后手动于下一个渲染周期调用,而不是依赖生命周期重复进入。下表总结了三者差异:
| 生命周期 | 节点可用性 | 适用操作 |
|---|---|---|
| created | 不可用 | 数据初始化、事件绑定 |
| attached | 已挂载但未渲染完 | 读取属性、发起请求 |
| ready | 完全可用 | 节点测量、动画启动 |
综合来看,掌握created、attached与ready的执行顺序,核心在于理解小程序组件从构造到渲染的流水线。把不对等的操作放进对应的钩子,不仅能规避诡异的null报错,还能让组件在复杂页面中保持可预测的行为。建议在团队代码规范中明确标注每个生命周期的职责边界,从架构层面减少时序类bug。