Open soraping opened 5 years ago
最近用 redux-observable 搭建了一个样板项目,起先我就被人安利过这个库,由于自己工作的关系,一直没能用上,恰巧最近项目不紧,遂搭一个简单项目来瞅瞅,下面就请跟着我的步伐一步一步的探索这个库的奥秘。
redux-observable
这个库是基于 rxjs 基础上,为 redux 提供的异步解决方案。
rxjs
redux
redux 的异步流
原本 redux 的 action creator 只提供一个同步的 action 但随着业务的扩展,在某个场景下需要异步的 action 来延时调用 dispatch。 经典的库有 redux-thunk,redux-saga以及redux-observable。
action creator
action
dispatch
redux-thunk
redux-saga
redux-thunk的代码很短,很巧妙的使用了 redux 的 applyMiddleware 中间件模式,它让 action creator 不仅可以输出 plain object,也可以输出一个 function 来处理 action,而这个 function 传递的参数就是 上下文的 dispatch,当这个 function 在某个时段执行时,就可以实现延时触发 dispatch 了。
applyMiddleware
plain object
function
这个就是一个典型的函数式编程的案例,巧用了闭包,让 dispatch 方法在函子内没有被销毁。
redux-thunk 及其 applyMiddleware 源码解读
但是这也是有一定的缺点的,就拿常用的 ajax 请求来说,每个 action creator 输出的 function 不尽相同,异步操作分散,而且逻辑也千变万化,action 多了,就不易维护了。
ajax
redux-saga 是另一种异步流,不过它的 action 是统一形式的,并且会集中处理异步操作。
可以理解 redux-saga 做了一个监听器,专门监听 action ,此处的 action 就是 plain object ,当接收到 UI 触发了某个 action 时, redux-saga 就会触发相应的 effects 来处理对应的副作用函数,这个函数返回的也是一个 plain object 的 action给 reducer。
effects
reducer
这样做的好处是,redux-saga 接收了异步函数的管理,将复杂的业务逻辑部分与 redux 解耦,这也是 redux 设计的初衷,action 始终是 plain object,而且 redux-saga 提供了不少工具函数来处理异步流,极大的方便了开发者处理异步。
网上有诸多教程,这里就不一一赘述了。
不过有点郁闷的就是 redux-saga 使用的是 generator,写起来还要在 function 那里加个 *,在我个人看来非常的不习惯,就是特别的别扭。
generator
*
redux-observable 和 redux-saga 有些类似,可以理解为它是将 action 当作即是 observable 也是 observer(发布者与订阅者),就是 rxjs 的 Subject对象,他是数据流的中转站,能够订阅上游数据流,也能被一个或者多个下游订阅。
observable
observer
Subject
redux-observable 将从 ui 触发的 action 转化为一个数据流,并且订阅它。当数据流有数据发出时,这个流的数据管道中设置了对此数据流做的一系列的操作符,或者是高阶 observable,数据流通过管道后,将最终的流转成 action。
上述所说的管道就是由 rxjs 提供的操作符组合
现在开始做一个简易的项目(调用 github api 获取用户的头像和名称):
常规的 redux 项目所需的库,基本上都会用到
yarn add react react-dom redux react-redux react-router redux-logger ... yarn add -D webpack webpack-cli ...
yarn add rxjs redux-observable # ts声明库 yarn add -D @types/rx
. ├── actions ├── components ├── constants ├── epics ├── reducers ├── types └── utils ├── index.html ├── index.tsx ├── routes.tsx ├── store.ts
从上面的目录结构就能看出,比常规的 redux 项目多了一个 epics 目录,这个目录是存放什么文件呢。
epics
redux-observable 的核心就是 Epics ,它是一个函数,接收一个 action (plain object) ,返回一个 action 流,它是一个 Observable 对象。
Epics
Observable
函数签名:
function (action$: Observable<Action>, store: Store): Observable<Action>;
从 Epics 函数出来的 action 已经是一个 Observable 对象了,是一个上游数据流了,可以被各种 rxjs 操作符操作了。
数据流的终端就是一个订阅者,这个订阅者只做一件事儿,就是被 store.dispatch 分发至 reducer
store.dispatch
epic(action$, store).subscribe(store.dispatch);
redux-observable 简易流程:
import { USER } from "@constants"; import { createAction } from "typesafe-actions"; export namespace userActions { export const getGitHubUser = createAction(USER.GITHUB_USER_API); export const setUserInfo = createAction(USER.SET_USER_INFO, resolve => user => resolve(user) ); }
建立一个 user 的操作 action,定义两个 action
epic 文件:
import { ofType, ActionsObservable } from "redux-observable"; import { throwError } from "rxjs"; import { switchMap, map, catchError } from "rxjs/operators"; import { ajax } from "rxjs/ajax"; import { getType } from "typesafe-actions"; import { userActions } from "@actions"; const url = "https://api.github.com/users/soraping"; export const userEpic = (action$: ActionsObservable<any>) => action$.pipe( ofType(getType(userActions.getGitHubUser)), switchMap(() => { return ajax.getJSON(url).pipe( map(res => userActions.setUserInfo(res)), catchError(err => throwError(err)) ); }) );
建立一个 userEpic ,它是一个高阶函数,这个高阶函数携带的参数就是 action$ ,它就是一个上游数据流,这个函数的基础逻辑就是一个 rxjs 的一般操作了。
userEpic
action$
上游 action$ 的数据管道中,监听 action 的变化,当 getType 方法就是获得 action 的 type 是 操作符 oftype 返回的一致,则继续管道后面的操作,switchMap 是一个高阶的操作符,它一般用在 ajax 网络服务请求上,主要处理多个内部 Observable 对象产生并发的情况下,只订阅最后一个数据源,其他的都退订,这样的操作符,非常适合网络请求。
getType
type
oftype
switchMap
这个网络请求就是获取 github 的 api,当获取数据后,调用 action creator 方法传递获取的数据,这个时候并没有返回一个真正的 plain object ,而是一个最终的 action$ 数据流,触发 subscribe 的 store.dispatch(action) 方法,将 plain action 送至 reducer。
subscribe
store.dispatch(action)
plain action
typesafe-actions 库是一个 action 封装库,简化了 action 的操作,它和 redux-actions 很像,但是typesafe-actions这个库对 epic 支持得很好。
typesafe-actions
redux-actions
epic
整合多个epic:
import { combineEpics } from "redux-observable"; import { userEpic } from "./user"; export const rootEpic = combineEpics(userEpic);
combineEpics 方法用来整合多个 epic 高阶方法,它类似与 reducers 的 combineReducers。
combineEpics
reducers
combineReducers
那么,epic 方法已经有了,redux-observable 毕竟是一个中间件,它在 store 中的操作:
store
import { createStore, applyMiddleware } from "redux"; import { createEpicMiddleware } from "redux-observable"; import { composeWithDevTools } from "redux-devtools-extension"; import { routerMiddleware } from "connected-react-router"; import { createLogger } from "redux-logger"; import { createBrowserHistory } from "history"; import { rootReducer } from "./reducers"; import { rootEpic } from "./epics"; export const history = createBrowserHistory(); const epicMiddleware = createEpicMiddleware(); const middlewares = [ createLogger({ collapsed: true }), epicMiddleware, routerMiddleware(history) ]; export default createStore( rootReducer(history), composeWithDevTools(applyMiddleware(...middlewares)) ); // run 方法一定要在 createStore 方法之后 epicMiddleware.run(rootEpic);
将epicMiddleware注册到 redux 中间件中,这样,就能接收到上下文的 action 和 dispatch,不过要注意的是,epicMiddleware要在store设置之后,执行 run 方法,这和 redux-saga一致。
epicMiddleware
这样,基本上 redux 和 redux-observable 组合的基本操作已经差不多了,reducer 的操作基本不变
上述例子的 github 源码
yarn && yarn start # localhost:8000
喜欢的话给个 star 啊!
最后说下学习路径:
函数式编程
从头开始学编程吧,用函数式,纯函数的那种。
redux 源码阅读
大牛的作品,闭包用的炉火纯青,各种高阶函数,精妙绝伦的操作大大降低了代码量,更能看到函数式编程的妙处。
redux源码阅读参考
rxjs 及其操作符
响应式编程的系统学习,但不必要所有操作符都过一遍,这里推荐一本书 《深入浅出 rxjs》,不过书里的版本是 v5 的,官网是 v6 的,除了一些改变外,原理都是相同的。
最近用
redux-observable
搭建了一个样板项目,起先我就被人安利过这个库,由于自己工作的关系,一直没能用上,恰巧最近项目不紧,遂搭一个简单项目来瞅瞅,下面就请跟着我的步伐一步一步的探索这个库的奥秘。redux-observable
背景这个库是基于
rxjs
基础上,为redux
提供的异步解决方案。原本
redux
的action creator
只提供一个同步的action
但随着业务的扩展,在某个场景下需要异步的action
来延时调用dispatch
。 经典的库有redux-thunk
,redux-saga
以及redux-observable
。redux-thunk
redux-thunk
的代码很短,很巧妙的使用了redux
的applyMiddleware
中间件模式,它让action creator
不仅可以输出plain object
,也可以输出一个function
来处理action
,而这个function
传递的参数就是 上下文的dispatch
,当这个function
在某个时段执行时,就可以实现延时触发dispatch
了。这个就是一个典型的函数式编程的案例,巧用了闭包,让
dispatch
方法在函子内没有被销毁。redux-thunk
及其applyMiddleware
源码解读但是这也是有一定的缺点的,就拿常用的
ajax
请求来说,每个action creator
输出的function
不尽相同,异步操作分散,而且逻辑也千变万化,action
多了,就不易维护了。redux-saga
redux-saga
是另一种异步流,不过它的action
是统一形式的,并且会集中处理异步操作。可以理解
redux-saga
做了一个监听器,专门监听action
,此处的action
就是plain object
,当接收到 UI 触发了某个action
时,redux-saga
就会触发相应的effects
来处理对应的副作用函数,这个函数返回的也是一个plain object
的action
给reducer
。这样做的好处是,
redux-saga
接收了异步函数的管理,将复杂的业务逻辑部分与redux
解耦,这也是redux
设计的初衷,action
始终是plain object
,而且redux-saga
提供了不少工具函数来处理异步流,极大的方便了开发者处理异步。网上有诸多教程,这里就不一一赘述了。
不过有点郁闷的就是
redux-saga
使用的是generator
,写起来还要在function
那里加个*
,在我个人看来非常的不习惯,就是特别的别扭。redux-observable
redux-observable
和redux-saga
有些类似,可以理解为它是将action
当作即是observable
也是observer
(发布者与订阅者),就是rxjs
的Subject
对象,他是数据流的中转站,能够订阅上游数据流,也能被一个或者多个下游订阅。redux-observable
将从 ui 触发的action
转化为一个数据流,并且订阅它。当数据流有数据发出时,这个流的数据管道中设置了对此数据流做的一系列的操作符,或者是高阶observable
,数据流通过管道后,将最终的流转成action
。上述所说的管道就是由
rxjs
提供的操作符组合在
redux
中使用redux-observable
现在开始做一个简易的项目(调用 github api 获取用户的头像和名称):
redux
全家桶常规的
redux
项目所需的库,基本上都会用到redux-observable
从上面的目录结构就能看出,比常规的
redux
项目多了一个epics
目录,这个目录是存放什么文件呢。redux-observable
的核心就是Epics
,它是一个函数,接收一个action
(plain object) ,返回一个action
流,它是一个Observable
对象。函数签名:
从
Epics
函数出来的action
已经是一个Observable
对象了,是一个上游数据流了,可以被各种rxjs
操作符操作了。数据流的终端就是一个订阅者,这个订阅者只做一件事儿,就是被
store.dispatch
分发至reducer
redux-observable 简易流程:
建立一个 user 的操作 action,定义两个 action
epic 文件:
建立一个
userEpic
,它是一个高阶函数,这个高阶函数携带的参数就是action$
,它就是一个上游数据流,这个函数的基础逻辑就是一个rxjs
的一般操作了。上游
action$
的数据管道中,监听 action 的变化,当getType
方法就是获得action
的type
是 操作符oftype
返回的一致,则继续管道后面的操作,switchMap
是一个高阶的操作符,它一般用在ajax
网络服务请求上,主要处理多个内部Observable
对象产生并发的情况下,只订阅最后一个数据源,其他的都退订,这样的操作符,非常适合网络请求。这个网络请求就是获取 github 的 api,当获取数据后,调用
action creator
方法传递获取的数据,这个时候并没有返回一个真正的plain object
,而是一个最终的action$
数据流,触发subscribe
的store.dispatch(action)
方法,将plain action
送至reducer
。typesafe-actions
库是一个action
封装库,简化了action
的操作,它和redux-actions
很像,但是typesafe-actions
这个库对epic
支持得很好。整合多个
epic
:combineEpics
方法用来整合多个 epic 高阶方法,它类似与reducers
的combineReducers
。那么,epic 方法已经有了,
redux-observable
毕竟是一个中间件,它在store
中的操作:将
epicMiddleware
注册到 redux 中间件中,这样,就能接收到上下文的action
和dispatch
,不过要注意的是,epicMiddleware
要在store
设置之后,执行 run 方法,这和redux-saga
一致。这样,基本上
redux
和redux-observable
组合的基本操作已经差不多了,reducer
的操作基本不变上述例子的 github 源码
喜欢的话给个 star 啊!
推荐学习路径
最后说下学习路径:
从头开始学编程吧,用函数式,纯函数的那种。
大牛的作品,闭包用的炉火纯青,各种高阶函数,精妙绝伦的操作大大降低了代码量,更能看到函数式编程的妙处。
redux
源码阅读参考响应式编程的系统学习,但不必要所有操作符都过一遍,这里推荐一本书 《深入浅出 rxjs》,不过书里的版本是 v5 的,官网是 v6 的,除了一些改变外,原理都是相同的。