如何在快应用开发中使用 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-reduxqa-vuex );年与时驰,未来衍生出其他出色状态管理工具,相信也有快应用版本适配。在众多可选的工具,合适团队的,或是更好的。关于「如何使用 RxJS 作为快应用开发中状态管理工具」,这里也只是做了些抛砖引玉的工作;有任何问题或建议,欢请留言分享。

您可能会感兴趣的文章