redux-saga

redux-saga解决的问题就是:避免redux-thunkredux-promiseaction的带来的不纯粹性,dispatch调用的时候的不一致性,减少写测试代码时候的复杂性。

redux-saga主要使用的技术就是ES6出的Generator,用yield来控制代码的执行。

基本使用

和普通的中间件的使用方式一样 。只不过不能直接使用,需要从redux-saga中导出一个创建中间件的方法。

  1. import createSagaMiddleware from 'redux-saga'
  2. import mySaga from './sagas/mySaga'
  3. const sagaMiddleware = createSagaMiddleware()
  4. const store = createStore(rootReducer, applyMiddleware(sagaMiddleware, logger))
  5. // 与普通的中间件不同的地方就在于这里,需要执行run方法。将Middleware连接Store
  6. sagaMiddleware.run(mySaga)

实现一个saga函数,saga函数其实就是一个生成器生成函数。

  1. function* fetchUser(action: AnyAction) {
  2. console.log(action, '我是传递的action')
  3. try {
  4. const user: Record<string, any> = yield delay(1000)
  5. put(createUpdateUserAction(nanoid(), user))
  6. } catch (error) {
  7. console.log('错误', error)
  8. }
  9. }
  10. export default function* () {
  11. yield takeEvery(FETCH_USER, fetchUser)
  12. }

其中FETCH_USERactiontypefetchUser是一个新的生成器函数,里面进行异步数据的获取。

其中几个方法的说明:

  • takeEveryredux-saga提供的一个指令。能够监听store.dispatch触发这个指令监听的actiontype。然后就会执行第二个参数的生成器函数。
  • put:用于执行与dispatch相同的功能。
  • delayredux-saga提供的延迟方法。

而外界调用还是跟原来一样:

  1. store.dispatch(
  2. createFetchUserAction({
  3. id: nanoid(),
  4. name: 'test'
  5. })
  6. )

当然,我们也可以一次性监听很多的Actiontype。不过一般来说,我们也只会监听异步的Actiontype
同步的话,没有这么麻烦,直接调用改变相应的state就可以了。


take和takeEvery

take是比较低层级的Api,它不会帮助我们做些什么事情,只会监听到某个Actiontype,然后我们拿到相应的Action,要做什么我们自己决定。同时它是阻塞性的。且是一次性的,只会监听一次。

  1. function* mySaga() {
  2. while (true) {
  3. // 调用dispatch后就卡在这里,不会进行接下来的工作
  4. const action: unknown = yield take(FETCH_USER)
  5. console.log(action as ReturnType<typeof createFetchUserAction>, 'action')
  6. }
  7. }

这样的话有个好处就是:

  1. take后面的代码阻塞后不会执行。我们有一段流程是比较固定的话,使用这个的话能很快的知道大致流程是怎么样的。例如:
    1. function* loginFlow() {
    2. while (true) {
    3. yield take('LOGIN')
    4. // ... perform the login logic
    5. // 在这里实现或者调用登录的逻辑,然后会卡在下面的take中
    6. yield take('LOGOUT')
    7. // ... perform the logout logic
    8. // 登出之后,在这里写相应的逻辑。然后执行完成
    9. // 等待登录的action
    10. }
    11. }
    经过上面这样写的话,流程就很清楚了。一下子就知道当前是什么状态。

这其实是监听未来的action,因为take只监听了action,但是没有对应的处理函数去处理。
而不像takeEvery一开始就必须针对某一个特定的action做出相应的处理。

一个好的 **UI** 程序应该始终强制执行顺序一致的 **actions**,通过隐藏或禁用意料之外的 **action**

all指令

all指令也是阻塞的,只有等到里面的所有的任务(生成器都执行完成后)都执行完成后,才会执行完成。

  1. export default function* () {
  2. yield all([counterTask(), studentTask()])
  3. console.log("saga 完成")
  4. }

fork指令

fork指令最大的作用就是新开一个任务,不阻塞当前的后面代码执行。但是当前的上下文的生成器函数,必须要等到fork执行完成后才能整体的完成。有点像新开了一个线程。

假如有如下的任务需要执行:

  1. function* fetchAll() {
  2. const task1 = yield fork(handleDelay)
  3. const task2 = yield fork(handleDelay)
  4. const task3 = yield delay(500)
  5. }

task1task2的执行不影响task3的执行,不会进行阻塞。

但是fetchAll任务的完成情况会受到三个任务都完成,这个才会完成。所以,fetchAll这个任务,与下面的写法是一致的。

  1. yield all([call(handleDelay), call(handleDelay), delay(500)])

如果用了fork的话,fetchAll进行错误捕获,而是要在使用fetchAll的地方进行错误捕获。因为当handleDelay报错的时候,handleDelay就会被取消,错误将由调用fetchAll的地方进行捕获。

cancel

用于取消一个或多个任务,实际上,取消的实现原理,是利用generator.return

一旦任务被 fork,可以使用 yield cancel(task) 来中止任务执行。取消正在运行的任务。

如果cancel没有参数,则取消当前任务线(包括他的parent的任务,所有的都会被取消)。一般不会使用,除非是走到某个判断的时候,整个任务都不执行了。

  • 正常的取消fork任务 ```typescript function* handleDelay() { console.log(‘我取消完了’) // 1 yield delay(2000) yield call(getAllStudents) console.log(‘我取消了’) // 不会打印 }

function* fetchAll() { const task1: Task = yield fork(handleDelay) yield cancel(task1) yield delay(1000) console.log(‘我是取消’) // 2 }

export default function* () { try { yield call(fetchAll) } catch (error) { console.warn(error) } console.log(‘我不阻塞’) // 3 }

  1. - 取消了这个任务,并不是表示这个任务里面的代码不会执行了。而是表示这个任务取消之后的代码不会执行了。
  2. - 如果想要在被取消的任务里面执行某一些代码。我们就进行`try{}finnally{}`,可以在`finally`里面进行相应的取消。但是`finally`里面的代码,无论是否取消都会执行,所以,一般我们会配置`cancelled`这个`api`进行判断,如果取消了,那么就执行某些代码,如果没有取消就执行什么代码:具体代码如下:
  3. ```typescript
  4. function* handleDelay() {
  5. try {
  6. console.log('我马上取消')
  7. yield delay(1000)
  8. yield call(getAllStudents)
  9. console.log('我取消完了')
  10. } finally {
  11. const hasCancle: boolean = yield cancelled()
  12. console.log('我被取消了,完毕了', hasCancle)
  13. }
  14. }