如何在快应用开发中使用 RxJS 做状态管理?
RxJS 是 JavaScript 的反应式(响应式)编程库,它通过使用可观察(Observable)序列,来编写异步和基于事件的程序。可将 RxJS 视为事件的 Lodash
。之前,分享了一篇文章: 如何在快应用开发中使用 vuex 做状态管理 ,本文旨在与大家分享: 如何在快应用开发中使用 RxJS 做状态管理 。
RxJS Subject 介绍
RxJS Subject 是一种特殊类型的 Observable,它允许将值多播到多个 Observer。虽然普通的 Observable 是单播的(每个订阅的 Observer 都拥有 Observable 的独立执行),但 Subjects 是多播的。在 Subhect 内部,subscribe
并不调用一个新的执行来传递值。它只是将给定的观察者注册到观察者列表中,类似于其他库和语言中 addListener
的工作方式。
在 RxJS 中有四种 Subject 分别是:Subject,BehaviorSubject,AsyncSubject,ReplaySubject;这四种 Subject 都是特殊的 Observable(Hot)关于几者区别,可参见文章: RxJS:四种 Subject 的用法和区别 、 Subjects - Learn RxJS 。
普通的 Observable 只是数据生产者(发送数据一方)。而 Subject,BehaviorSubject,AsyncSubject 和 ReplaySubject 既是是生产者又是消费者(接收数据的一方)。因此,完全可以借助 Subject 来实现发布订阅,方便让 Component 之间,Page 之间,Component 与 Page 之间共享数据,从而实现状态管理。
状态管理的必要性
您知道, 快应用 也使用 MVVM
的设计模式进行开发,开发者无需直接操作 DOM 节点的增删,利用数据驱动的方式完成节点更新;基于组件树来描述页面,已成为行业共识;为更好复用代码,组件开发应该遵守单一职责、低耦合等原则,尽可能细粒度拆分;当有数据联动的内容被分散在不同组件,便会产生诸如兄弟组件、跨层级组件通信问题。虽然基于 props
、全局变量可以解决问题,但不够优雅简洁。能清晰管理多个组件共享状态,便是「状态管理」显而易见的动机。当然,它的功用远不止如此。
假如您的 快应用 项目比较简单,大可不必引入状态管理。
RxJS 如何做状态管理?
基于 RxJS 充当快应用数据管理,与 Pinia 之于 Vue3 ,非常类似:基本 stores,然后在组件或页面中使用它们即可。下面是简单的代码示例说明:
创建 stores
// stores/index.js
import fetch from '@system.fetch'
import { Subject } from 'rxjs'
export const ActionTypes = {
UPDATE_COUNT: 'update count',
UPDATE_RECORD: 'update record',
REQUEST_DATA: 'request data'
}
// Save the data state.
const state = {
count: 0,
record: [],
}
export const store = new Subject()
export const dispatcher = new Subject()
dispatcher.subscribe((action) => {
switch (action.type) {
case ActionTypes.UPDATE_COUNT:
state.count = action.data
break
case ActionTypes.UPDATE_RECORD:
state.record = action.data
break
case ActionTypes.REQUEST_DATA:
fetch.fetch({
url: 'https://doc.quickapp.cn/search_plus_index.json',
responseType: 'json'
}).then(res => {
// do something.
}).catch((error) => {
console.log(`Something Error: ${error}`)
})
break
}
// record the actions about count.
state.record.push(action.data)
store.next(state)
})
组件、页面中使用
component/bar.ux 代码:
<template>
<div class="wrapper">
<input
class="btn"
type="button"
value="Bar Component (+)"
onclick="onExecAction"
/>
</div>
</template>
<script>
import { store, dispatcher, ActionTypes } from './../stores/index'
export default {
data: {
count: 0,
},
onInit() {
store.subscribe((state) => {
console.log('Subscribe @bar Component', state)
const { count } = state
this.count = count
})
},
onExecAction() {
dispatcher.next({ type: ActionTypes.UPDATE_COUNT, data: this.count + 1 })
// dispatcher.next({ type: ActionTypes.REQUEST_DATA })
},
}
</script>
pages/Demo/index.ux 主页面代码:
<template>
<div class="wrapper">
<bar />
<text class="title">Main Page Value : {{ count }}</text>
<text class="record">Record Array : [{{ recordStr }}]</text>
<foo />
</div>
</template>
<import name="bar" src="./../../component/bar.ux"></import>
<import name="foo" src="./../../component/foo.ux"></import>
<script>
import { store } from './../../stores/state'
export default {
data: {
count: 0,
recordStr: ''
},
onInit() {
store.subscribe((state) => {
console.log('Subscribe [count] @Demo page', state)
const { count, record } = state
this.count = count
this.recordStr = record.join(',')
})
}
}
</script>
如上代码写法,无论是 count、抑或是 record 数据发生改变,都会触发 subscribe
的地方得到响应;如果你的业务中,这些数据之间,不存在某种相关性,完全可以走各自发布订阅路线;只需针对每个数据,创建 Subject 即可;简要示例代码如下:
// store define
export const store = {
count: new Subject(),
record: new Subject()
}
// page using
import { store } from './../../stores/state'
store.count.subscribe((data) => {})
具体效果
RxJS 状态管理优缺点
早前,有分享文章: 如何在快应用开发中使用 vuex 做状态管理 ,针对快应用本身已经具备的能力(诸如: 兄弟跨级组件通信 、全局变量、 props 等),所存在缺陷简要做了说明,并推荐使用 qa-vuex 管理快应用开发的数据状态。
正如这世间没有完美解决问题方案一样,软件的世界 没有银弹
;同样,基于 qa-vuex
方案也是如此;至少体现在这几点:1,会增加额外约 10KB 体积;2,相对 快应用
写法,有较强侵入性;3,尚未使用过 Vuex
的朋友,有一定学习成本。
RxJS 实现状态管理的优点
相比 qa-vuex
,基于 RxJS Subject 来塑造「状态管理」,学习成本低;Subject 而且非常灵活,极具可扩展性(别忘了还有 BehaviorSubject 其他变体);可以根据自身项目,封装/构建适合的 Store。此外,在上篇文章: 如何在快应用开发中使用 RxJS? ,介绍了 RxJS 些许基本用法及场景,完全可结合起来,最大化发挥 RxJS (响应式、函数式编程)的优势。
RxJS 实现状态管理的缺点
JavaScript 语言,灵活是其优点,却也是缺点。基于 RxJS Subject 来塑造「状态管理」,同样也存在类似问题。灵活,也就创造了可能被滥用的肥沃土壤;这与 Vue Event Bus 如出一辙,基于发布订阅者的思想,虽然非常优雅且简单,但存在状态变更难以跟踪等问题。这也便是 Vuex、React、Redux 都设置诸多限制及附加功能的动机。在上面的示例代码中,也是特意增加 dispatcher
,用以集中处理操作(不建议直接调用 store.next()
来提供新值)。适当的限制,或可促进整体之自由(尤其是在 维护
)。
温馨提醒:快应用构建( hap-toolkit )先前存在些许问题,诸如 TreeShaking 不能按预期工作;在 IDE 6.1 版本以后,做了修复。在写这篇文章时,有基于 IDE 6.2 版本构建,发现即便手动开启了 useTreeShaking
这样的配置(详情可参见 如何优化「快应用」rpk 包体积? ),仍使得体积增大不少;需要进一步研究、排查 & 解决。目前,可采取如下方式,以做规避:
// import { Subject } from 'rxjs'
// 修改为如下写法: =>
import { Subject } from '../../node_modules/rxjs/dist/esm5/internal/Subject.js'
备注:基于如上方式引入 Subject(RxJS), 在生产环境,对 rpk 体积造成的增加,跟使用 qa-vuex
差不多, 大概有 10KB。
如何优化改进?
上述基于 RxJS Subject 实现的简单状态管理,在真实项目中,建议更多做些处理,诸如对具有相关性进行分组,以建立多个 Store,在页面和组件,按需 import、subscribe 和 dispatch;从而让促进代码条理清晰;在此基础上,也可以参考 Redux、 Pinia 设计理念,进行再次封装。
蛮久之前,就有程序员对 Redux、Vuex 对快应用进行了适配(可参见: quickapp-redux 、 qa-vuex );年与时驰,未来衍生出其他出色状态管理工具,相信也有快应用版本适配。在众多可选的工具,合适团队的,或是更好的。关于「如何使用 RxJS 作为快应用开发中状态管理工具」,这里也只是做了些抛砖引玉的工作;有任何问题或建议,欢请留言分享。