在微信小程序的项目架构中,自定义组件承担着越来越重要的角色。一个页面往往由多个组件拼装而成,而页面本身又通过URL携带参数(比如商品详情页的?id=123)。问题来了:组件内部如何感知这些页面参数?很多开发者只知道页面的onLoad(options)能拿到参数,却不知道组件里也有对应的手段,这就是pageLifetimes节点的用途。本文将系统讲解组件生命周期与页面生命周期的关系,并给出在组件中获取页面参数的完整方案。

一、组件生命周期与页面生命周期的区别
微信小程序的自定义组件有自己的一套生命周期,定义在lifetimes节点中,最常用的是created、attached、ready和detached。它们分别对应组件实例被创建、进入页面节点树、布局完成以及离开节点树这几个时机。需要注意的是,在created阶段还不能调用setData,因为此时组件刚刚实例化,还没有挂载到页面上,只能给this添加一些简单属性。
而页面生命周期是另一条线:onLoad、onShow、onReady、onHide、onUnload。这两条线是交叉运行的。以用户打开一个包含组件的页面为例,时间顺序大致是:页面onLoad触发,组件created和attached触发,接着页面onShow,组件ready,最后页面onReady。理解这个顺序非常关键,因为很多参数获取失败的 bug 都源于对时机的误判。
组件默认感知不到页面的显示与隐藏。比如用户把小程序切到后台再切回来,页面会触发onShow,但组件内部的lifetimes不会有任何反应。微信团队为了弥补这个空档,专门提供了pageLifetimes节点,让组件可以监听所在页面的生命周期,这正是我们获取页面参数的切入点。
二、pageLifetimes的基本用法
pageLifetimes写在组件的Component构造器中,与lifetimes平级。它支持三个回调:show(页面显示)、hide(页面隐藏)和resize(页面尺寸变化,回调参数为size对象)。下面是一个基础示例:
Component({
lifetimes: {
attached() {
console.log('组件 attached,已挂载到页面')
}
},
pageLifetimes: {
show() {
// 页面显示时触发,此时可以通过 this.getPageId() 或 getOpenerEventChannel 拿到信息
const page = this.getPageId ? null : null
console.log('所在页面显示了')
},
hide() {
console.log('所在页面隐藏了')
},
resize(size) {
console.log('页面尺寸变化', size)
}
}
})有一个重要细节:pageLifetimes.show在组件第一次挂载时也会触发一次,因为页面此时正处于显示状态。所以如果你在show里做初始化逻辑,要注意避免与attached中的逻辑重复执行。一个常见技巧是加一个标志位,保证初始化代码只跑一遍。
另外,resize回调在小程序分屏、iPad 多窗口等场景下特别有用,参数size包含windowWidth、windowHeight等字段,可以据此动态调整组件布局。
三、在pageLifetimes中获取页面参数的完整方案
pageLifetimes的回调本身不会把页面参数直接传给组件,它只是提供了时机。真正拿到参数有两种主流方式。
第一种是通过页面实例获取。在组件中调用this.getPageId()可以拿到页面标识,但更实用的做法是在合适的生命周期里通过页面栈拿到页面实例,然后读取页面在onLoad时保存的参数。示例代码如下:
// 页面代码 pages/detail/detail.js
Page({
data: { goodsId: '' },
onLoad(options) {
// 把URL参数存到data中,供组件读取
this.setData({ goodsId: options.id || '' })
// 也可以直接挂在页面实例上
this.pageParams = options
}
})
// 组件代码 components/goods-info/index.js
Component({
data: { goodsId: '' },
lifetimes: {
attached() {
this._loadParams()
}
},
pageLifetimes: {
show() {
// 页面再次显示时刷新参数,比如从分享链接进入
this._loadParams()
}
},
methods: {
_loadParams() {
const pages = getCurrentPages()
const current = pages[pages.length - 1]
if (!current) return
// 方式一:读取页面data
const goodsId = current.data.goodsId
// 方式二:读取页面实例上的自定义属性
const params = current.pageParams
this.setData({ goodsId: goodsId || (params && params.id) || '' })
}
}
})第二种方式是通过组件属性传递。页面在WXML中使用组件时,把参数通过properties传进去,再配合observers监听变化:
<!-- pages/detail/detail.wxml -->
<goods-info goods-id="{{goodsId}}" />
// 组件内通过 observers 监听属性变化
Component({
properties: {
goodsId: {
type: String,
value: '',
// 通常不需要observer,用observers更灵活
}
},
observers: {
goodsId(val) {
if (val) {
this.fetchDetail(val)
}
}
},
methods: {
fetchDetail(id) {
wx.request({
url: 'https://ipipp.com/api/goods/detail',
data: { id },
success: res => {
this.setData({ detail: res.data })
}
})
}
}
})两种方式的适用场景不同。属性传递是最符合组件化思想的方案,数据流向清晰,组件不依赖页面内部结构,便于复用和测试。而通过页面栈读取实例的方式更灵活,适合组件需要拿到完整页面参数或页面状态的场景,比如埋点组件、通用的登录弹窗组件,它们往往需要在多个页面间复用,不可能为每个页面都声明一遍属性。
四、常见坑与注意事项
第一个坑是时机问题。组件的attached可能在页面onLoad之前触发吗?答案是页面onLoad一定先于组件attached,所以在attached里读页面参数是安全的。但要注意,如果页面在onLoad里是异步 setData 参数(比如先请求再回填),组件attached时可能拿到的还是空值,此时必须依赖observers或pageLifetimes.show来补一次读取。
第二个坑是getCurrentPages的边界情况。当组件被用在自定义 tabBar、独立分包或某些特殊容器中时,页面栈的最后一项未必是组件所在的页面。稳妥的做法是通过this.getPageId()与各页面实例比对,或干脆约定页面把参数写到全局的getApp().globalData中。
第三个坑是重复请求。由于pageLifetimes.show在小程序从后台切回前台时也会触发,如果组件在show里无条件发起网络请求,用户每次切后台回来都会刷新一次,体验和流量都不友好。建议加节流或数据有效期判断,比如距离上次拉取不足30秒就跳过。
最后补充一点,如果你的基础库版本在2.2.3以下,pageLifetimes是不可用的,可以在发布前通过基础库版本判断做好降级处理。总体来说,pageLifetimes补齐了组件感知页面的能力,配合properties和observers,基本可以覆盖所有在组件中获取页面参数的场景,值得在项目中规范地用起来。
微信小程序组件生命周期pageLifetimes修改时间:2026-09-04 09:08:49