微信小程序:如何做到爷孙组件通讯
背景
我们公司项目使用的是原生微信小程序,很多东西都逐渐组件化,但存在多层嵌套或者需要监听A一个变量的变化=> 触发B、C组件进行相应的事件。。。
例子:一个很深层级的Tabs组件,Tab的点击触发页面滚动到最顶部,一个按钮组件点击也回到最顶部。
核心思路
我们如果经常使用Vue做B端开发的帅哥
会想到以下常见的解决方案
状态管理
常见的状态管理有Redux、Vuex与MobX。
Redux | Vuex | MobX | |
---|---|---|---|
语言 | 主要与React结合使用,但也可用于其他框架。 | 针对Vue.js。 | 不与特定框架绑定,可用于React、Vue等。 |
工作原理 | 单一不可变的状态树,通过纯函数来处理状态变化。 | 基于Flux架构,有状态、变更和行为三个核心概念。 | 基于可观察的数据结构,状态可直接变更。 |
异步 | 异步操作需要使用中间件(例如Redux Thunk)。 | 使用actions处理异步操作。 | 内置支持异步操作。 |
严格性 | 严格的单向数据流,更多的概念,如actions、reducers和store。 | 严格单向数据流,但比Redux更简单。 | 松散的单向数据流,更自由。 |
可预测性 | 可追踪、可重现的状态变化。 | 易于追踪和调试。 | 相对较灵活,可能需要更多的注意力。 |
学习曲线 | 相对较高,需要理解一些概念。 | 相对较低,特别适用于Vue.js项目。 | 相对较低,特别适用于那些希望更自由的状态管理方式的开发者。 |
适用项目 | 大型项目,需要严格的状态管理和可维护性. | 中小型项目,需要一种简单而直观的状态管理. | 中小型项目或者需要跨框架的项目,注重简洁性和灵活性. |
微信小程序原生可以采取Mobx,有一个专门的第三方库mobx-miniprogram(点击查看使用方法
(后续可能会出一章专门讲这个的文章)。
在这里有一种大材小用的感觉,还增加了项目的大小深受小程序包大小限制的苦
dddd,当然还有一种最简单的状态管理,利用小程序的全局app.js
,但可能不利于后续维护且不适用于复杂的状态管理。
provide/inject API
在Vue.js中,provide
和 inject
是一对用于父组件向子组件传递数据的高级选项。它们的原理基于 Vue.js 的依赖注入系统,允许父组件在组件树中提供数据,而子组件可以通过 inject
选项来接收这些数据。
原理简述
-
provide
的作用- 在父组件中,通过
provide
选项提供数据 provide
选项的值可以是对象或一个函数,返回一个对象。这个对象中包含需要传递给子组件的数据。
// ParentComponent.vue export default { provide() { return { providedData: this.someData, }; }, data() { return { someData: 'Hello from Parent', }; }, };
- 在父组件中,通过
-
inject
的作用- 在子组件中,通过
inject
选项来接收从父组件提供的数据。 inject
的值可以是一个数组,包含需要注入的属性名,也可以是一个对象,指定属性名和对应的默认值。
// ChildComponent.vue export default { inject: ['providedData'], mounted() { console.log(this.providedData); // Accessing the provided data }, };
- 在子组件中,通过
原理解析
- 当子组件需要注入数据时,Vue.js 在组件的
inject
选项中查找提供该数据的所有父级组件。 - 如果找到匹配的父级组件,子组件将接收到那个父级组件
provide
选项中对应的数据。 - 如果没有找到匹配的父级组件,子组件将按照
inject
选项中定义的默认值来处理。
通过这种方式,Vue.js 实现了一种非常灵活的数据传递机制,允许在组件树中的任意层次进行数据的提供和接收。这也使得组件之间的耦合度降低,同时增加了灵活性。
小程序应用
在微信小程序中,并没有类似 Vue.js 中 provide 和 inject 的内置机制,通常涉及到 全局数据管理 和 事件订阅发布模式。
以下是一个简单的示例,但主要适用于静态的全局变量:
- 创建一个全局数据管理模块(globalData.js)
// globalData.js
const dataStore = {};
export function provide(key, value) {
dataStore[key] = value;
}
export function inject(key) {
return dataStore[key];
}
- 在需要提供数据的组件中使用 provide
// pages/index/index.js
import { provide } from '../../utils/globalData';
Page({
onLoad: function () {
provide('providedData', 'Hello from Index');
},
});
- 在需要使用数据的组件中使用 inject:
// pages/child/child.js
import { inject } from '../../utils/globalData';
Page({
onLoad: function () {
const providedData = inject('providedData');
console.log(providedData); // Accessing the provided data
},
});
发布订阅模式
发布订阅模式是一种设计模式,用于处理对象之间的松散耦合,其中一个对象(发布者或主题)发布事件,而其他对象(订阅者)订阅这些事件。
原理解析
- 发布者(Subject/Publisher) 负责维护事件的注册和通知订阅者的机制。
- 订阅者(Observer/Subscriber) 注册对特定事件的兴趣,并在事件发生时得到通知。
- 事件(Event) 表示发布者发生的某个动作或状态改变。
- 事件通道(Event Channel) 是发布者和订阅者之间的通信媒介,用于传递事件信息。
原谅我的懒惰,不想用其他软件画了,讲究看吧。。。
自定义实现
- 定义一个事件发布类 定义一个类,而不是一个Maps,是防止出现不同页面之间变量或事件的冲突。
class EventEmitter {
static callbacks = {}
constructor(id) {
this.id = id
}
on(event, callback) {
if (!EventEmitter.callbacks[event]) {
EventEmitter.callbacks[event] = []
}
EventEmitter.callbacks[event].push(callback)
}
emit(event, data) {
if (EventEmitter.callbacks[event]) {
EventEmitter.callbacks[event].forEach((callback) => {
callback(data)
})
} else {
console.error('EventEmitter: no event named ' + event + ' found')
}
}
}
export default EventEmitter
2.订阅事件
const emitter = new EventEmitter('PageName')
emitter.on('event', (data) => { console.log('Event received:', data); });
3.触发事件
const emitter = new EventEmitter('PageName')
emitter.emit('event', { message: 'Hello, mitt!' });
适用第三方库mitt
mitt
是一个简单且灵活的事件总线库,属于发布-订阅模式中的一种。它提供了一个基于 JavaScript 事件机制的轻量级实现,用于处理事件的订阅和发布。通过 on
方法订阅事件,通过 emit
方法触发事件。
import mitt from 'mitt';
// 创建事件总线
const emitter = mitt();
// 订阅事件
emitter.on('event', (data) => {
console.log('Event received:', data);
});
// 触发事件
emitter.emit('event', { message: 'Hello, mitt!' });
相比采用类的静态属性和方法,而 mitt
库则更倾向于通过实例化来创建事件总线对象。同时mitt
库通过每个实例来维护事件回调,从而避免了这个问题。每个事件总线实例都有自己的回调存储,确保了更好的隔离性。
如果你需要更多的特性、更好的隔离性和更丰富的错误处理,可以考虑使用成熟的库,如 mitt
。
总结
状态管理(MobX) | 模仿Vue的provide/inject API | 发布订阅模式 | |
---|---|---|---|
优点 | - 可以在全局状态中存储数据。- 提供响应式状态变化。- 适合大型应用。 | - 轻量级的组件通信方式。- 易于在组件树中传递数据。 | - 提供松散耦合的通信方式。- 支持组件相互独立。 |
缺点 | - 引入可能较繁琐。- 学习曲线较高。 | - 灵活性可能不如全局状态管理。- 需要手动模拟 provide/inject。 | - 需要额外的事件系统。- 事件追踪可能较难。 |
适用场景 | - 复杂的状态逻辑。- 需要全局响应式状态。 | - 小型应用。- 轻量级通信需求。 | - 组件解耦。- 灵活通信需求。 |