3、组件数据与属性:data定义、properties声明、observer监听器、数据流向
好,咱们今天聊点实在的。
组件的数据和属性,说白了就是组件的「内存」和「接口」。你想想看,一个组件要是没有自己的数据,那它就是个空壳子。要是没有属性,那它跟外界就断了联系。这两样东西,是小程序组件的根基。
3.1 data定义:组件的私有状态
每个组件都有自己的 data,就像每个人都有自己的小秘密。这个 data 是组件内部用的,外部拿不到,也改不了。
// components/my-card.js
Component({
data: {
title: '默认标题',
count: 0,
isVisible: true
}
})
我在项目中遇到过一个问题:有同事把 data 定义成了 string 类型,结果渲染的时候直接报错。记住,data 必须是个 Object,里面放键值对。
uiState 放界面状态,businessData 放业务数据。这样维护起来清爽很多。
3.2 properties声明:组件的对外接口
properties 是组件跟外界沟通的桥梁。父组件传什么,组件就收什么。有点像函数的参数。
// components/my-card.js
Component({
properties: {
// 简化写法
name: String,
// 完整写法
age: {
type: Number,
value: 18,
observer: function(newVal, oldVal) {
console.log('年龄变了', oldVal, '->', newVal)
}
},
// 复杂类型
userInfo: {
type: Object,
value: {}
}
}
})
这里有个坑,我踩过好几次。你看 value 这个字段,它叫「默认值」。但如果你传了 undefined 进来,它不会用默认值!
null 或 undefined,默认值就废了。我曾经因为这个 bug 排查了一下午。
3.3 observer监听器:数据变化的哨兵
observer 是个好东西。它能在数据变化时自动触发,就像装了个监控摄像头。
Component({
properties: {
score: {
type: Number,
observer: function(newVal, oldVal) {
// 新值、旧值都能拿到
if (newVal < 60) {
this.setData({ grade: '不及格' })
}
}
}
},
observers: {
// 监听多个字段
'score, grade': function(score, grade) {
console.log(`分数:${score},等级:${grade}`)
},
// 支持通配符
'user.**': function(user) {
console.log('user 对象变了', user)
}
}
})
嗯,这里要注意。微信小程序有两个监听机制:observer 和 observers。前者是 properties 里的回调,后者是组件级别的监听器。功能差不多,但 observers 更灵活,能监听多个字段。
observer:写在 properties 里,只监听单个属性observers:写在组件根级别,支持多字段、通配符
3.4 数据流向:单向数据流
小程序组件的数据流是单向的。什么意思?
父组件 → 通过 properties 传给子组件。子组件不能直接改 properties,只能通过 triggerEvent 通知父组件去改。
// 子组件中
methods: {
onTap() {
// 不能直接 this.properties.count++
// 应该这样:
this.triggerEvent('change', { delta: 1 })
}
}
// 父组件中
<my-counter bind:change="onCounterChange" />
Page({
onCounterChange(e) {
this.setData({
count: this.data.count + e.detail.delta
})
}
})
这个设计其实很聪明。你想想看,如果子组件能随便改父组件的数据,那调试起来得多痛苦?数据流乱了,bug 就来了。
3.5 实战:做一个计数器组件
光说不练假把式。咱们写个完整的计数器组件,把上面这些知识点串起来。
// components/counter/counter.js
Component({
properties: {
min: {
type: Number,
value: 0
},
max: {
type: Number,
value: 100
},
step: {
type: Number,
value: 1
}
},
data: {
current: 0,
canDecrease: false,
canIncrease: true
},
observers: {
'min, max, current': function(min, max, current) {
this.setData({
canDecrease: current > min,
canIncrease: current < max
})
}
},
methods: {
onDecrease() {
if (!this.data.canDecrease) return
const newVal = this.data.current - this.data.step
this.setData({ current: newVal })
this.triggerEvent('change', { value: newVal })
},
onIncrease() {
if (!this.data.canIncrease) return
const newVal = this.data.current + this.data.step
this.setData({ current: newVal })
this.triggerEvent('change', { value: newVal })
}
}
})
<!-- components/counter/counter.wxml -->
<view class="counter">
<button disabled="{{!canDecrease}}" bindtap="onDecrease">-</button>
<text>{{current}}</text>
<button disabled="{{!canIncrease}}" bindtap="onIncrease">+</button>
</view>
这个组件里,properties 定义了外部接口,data 管理内部状态,observers 自动计算按钮是否可用,triggerEvent 把变化通知给父组件。一套组合拳下来,组件既独立又协作。
| 概念 | 作用 | 谁在用 |
|---|---|---|
| data | 私有数据,组件内部使用 | 组件自己 |
| properties | 对外接口,接收外部数据 | 父组件传值 |
| observer/observers | 监听数据变化,自动响应 | 组件内部逻辑 |
| triggerEvent | 向父组件发送事件 | 子组件通知父组件 |
说白了,数据流就是「父传子用 properties,子传父用 events」。这个规矩守住了,组件化开发就成功了一半。
我记得刚带团队时,有个新人问我:「为什么不能直接改 properties?」我让他试了试,结果页面没反应,控制台还报了个警告。嗯,实践出真知嘛。