Redux-Saga.js 学习笔记

昨天认真看完了Promise 与 Generator,今天早上认真学了一下redux-saga.js。把笔记输出到这里

redux-saga的api

  • put

    作用类似于 dispatch,用来 put 同步 的 generator function,也把一些状态 put 到 reducer

  • call

    我的理解是 调用api , 发起请求,也可以用来call 同步的 action

    官方说明

    与前面的例子不同的是,现在我们不立即执行异步调用,相反,call 创建了一条描述结果的信息。 就像在 Redux 里你使用 action 创建器,创建一个将被 Store 执行的、描述 action 的纯文本对象。 call 创建一个纯文本对象描述函数调用。redux-saga middleware 确保执行函数调用并在响应被 resolve 时恢复 generator。

    需要注意的,写异步流的时候

    // 错误写法,effects 将按照顺序执行
    const users = yield call(fetch, '/users'),
          repos = yield call(fetch, '/repos')
    
    // 由于第二个 effect 将会在第一个 call 执行完毕才开始。所以我们需要这样写:
    import { call } from 'redux-saga/effects'
    
    // 正确写法, effects 将会同步执行
    const [users, repos] = yield [
      call(fetch, '/users'),
      call(fetch, '/repos')
    ]
    
    
  • takeEvery

    非常常见,提供了类似 redux-thunk的行为

    // 首先我们创建一个将执行异步 action 的任务:
    import { call, put } from 'redux-saga/effects'
    
    export function* fetchData(action) {
       try {
          const data = yield call(Api.fetchUser, action.payload.url);
          yield put({type: "FETCH_SUCCEEDED", data});
       } catch (error) {
          yield put({type: "FETCH_FAILED", error});
       }
    }
    // 然后在每次 FETCH_REQUESTED action 被发起时启动上面的任务。
    import { takeEvery } from 'redux-saga'
    
    function* watchFetchData() {
      yield* takeEvery('FETCH_REQUESTED', fetchData)
    }
    

    在上面的例子中,takeEvery 允许多个 fetchData 实例同时启动。在某个特定时刻,尽管之前还有一个或多个 fetchData 尚未结束,我们还是可以启动一个新的 fetchData 任务,

  • takeLatest

    如果我们只想得到最新那个请求的响应(例如,始终显示最新版本的数据)。我们可以使用 takeLatest 辅助函数。

    import { takeLatest } from 'redux-saga'
    
    function* watchFetchData() {
      yield* takeLatest('FETCH_REQUESTED', fetchData)
    }
    

    takeEvery 不同,在任何时刻 takeLatest 只允许一个 fetchData 任务在执行。并且这个任务是最后被启动的那个。 如果已经有一个任务在执行的时候启动另一个 fetchData ,那之前的这个任务会被自动取消。

  • select

    根据 在组件中dispatch一个action的例子中,如果要在effects中对于param数据和当前的state数据进行再出处理,这里怎么获取state呢?采用select,如下:

    export default {
     
      namespace: 'example',   
     
      state: {num:1},     //表示当前的example中的state状态,这里可以给初始值,这里num初始为1
     
     
      effects: { //这里是做异步处理的
        *addByONe({ param}, { call, put,select }) { //这里使用select
     
          const num = yield select(state => state.num)    //这里就获取到了当前state中的数据num
         //方式二: const num = yield select(({num}) =>num)
     
        //方式三: const num = yield select(_ =>_.num)
     
          let param1;
           param1 = num + param;   这里就可以使用num进行操作了
      
          yield put({
             type: 'save',   
             num:param1
          });
        }
       
     
      },
        
      //用来保存更新state值 上面的put方法调用这里的方法
      reducers: {
        save(state, action) { //这里的state是当前总的state,这里的action包含了上面传递的参数和type
          return { ...state, ...action.num }; //这里用ES6语法来更新当前state中num的值
        },
      },
     
    };
    
  • take

    take 就像我们更早之前看到的 callput。它创建另一个命令对象,告诉 middleware 等待一个特定的 action。 正如在 call Effect 的情况中,middleware 会暂停 Generator,直到返回的 Promise 被 resolve。 在 take 的情况中,它将会暂停 Generator 直到一个匹配的 action 被发起了。 在以上的例子中,watchAndLog 处于暂停状态,直到任意的一个 action 被发起。

    import { select, take } from 'redux-saga/effects'
    
    function* watchAndLog() {
      while (true) {
        const action = yield take('*')
        const state = yield select()
    
        console.log('action', action)
        console.log('state after', state)
      }
    }
    

    一个简单的例子,假设在我们的 Todo 应用中,我们希望监听用户的操作,并在用户初次创建完三条 Todo 信息时显示祝贺信息。

    import { take, put } from 'redux-saga/effects'
    
    function* watchFirstThreeTodosCreation() {
      for (let i = 0; i < 3; i++) {
        const action = yield take('TODO_CREATED')
      }
      yield put({type: 'SHOW_CONGRATULATION'})
    }
    

    使用拉取(pull)模式,我们可以在同一个地方写控制流,而不是重复处理相同的 action。

    function* loginFlow() {
      while (true) {
        yield take('LOGIN')
        // ... perform the login logic
        yield take('LOGOUT')
        // ... perform the logout logic
      }
    }
    
  • fork

    为了表示无阻塞调用,redux-saga 提供了另一个 Effect:fork。 当我们 fork 一个 任务,任务会在后台启动,调用者也可以继续它自己的流程,而不用等待被 fork 的任务结束。

    所以为了让 loginFlow 不错过一个并发的 LOGOUT,我们不应该使用 call 调用 authorize 任务,而应该使用 fork

    import { take, call, put, cancelled } from 'redux-saga/effects'
    import Api from '...'
    
    function* authorize(user, password) {
      try {
        const token = yield call(Api.authorize, user, password)
        yield put({type: 'LOGIN_SUCCESS', token})
        yield call(Api.storeItem, {token})
        return token
      } catch(error) {
        yield put({type: 'LOGIN_ERROR', error})
      } finally {
        if (yield cancelled()) {
          // ... put special cancellation handling code here
        }
      }
    }
    
  • cancel

    如果我们收到一个 LOGOUT action,我们将那个 task 传入给 cancel Effect。 如果任务仍在运行,它会被中止。如果任务已完成,那什么也不会发生,取消操作将会是一个空操作(no-op)。最后,如果该任务完成了但是有错误, 那我们什么也没做,因为我们知道,任务已经完成了。

    import { take, call, put, cancelled } from 'redux-saga/effects'
    import Api from '...'
    
    function* authorize(user, password) {
      try {
        const token = yield call(Api.authorize, user, password)
        yield put({type: 'LOGIN_SUCCESS', token})
        yield call(Api.storeItem, {token})
        return token
      } catch(error) {
        yield put({type: 'LOGIN_ERROR', error})
      } finally {
        if (yield cancelled()) {
          // ... put special cancellation handling code here
        }
      }
    }
    
  • race

    有时候我们同时启动多个任务,但又不想等待所有任务完成,我们只希望拿到 胜利者:即第一个被 resolve(或 reject)的任务。 race Effect 提供了一个方法,在多个 Effects 之间触发一个竞赛(race)。

    // 下面的示例演示了触发一个远程的获取请求,并且限制了 1 秒内响应,否则作超时处理。
    import { race, call, put } from 'redux-saga/effects'
    import { delay } from 'redux-saga'
    
    function* fetchPostsWithTimeout() {
      const {posts, timeout} = yield race({
        posts: call(fetchApi, '/posts'),
        timeout: call(delay, 1000)
      })
    
      if (posts)
        put({type: 'POSTS_RECEIVED', posts})
      else
        put({type: 'TIMEOUT_ERROR'})
    }
    

    race 的另一个有用的功能是,它会自动取消那些失败的 Effects。例如,假设我们有 2 个 UI 按钮:

    • 第一个用于在后台启动一个任务,这个任务运行在一个无限循环的 while(true) 中(例如:每 x 秒钟从服务器上同步一些数据)
    • 一旦该后台任务启动了,我们启用第二个按钮,这个按钮用于取消该任务。
    import { race, take, call } from 'redux-saga/effects'
    
    function* backgroundTask() {
      while (true) { ... }
    }
    
    function* watchStartBackgroundTask() {
      while (true) {
        yield take('START_BACKGROUND_TASK')
        yield race({
          task: call(backgroundTask),
          cancel: take('CANCEL_TASK')
        })
      }
    }
    
posted @ 2020-04-30 12:57  jaiodfjiaodf  阅读(601)  评论(0编辑  收藏  举报