首頁  >  文章  >  web前端  >  使用RxJS管理React應用程式狀態的介紹

使用RxJS管理React應用程式狀態的介紹

不言
不言轉載
2019-04-03 10:24:592794瀏覽

這篇文章帶給大家的內容是關於使用RxJS管理React應用程式狀態的介紹,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

隨著前端應用的複雜度越來越高,如何管理應用的資料已經是一個不可迴避的問題。當你面對的是業務場景複雜、需求變動頻繁、各種應用資料互相關聯依賴的大型前端應用時,你會如何去管理應用的狀態資料呢?

我們認為應用的資料大體上可以分為四類:

  • 事件:瞬間產生的數據,資料被消費後立即銷毀,不儲存。
  • 非同步:非同步獲取的數據;類似於事件,是瞬間數據,不儲存。
  • 狀態:隨著時間空間變化的數據,總是會儲存一個目前值/最新值。
  • 常數:固定不變的資料。

RxJS天生就適合寫非同步和基於事件的程序,那麼狀態資料用什麼去管理呢?還是用RxJS嗎?合不合適呢?

我們去研究和學習了前端社群已有的優秀的狀態管理解決方案,也從一些大牛分享的關於用RxJS設計資料層的構想和實踐中得到了啟發:

  1. 使用RxJS完全可以實現諸如Redux,Mobx等管理狀態資料的功能。
  2. 應用的資料不是只有狀態的,還有事件、非同步、常數等等。如果整個應用都由observable來表達,則可以藉助RxJS基於序列且可回應的的特性,以流的方式自由地拼接和組合各種類型的數據,能夠更優雅更有效率地抽像出可重複使用可擴充的商業模式。

基於上述兩點原因,最後決定基於RxJS來設計一套管理應用程式的狀態的解決方案。

原理介紹

對於狀態的定義,通常認為狀態需要滿足以下3個條件:

  1. 是一個具有多個值的集合。
  2. 能夠透過eventaction對值進行轉換,從而得到新的值。
  3. 有「目前值」的概念,對外一般只暴露目前值,即最新值。

那麼,RxJS適合用來管理狀態資料嗎?答案是肯定的!

首先,因為Observable本身就是多個值的推送集合,所以第一個條件是滿足的!

其次,我們可以實作一個使用dispatch action模式來推送資料的observable來滿足第二個條件!

眾所周知,RxJS中的observable可以分成兩種類型:

cold observable: 推送值的生產者(producer)來自observable內部。

  • 將會推送幾個值以及推送什麼樣的值已在observable建立時被定義下來,不可改變。
  • producer與觀察者(observer) 是一對一的關係,即是單播的。
  • 每當有observer訂閱時,producer都會把預先定義好的若干個值依序推送到observer

hot observable: 推送值的producer來自observable外部。

  • 將會推送幾個值、推送什麼樣的值以及何時推送在建立時都是未知的。
  • producerobserver是一對多的關係,即是多播的。
  • 每當有observer訂閱時,會將observer註冊到觀察者清單中,類似於其他函式庫或語言中的addListener的工作方式。
  • 當外部的producer被觸發或執行時,會將值同時推送給所有的observer;也就是說,所有的observer共享了hot observable推送的值。

RxJS提供的BehaviorSubject就是一個特殊的hot observable,它向外暴露了推送資料的介面next函數;並且有「目前值」的概念,它保存了發送給observer的最新值,當有新的觀察者訂閱時,會立即從BehaviorSubject那接收到「目前值」。

那麼這說明使用BehaviorSubject來更新狀態並保存狀態的目前值是可行的,第三個條件也滿足了。

簡單實作

請看以下的程式碼:

import { BehaviorSubject } from 'rxjs';

// 数据推送的生产者
class StateMachine {
  constructor(subject, value) {
    this.subject = subject;
    this.value = value;
  }

  producer(action) {
    let oldValue = this.value;
    let newValue;
    switch (action.type) {
      case 'plus':
        newValue = ++oldValue;
        this.value = newValue;
        this.subject.next(newValue);
        break;
      case 'toDouble':
        newValue = oldValue * 2;
        this.value = newValue;
        this.subject.next(newValue);
        break;
    }
  }
}

const value = 1;  // 状态的初始值
const count$ = new BehaviorSubject(value);
const stateMachine = new StateMachine(count$, value);

// 派遣action
function dispatch(action) {
  stateMachine.producer(action);
}

count$.subscribe(val => {
  console.log(val);
});

setTimeout(() => {
  dispatch({
    type: "plus"
  });
}, 1000);

setTimeout(() => {
  dispatch({
    type: "toDouble"
  });
}, 2000);

執行程式碼控制台會印出三個值:

Console

 1
 2
 4

上面的程式碼簡單實作了一個簡單管理狀態的範例:

  • 狀態的初始值: 1
  • #執行plus之後的狀態值: 2
  • 執行toDouble之後的狀態值: 4

實作方法挺簡單的,就是使用BehaviorSubject來表達狀態的目前值:

  • 第一步,通过调用dispatch函数使producer函数执行
  • 第二部,producer函数在内部调用了BehaviorSubjectnext函数,推送了新数据,BehaviorSubject的当前值更新了,也就是状态更新了。

不过写起来略微繁琐,我们对其进行了封装,优化后写法见下文。

使用操作符来创建状态数据

我们自定义了一个操作符state用来创建一个能够通过dispatch action模式推送新数据的BehaviorSubject,我们称她为stateObservable

const count$ = state({
  // 状态的唯一标识名称
  name: "count",
    
  // 状态的默认值
  defaultValue: 1,
    
  // 数据推送的生产者函数
  producer(next, value, action) {
    switch (action.type) {
      case "plus":
        next(value + 1);
        break;
      case "toDouble":
        next(value * 2);
        break;
    }
  }
});

更新状态

在你想要的任意位置使用函数dispatch派遣action即可更新状态!

dispatch("count", {
  type: "plus"
})

异步数据

RxJS的一大优势就在于能够统一同步和异步,使用observable处理数据你不需要关注同步还是异步。

下面的例子我们使用操作符frompromise转换为observable

指定observable作为状态的初始值(首次推送数据)

const todos$ = state({
  name: "todos",
    
  // `observable`推送的数据将作为状态的初始值
  initial: from(getAsyncData())
    
  //...
  
});

producer推送observable

const todos$ = state({
  name: "todos",
    
  defaultValue: []
    
  // 数据推送的生产者函数
  producer(next, value, action) {
    switch (action.type) {
      case "getAsyncData":
        next(
          from(getAsyncData())
        );
        break;
    }
  }
});

执行getAsyncData之后,from(getAsyncData())的推送数据将成为状态的最新值。

衍生状态

由于状态todos$是一个observable,所以可以很自然地使用RxJS操作符转换得到另一个新的observable。并且这个observable的推送来自todos$;也就是说只要todos$推送新数据,它也会推送;效果类似于Vue的计算属性。

// 未完成任务数量
const undoneCount$ = todos$.pipe(
  map(todos => {
    let _conut = 0;
    todos.forEach(item => {
      if (!item.check) ++_conut;
    });
    return _conut;
  })
);

React视图渲染

我们可能会在组件的生命周期内订阅observable得到数据渲染视图。

class Todos extends React.Component {
  componentWillMount() {
    todos$.subscribe(data => {
      this.setState({
        todos: data
      });
    });
  }
}

我们可以再优化下,利用高阶组件封装一个装饰器函数@subscription,顾名思义,就是为React组件订阅observable以响应推送数据的变化;它会将observable推送的数据转换为React组件的props

@subscription({
  todos: todos$
})
class TodoList extends React.Component {
  render() {
    return (
      <p className="todolist">
        <h1 className="header">任务列表</h1>
        {this.props.todos.map((item, n) => {
          return <TodoItem item={item} key={item.desc} />;
        })}
      </p>
    );
  }
}

总结

使用RxJS越久,越令人受益匪浅。

  • 因为它基于observable序列提供了较高层次的抽象,并且是观察者模式,可以尽可能地减少各组件各模块之间的耦合度,大大减轻了定位BUG和重构的负担。
  • 因为是基于observable序列来编写代码的,所以遇到复杂的业务场景,总能按照一定的顺序使用observable描述出来,代码的可读性很强。并且当需求变动时,我可能只需要调整下observable的顺序,或者加个操作符就行了。再也不必因为一个复杂的业务流程改动了,需要去改好几个地方的代码(而且还容易改出BUG,笑~)。

所以,以上基于RxJS的状态管理方案,对我们来说是一个必需品,因为我们项目中大量使用了RxJS,如果状态数据也是observable,对我们抽象可复用可扩展的业务模型是一个非常大的助力。当然了,如果你的项目中没有使用RxJS,也许ReduxMobx是更合适的选择。

这套基于RxJS的状态管理方案,我们已经用于开发公司的商用项目,反馈还不错。所以我们决定把这套方案整理成一个js lib,取名为:Floway,并在github上开源:

  • github源码:https://github.com/shayeLee/floway
  • 使用文档:https://shayelee.github.io/floway

【相关推荐:react视频教程

以上是使用RxJS管理React應用程式狀態的介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除