如何在快应用开发中使用 RxJS 做状态管理?

快应用 Sep 15, 2022

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 做状态管理

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 作为快应用开发中状态管理工具」,这里也只是做了些抛砖引玉的工作;有任何问题或建议,欢请留言分享。

您可能会感兴趣的文章

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.