RxJS 在现在的前端用比较少,但是 RxJS 作为响应式和函数式编程的集大成者,似乎被前端开发者遗忘,可能是学习难度大,可能是有更加方便的解决方案。
不是因为 Redux 更具有性价比,而是 RxJS 可以打开更大的 JS 生态空间
下面我们先回顾一下 Redux 是如何运作开始。
一、Redux 创建一个 Store 做了哪些事情?
以上是一个简单的 Redux 的工作流。从 reducer 到视图派发更新的整个流程
Redux 通常在单页面应用中,与React结合,他的基本使用流程。
定义 Reducer 函数
使用 createStore 函数传入 Reducer 创建 store
订阅函数 store
派发类型更新数据。
以下是一个官网的例子:
import { createStore } from 'redux'
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case 'counter/incremented':
return { value: state.value + 1 }
case 'counter/decremented':
return { value: state.value - 1 }
default:
return state
}
}
let store = createStore(counterReducer)
store.subscribe(() => console.log(store.getState()))
store.dispatch({ type: 'counter/incremented' })
store.dispatch({ type: 'counter/decremented' })
创建一个 Redux 其实也是需要订阅(subscribe)数据。才能使用 dispatch 来派发行为到 reducer 里面。
二、React Redux 与 React 结合
React Redux 是 React 官方与 Redux 结合,方便在 React 组件中使用 Redux。提供了:
Provider 组件
钩子函数
...
2.1)Redux 的难点
初学 Redux 难点就是熟练根据自己的业务完成此流程。
2.2)中心化
Redux 是将数据集中到一个 store 库,集中化数据管理,在大型项目中,你可能不想讲数据集中化,Redux 在新的版本 Redux Toolkit 有了分片等功能开始弥补 Redux 的问题。
初学 Redux 其实还是有一定难度的,需要灵活使用 reducer 与 dispatch, 还需要 React 进行结合。并且异步副作用在 Redux 中还不好处理。
2.3)异步任务处理
Redux 通常不能直接处理异步任务,通常配合
Redux-thunk
Redux-saga
对异步任务进行处理。基于以上我们知道了其实 Redux 其实也具有自己优点和缺点。
三、RxJS 为什么可以替代 Redux ?
3.1)React 作为视图层
类似于 React 和 Vue 等框架,虽然组件具有自己的状态管理,但是在复杂数据关系时力不从心。于是演化出了 Redux 等数据层管理库。但是 RxJS 本身基于事件流,拥有强大的处理数据流能力。与视图层集合便拥有了强大的数据处理能力。
3.2)Redux 作为数据层
通过以上的简介,可以理解 Redux 其实就是一个 store 作为数据层而存在。它通常具有有以下一些操作:
存储数据
取出数据
改变数据
异步处理
3.3) RxJS 作为数据层可以这样做
RxJS 是一个函数式和响应式的 JavaScript, 自带响应式数据能力。
普通的可观察对象,只产生一个可观察对象数据。也没有缓存数据的能力。明显不好,但是 RxJS 实现了 Subject 系列,其中 BehaviorSubject 能够自带初始值,并且也有缓存能力,能够实现跨组件的订阅数据。
BehaviorSubject 是实现 Store 中状态管理的最佳选择。
四、一个简单的 RxJS 实现 Store
4.1)思考
我们要实现一个 Store,我们基于 class 和 React 的 hooks 进行设计。在 Store 需要考虑跨越组件和和跨越页面的状态管理设计。
BehaviorSubject 行为主题具有函数数据和初始化能力,是作为状态管理的最佳选择
4.2)实现一个简单的 Store
import { BehaviorSubject, type SubjectLike } from "rxjs";
export class Store {
state$;
constructor() {
this.state$ = new BehaviorSubject({counter: 10 });
}
updateState(data: IState) {
this.state$.next({ ...data });
}
getState(): SubjectLike<any> {
return this.state$;
}
unsubscribe() {
this.state$.unsubscribe()
}
}
export const store = new Store()
这个 Store 也非常简单,在构造函数中创建行为主题,然后再 getState 中获取状态主题,已经在 updateState 中使用 next 方法更新主题,最后在 unsubscribe 中取消订阅。
这里我们实例化一个全局 store, 然后输出,方便在全局使用。
4.3)基于 Store 封装 hooks 方便在 React 组件中使用
import { store } from './store'
export function useStore() {
const [data, setData] = useState<IState>({ counter: 0 })
useEffect(() => {
store.getState().subscribe((v: any) => {
setData(!v ? { counter: 0 } : v);
});
return () => {
store.unsubscribe()
}
}, [])
const updateData = (data: IState) => {
store.updateState({...data})
}
return [data, updateData]
}
注意: store 是设计考虑在全局使用的,意味着这里的 useStore 是具有跨越页面和组件能力的。其中重要的方法 updateData 是通过在修改 Store 内部的数据进行全局的状态管理。
4.4)用法
export function Sub() {
const [data, updateData] = useStore()
return <div>
counter {data.counter}
<button onClick={() => updateData({
counter: data.counter + 1
})}>+</button>
</div>
}
一个简单的 Sub 组件中,直接调用 useStore 可以方便进行同步状态管理和内容更新。下面添加一个方法,用于处理异步任务。
4.5)添加异步方法
在 store 类中添加一个异步方法:
class Store {
/ 其他方法/
// 添加异步管理
asyncOperation(): Observable<any> {
return of(/* Your async operation result */).pipe(
switchMap((result) => {
// 处理异步操作的结果,并更新状态
this.updateState({ counter: result });
return of(result);
}),
catchError((error) => {
// 处理错误
console.error('Error in async operation:', error);
return of(null);
})
);
}
}
asyncOperation 是一个 store 异步方法,使用 of 操作符,通过管道 switchMap 处理异步任务,并返回新的可观察对象,然后使用 catchError 处理错误。
4.6) RxJS 作为状态管理的优点
可以借助强大的 RxJS 异步数据处理能力
配合 React hooks 可以磨平 RxJS 代码层面对React代码的入侵
熟悉 RxJS 的小伙伴,可以拥抱更加广泛的 JS 生态
适合大型项目
4.7) 缺点
对新手并不友好
RxJS 学习曲线比较高
不适合小型项目
五、小结
本文主要关注 RxJS 作为状态管理的能力,通过实现一个简单的 Store 配合 BehaviorSubject 与 React hooks 进行配合实现一个简单的全局状态管理方案。同时也给出了异步数据的处理方案。本文需要对 RxJS 和 React 以及 React 状态管都比较熟悉,借助 RxJS 强大的异步数据流处理能力与 React hooks 结合,能够很好的磨平 RxJS 客观对象对React 组件代码的入侵,在代码层面也保持了简洁。最后也希望本文能够帮助到大家。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。