做微信小程序开发时,不少刚入门的朋友都会犯懵:Page和Component到底有啥区别?啥时候该用Page,啥时候得用Component?其实这俩就像开发里的「大管家」和「小助手」,分工不同但配合起来能让项目又稳又灵活,今天咱就把Page和Component掰开揉碎了唠,从概念到实战帮你彻底搞明白~
你可以把Page理解成「页面级的容器」,它负责承载整个页面的结构、逻辑还有和用户的交互,比如你做个电商小程序的「商品详情页」,整个页面的布局(顶部轮播图、中间价格区、底部按钮栏)、进入页面时请求商品数据、用户点击按钮跳转到支付页这些逻辑,都得靠Page来管。
那Component是啥?它更像「可复用的功能模块」,还是拿商品详情页举例,页面里的「商品图片轮播组件」「价格展示组件」「规格选择弹窗」,这些模块在其他页面也可能用到(比如首页推荐商品也需要图片轮播),把它们拆成Component,下次用的时候直接拖过去,不用重复写代码,简单说,Page管整个页面的「大局」,Component负责局部功能的「复用和封装」。
Page和Component核心区别在哪?
功能定位:一个管全局,一个管局部
Page得负责页面级的「大决策」,比如页面的导航栏长啥样(navigationBar配置)、要不要加入tabBar、页面跳转时的生命周期(比如进入页面加载数据、离开页面保存状态),它就像整个页面的「指挥官」,掌控着页面从创建到销毁的全过程。
Component只聚焦「自己那一块」的逻辑,比如做个「点赞按钮组件」,它只关心用户点没点、当前点赞状态是啥,至于点完赞后页面要怎么更新列表、怎么统计数据,这些交给父页面(Page或者其他Component)来处理,而且组件能被多个Page或者其他组件引用,复用性拉满。
生命周期:启动到销毁的节奏不一样
Page的生命周期是「页面级」的,常用的有这几个阶段:
- onLoad:页面刚加载时触发,而且只触发一次(比如从首页跳转到详情页,详情页的onLoad只在第一次进入时执行);
- onShow:页面显示时触发(每次切回这个页面都会执行,比如从详情页返回首页,首页的onShow会重新触发);
- onReady:页面初次渲染完成,这时候可以操作页面元素了;
- onHide:页面被隐藏时触发(比如跳转到其他页面,当前页的onHide执行);
- onUnload:页面被卸载时触发(比如从详情页跳转到支付页,详情页的onUnload会执行,释放资源)。
Component的生命周期是「组件级」的,重点关注组件自身的创建、挂载、销毁:
- created:组件实例刚被创建,这时候还没把组件渲染到页面上,只能初始化一些内部数据;
- attached:组件被挂载到页面的节点树里了,这时候可以操作DOM、请求组件自己需要的局部数据;
- ready:组件布局完成,类似Page的onReady,这时候能拿到组件的宽高这些布局信息;
- detached:组件从页面节点树里被移除,比如页面切换时把组件销毁,这时候可以做一些资源释放(比如清除定时器)。
举个栗子:用户打开「商品列表页」(Page),列表里每个「商品卡片组件」(Component)会经历created→attached→ready;当用户跳转到「商品详情页」,列表页触发onHide,每个商品卡片组件触发detached。
配置项:json文件里的差异
Page的json文件主要配置「页面全局样式和行为」,
{ "navigationBarTitleText": "商品列表", // 导航栏标题 "navigationBarBackgroundColor": "#fff", // 导航栏背景色 "usingComponents": {} // 页面里要用到的组件,在这里声明 }
Component的json文件核心是「声明自己是组件」,还要处理组件内部的依赖:
{ "component": true, // 必须写true,告诉小程序这是个组件 "usingComponents": {}, // 组件内部要引用其他组件,比如弹窗里用图标组件 "externalClasses": ["custom-class"] // 允许父页面给组件传样式类,实现样式自定义 }
简单说,Page的json管页面「长啥样、去哪」,Component的json管自己「是谁、用啥」。
数据管理:传值和内部状态的区别
Page里的数据直接存在data里,用this.setData({})
更新,比如页面要显示用户信息:
Page({ data: { userInfo: {} }, onLoad() { // 请求用户信息,更新data this.setData({ userInfo: res.data }) } })
Component的数据分两部分:properties(外部传进来的值)和data(内部状态),properties是父页面(或父组件)传给它的,类似「参数」;data是组件自己内部用的状态,比如做个「评分组件」,父页面传初始评分,组件内部记录用户操作后的评分:
Component({ properties: { initScore: { type: Number, value: 0 } // 父页面传的初始评分 }, data: { currentScore: 0 // 组件内部当前评分,用户操作后变化 }, attached() { // 组件挂载时,把initScore赋值给currentScore this.setData({ currentScore: this.properties.initScore }) } })
这里要注意:properties里的值不能在组件内部直接修改(要改得让父页面重新传),而data里的值可以用this.setData
随便改~
啥场景用Page,啥场景用Component?
必须用Page的情况
- 页面路由的载体:小程序的每个「页面入口」都得是Page,比如tabBar上的「首页」「购物车」「我的」,或者通过wx.navigateTo
跳转的「商品详情页」「订单页」,这些页面需要管理自己的生命周期(比如进入时加载数据、离开时保存草稿)。
- 全局配置的容器:如果页面需要设置导航栏标题、底部tabBar、下拉刷新、上拉加载这些全局行为,只能用Page,因为Component管不了这些配置。
优先用Component的情况
- 重复出现的模块:如果某个UI或逻辑在多个页面出现(商品卡片」在首页、分类页、搜索页都有),做成Component能避免重复写代码,下次要改样式或逻辑,只需要改组件文件,所有用了这个组件的地方都会同步更新。
- 复杂交互的拆分:页面里有一堆交互逻辑(比如弹窗、滑动选择器、联动选择器),把这些拆成Component,页面代码会更简洁,地址选择组件」,用户选择省市区的逻辑全封装在组件里,页面只需要接收最终选择的地址就行。
- 父子互动的模块:组件需要和父页面(或父组件)传递数据,数量选择器组件」,父页面传初始数量,组件里用户点「+」「-」后,把最新数量传给父页面更新购物车,这种双向互动用Component的properties和triggerEvent特别方便。
生命周期咋对应?实操里咋用?
咱结合「用户打开商品列表页→点击商品进入详情页→返回列表页」的流程,看Page和Component的生命周期怎么配合:
打开商品列表页(Page):
- Page的onLoad触发:请求列表数据;
- 列表页里的每个「商品卡片组件」(Component)触发created→attached→ready:组件初始化、挂载到页面、布局完成。点击商品进入详情页(Page跳转):
- 商品列表页触发onHide:页面被隐藏,暂停一些非必要的定时器;
- 每个商品卡片组件触发detached:组件从页面节点树移除,释放资源(比如组件里的定时器要清掉);
- 详情页(Page)触发onLoad→onShow→onReady:加载详情数据、显示页面、渲染完成。返回商品列表页:
- 详情页触发onUnload:页面卸载,销毁资源;
- 商品列表页触发onShow:页面重新显示,可能需要刷新列表数据(比如商品状态变化);
- 商品卡片组件重新触发created→attached→ready:因为页面重新显示,组件重新挂载。
实操建议:如果组件里有定时器、订阅事件这些「占资源」的操作,要在detached生命周期里清除(比如clearInterval),避免页面切换后组件还在偷偷跑逻辑,导致内存泄漏或奇怪的bug~
数据传递和通信咋玩得溜?
Page内部:自己玩自己的
Page里的数据更新很简单,用this.setData({})
就行,比如页面有个计数器,点击按钮加1:
Page({ data: { count: 0 }, handleClick() { this.setData({ count: this.data.count + 1 }) } })
页面结构里绑事件:<button bindtap="handleClick">点我加1</button>
Component和Page之间:父子传值
父Page给子Component传值:用properties,比如页面里有个「头像组件」,需要传用户头像地址和昵称:
页面的wxml:<avatar-component img-url="{{userAvatar}}" nick-name="{{userNick}}"></avatar-component>
组件的js:
Component({ properties: { imgUrl: { type: String, value: '' }, nickName: { type: String, value: '' } } })
子Component给父Page传值:用triggerEvent,点赞组件」点完赞,把点赞状态传给页面:
组件的js:
Component({ methods: { handleLike() { const isLiked = !this.data.isLiked this.setData({ isLiked }) // 触发事件,把isLiked传给父页面 this.triggerEvent('likeChange', { isLiked }) } } })
页面的wxml:<like-component bind:likeChange="handleLike"></like-component>
页面的js:
Page({ handleLike(e) { const { isLiked } = e.detail // 处理点赞后的逻辑,比如发请求、更新列表 } })
这样父和子各司其职:父传初始值,子处理交互后把结果传回父~
Component之间:同页兄弟咋沟通?
如果页面里有两个组件A和B,需要互相传值,一般有两种思路:
- 通过父Page当中间人:A把值传给Page,Page再传给B,比如A是「省选择组件」,B是「市选择组件」,A选完省,把省ID传给Page,Page再把省ID传给B,B根据省ID加载市数据。
- 用relations关联组件:如果是有层级关系的组件(比如父组件套子组件),可以用Component的relations配置,让子组件能找到父组件,直接通信,比如做个「折叠面板组件」,父组件是面板容器,子组件是每个面板项,子组件点击时通知父组件展开/收起。
不过组件间通信别搞太复杂,尽量让Page当协调者,不然代码容易乱~
开发实战:Page和Component配合有多爽?
假设做个购物车页面,结构是「顶部导航栏 + 商品列表(每个商品是组件) + 底部结算栏」。
Page(购物车页面)负责啥?
- 全局配置:导航栏标题设为「购物车」,tabBar高亮购物车 tab;
- 生命周期:onLoad时请求购物车数据,onShow时刷新购物车(比如用户刚加了商品);
- 数据管理:保存整个购物车的商品列表、全选状态、总价格;
- 事件处理:全选按钮的点击、结算按钮的跳转。
Page的js大概长这样:
Page({ data: { cartList: [], // 购物车商品列表 isAllChecked: false, // 全选状态 totalPrice: 0 // 总价格 }, onLoad() { // 请求购物车数据 this.getCartData() }, onShow() { // 刷新购物车(比如从商品页回来,购物车有变化) this.getCartData() }, getCartData() { // 发请求,把数据塞到cartList里 this.setData({ cartList: res.data }) this.calcTotal() // 计算总价格和全选状态 }, calcTotal() { // 遍历cartList,计算总价格和是否全选 }, handleAllCheck() { // 切换全选状态,更新每个商品的选中状态 }, handleSettle() { // 跳转到结算页 wx.navigateTo({ url: '/pages/settle/settle' }) } })
Component(购物车商品项)负责啥?
每个商品项需要显示「商品图、名称、价格、数量选择、单选框」,这些逻辑封装成组件
- 接收父Page传的商品数据(item)、是否全选(isAllChecked);
- 处理单选框点击、数量加减的逻辑;
- 把单选状态、数量变化传给父Page。
Component的js:
Component({ properties: { item: { type: Object, value: {} }, // 商品数据 isAllChecked: { type: Boolean, value: false } // 全选状态 }, data: { currentNum: 0, // 当前商品数量 isChecked: false // 当前商品单选状态 }, attached() { // 组件挂载时,初始化当前数量和单选状态 this.setData({ currentNum: this.properties.item.num, isChecked: this.properties.isAllChecked }) }, methods: { handleCheck() { // 切换单选状态 const isChecked = !this.data.isChecked this.setData({ isChecked }) // 把单选状态传给父Page this.triggerEvent('checkChange', { goodsId: this.properties.item.id, isChecked }) }, handleNumChange(e) { // 数量加减,e.detail是+1或-1 const newNum = this.data.currentNum + e.detail this.setData({ currentNum: newNum }) // 把数量变化传给父Page this.triggerEvent('numChange', { goodsId: this.properties.item.id, newNum }) } } })
页面的wxml里用组件:
<view class="cart-header">购物车</view> <block wx:for="{{cartList}}" wx:key="id"
网友评论文明上网理性发言 已有0人参与
发表评论: