【小程序实战】告别繁琐传递!一文理清页面/组件通信与 Store 全局状态管理
📢 前言 : 大家好,今天集中攻克了微信小程序开发中的两座大山:页面与组件的通信 以及 全局状态管理(Store)。
在刚接触小程序时,我们通常习惯把所有逻辑都写在 Page 里;但随着项目变大,组件化是必经之路。而组件一旦多起来,数据怎么互相传递就成了头疼的问题。今天这篇笔记,就来总结一下我的学习成果,并分享几个避坑经验,希望能帮到正在学习小程序的你!
一、 页面与组件的"窃窃私语":基础通信方式
在引入复杂的 Store 之前,我们必须先掌握原生的页面与组件通信方式。核心可以总结为三招:
1. 父传子:properties (属性绑定)
这是最基础的单向数据流。页面(父)通过属性将数据传递给组件(子)。
页面(父)端:
xml
<!-- index.wxml -->
<my-component my-name="{{userName}}"></my-component>
组件(子)端:
javascript
// components/my-component/my-component.js
Component({
properties: {
myName: {
type: String,
value: '默认名字' // 默认值
}
}
})
2. 子传父:triggerEvent (事件绑定)
当组件内部发生了点击或数据改变,需要通知页面时,就需要用到自定义事件。
组件(子)端触发:
javascript
// 当点击按钮时触发
handleTap() {
this.triggerEvent('myevent', { age: 18 }) // 传递对象给父级
}
页面(父)端接收:
xml
<!-- index.wxml 绑定事件 -->
<my-component bind:myevent="handleChildEvent"></my-component>
javascript
// index.js 处理事件
handleChildEvent(e) {
console.log('收到子组件的数据:', e.detail.age); // 输出 18
}
3. 父控子:selectComponent (获取组件实例)
有时候页面需要直接调用子组件里的方法,这时候可以通过给组件加 id 或 class,直接获取实例。
javascript
// 父页面的 js 中
const child = this.selectComponent('#my-child-id');
child.someMethod(); // 直接调用子组件的方法
// ⚠️ 经验:虽然好用,但不建议滥用,容易造成父子组件强耦合。
二、 告别"回调地狱",拥抱 Store 全局状态管理
❓ 为什么需要 Store?
当遇到跨页面通信 ,或者兄弟组件通信 (比如 A 组件的数据,C 组件也要用)时,如果用原生方法,你需要:A组件 -> 传给父页面 -> 传给B组件 -> ...。这种**"属性层层透传"**简直是噩梦!
这时候,Store(全局状态管理) 就闪亮登场了!在原生小程序中,我们通常使用 mobx-miniprogram 和 mobx-miniprogram-bindings。
1. 定义 Store (数据仓库)
首先创建一个 store.js,用来存放全局共享的数据和修改数据的方法。
javascript
import { observable, action } from 'mobx-miniprogram';
export const store = observable({
// 1. 数据字段 (State)
numA: 1,
numB: 2,
// 2. 计算属性 (Getters)
get sum() {
return this.numA + this.numB;
},
// 3. 修改数据的方法 (Actions)
updateNumA: action(function (step) {
this.numA += step;
})
});
2. 在 Page 中使用 Store
在页面中使用,需要用到 createStoreBindings。
javascript
import { createStoreBindings } from 'mobx-miniprogram-bindings';
import { store } from '../../store/store';
Page({
onLoad() {
// 绑定 Store
this.storeBindings = createStoreBindings(this, {
store,
fields: ['numA', 'numB', 'sum'], // 需要的数据
actions: ['updateNumA'] // 需要的方法
})
},
onUnload() {
// ⚠️ 重点:页面卸载时一定要解绑,防止内存泄漏!
this.storeBindings.destroyStoreBindings();
},
btnHandler() {
this.updateNumA(1); // 直接调用 store 中的 action
}
})
3. 在 Component 中使用 Store
在组件中使用更加优雅,官方提供了一个 behavior。
javascript
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings';
import { store } from '../../store/store';
Component({
behaviors: [storeBindingsBehavior], // 引入 behavior
storeBindings: {
store,
fields: {
numA: () => store.numA, // 映射数据
sum: 'sum'
},
actions: {
updateNumA: 'updateNumA'
}
}
})
三、 💡 学习心得与"避坑"经验分享
经过今天的折腾,我对这两种方式有了更深的体会,总结了以下几条经验:
-
别把什么都塞进 Store 里! Store 确实"真香",但千万别把什么数据都往里面丢。
- 适合放 Store 的: 用户登录信息(Token、头像)、购物车数据、全局主题配置等(跨页面高度共享的数据)。
- 适合放页面/组件内部(data)的: 表单的输入内容、弹窗的显示隐藏状态(
isModalShow)、局部的 Loading 状态。保持局部状态的纯粹,代码才好维护。
-
时刻警惕内存泄漏 在 Page 中使用
createStoreBindings时,必须、一定、千万要 在onUnload生命周期里调用destroyStoreBindings()进行清理。如果你发现从小程序某个页面返回后,页面变卡或者数据出现诡异的重叠,大概率是忘记解绑了。 -
组件通信尽量保持"单向数据流" 即使有了
selectComponent,我们在开发组件时也应尽量遵循:父组件通过 properties 传值,子组件通过 triggerEvent 汇报。把子组件当成一个"黑盒",这样写出来的组件复用性最高,不会因为换了个父页面就报错。
如果这篇文章对你有帮助,点个赞支持一下吧!你的鼓励是我持续分享的动力!