欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

redux-saga框架使用详解及Demo教程

程序员文章站 2022-11-09 22:06:04
redux-saga框架使用详解及demo教程 redux-saga 官方地址 https://leonshi.com/redux-saga-in-chinese/index.html demo运行...

redux-saga框架使用详解及demo教程

redux-saga 官方地址

https://leonshi.com/redux-saga-in-chinese/index.html

demo运行效果图

todolist

redux-saga框架使用详解及Demo教程

counterapp

redux-saga框架使用详解及Demo教程

示例demo地址

redux-saga-demo作者还是按照以前的风格,提供了两个不同的版本,简单的 counterapp, 稍复杂的 todolist

counterapp

https://github.com/guangqiang-liu/redux-saga-counterapp

todolist

https://github.com/guangqiang-liu/redux-saga-todolistdemo

什么是redux-saga

redux-saga 是一个用于管理 redux 应用异步操作的中间件(又称异步 action)。 redux-saga 通过创建 sagas 将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件。

这意味着应用的逻辑会存在两个地方:

reducers 负责处理 action 的 state 更新

sagas 负责协调那些复杂或异步的操作

sagas是通过generator函数来创建的,如果有不熟悉 generator函数使用的,请查看阮老师对generator的介绍

sagas 不同于thunks,thunks 是在action被创建时调用,而 sagas只会在应用启动时调用(但初始启动的 sagas 可能会动态调用其他 sagas),sagas 可以被看作是在后台运行的进程,sagas 监听发起的action,然后决定基于这个 action来做什么:是发起一个异步调用(比如一个 fetch 请求),还是发起其他的action到store,甚至是调用其他的 sagas

在 redux-saga 的世界里,所有的任务都通用 yield effects 来完成(effect 可以看作是 redux-saga 的任务单元)。effects 都是简单的 javascript 对象,包含了要被 saga middleware 执行的信息(打个比方,你可以看到 redux action其实是一个个包含执行信息的对象), redux-saga 为各项任务提供了各种effect创建器,比如调用一个异步函数,发起一个action到store,启动一个后台任务或者等待一个满足某些条件的未来的 action

redux-saga框架核心api

一、saga 辅助函数

redux-saga提供了一些辅助函数,用来在一些特定的action 被发起到store时派生任务,下面我先来讲解两个辅助函数:takeevery 和 takelatest

takeevery

例如:每次点击 fetch 按钮时,我们发起一个 fetch_requested 的 action。 我们想通过启动一个任务从服务器获取一些数据,来处理这个action

首先我们创建一个将执行异步 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 函数可以使用下面的写法替换

function* watchfetchdata() {

   while(true){
     yield take('fetch_requested');
     yield fork(fetchdata);
   }
}
takelatest

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

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

import { takelatest } from 'redux-saga'

function* watchfetchdata() {
  yield* takelatest('fetch_requested', fetchdata)
}

和takeevery不同,在任何时刻 takelatest 只允许执行一个 fetchdata 任务,并且这个任务是最后被启动的那个,如果之前已经有一个任务在执行,那之前的这个任务会自动被取消

二、effect creators

redux-saga框架提供了很多创建effect的函数,下面我们就来简单的介绍下开发中最常用的几种

take(pattern) put(action) call(fn, …args) fork(fn, …args) select(selector, …args)

take(pattern)

take函数可以理解为监听未来的action,它创建了一个命令对象,告诉middleware等待一个特定的action, generator会暂停,直到一个与pattern匹配的action被发起,才会继续执行下面的语句,也就是说,take是一个阻塞的 effect

用法:

function* watchfetchdata() {
   while(true) {
     // 监听一个type为 'fetch_requested' 的action的执行,直到等到这个action被触发,才会接着执行下面的 yield fork(fetchdata)  语句
     yield take('fetch_requested');
     yield fork(fetchdata);
   }
}

put(action)

put函数是用来发送action的 effect,你可以简单的把它理解成为redux框架中的dispatch函数,当put一个action后,reducer中就会计算新的state并返回,注意: put 也是阻塞 effect

用法:

export function* toggleitemflow() {
    let list = []
    // 发送一个type为 'update_data' 的action,用来更新数据,参数为 `data:list`
    yield put({
      type: actiontypes.update_data,
      data: list
    })
}

call(fn, …args)

call函数你可以把它简单的理解为就是可以调用其他函数的函数,它命令 middleware 来调用fn 函数, args为函数的参数,注意: fn 函数可以是一个 generator 函数,也可以是一个返回 promise 的普通函数,call 函数也是阻塞 effect

用法:

export const delay = ms => new promise(resolve => settimeout(resolve, ms))

export function* removeitem() {
  try {
    // 这里call 函数就调用了 delay 函数,delay 函数为一个返回promise 的函数
    return yield call(delay, 500)
  } catch (err) {
    yield put({type: actiontypes.error})
  }
}

fork(fn, …args)

fork 函数和 call 函数很像,都是用来调用其他函数的,但是fork函数是非阻塞函数,也就是说,程序执行完 yield fork(fn, args) 这一行代码后,会立即接着执行下一行代码语句,而不会等待fn函数返回结果后,在执行下面的语句

用法:

import { fork } from 'redux-saga/effects'

export default function* rootsaga() {
  // 下面的四个 generator 函数会一次执行,不会阻塞执行
  yield fork(additemflow)
  yield fork(removeitemflow)
  yield fork(toggleitemflow)
  yield fork(modifyitem)
}

select(selector, …args)

select 函数是用来指示 middleware调用提供的选择器获取store上的state数据,你也可以简单的把它理解为redux框架中获取store上的 state数据一样的功能 :store.getstate()

用法:

export function* toggleitemflow() {
     // 通过 select effect 来获取 全局 state上的 `gettodolist` 中的 list
     let templist = yield select(state => state.gettodolist.list)
}

三、createsagamiddleware()

createsagamiddleware 函数是用来创建一个 redux 中间件,将 sagas 与 redux store 链接起来

sagas 中的每个函数都必须返回一个 generator 对象,middleware 会迭代这个 generator 并执行所有 yield 后的 effect(effect 可以看作是 redux-saga 的任务单元)

用法:

import {createstore, applymiddleware} from 'redux'
import createsagamiddleware from 'redux-saga'
import reducers from './reducers'
import rootsaga from './rootsaga'

// 创建一个saga中间件
const sagamiddleware = createsagamiddleware()

// 创建store
const store = createstore(
  reducers,
  将sagamiddleware 中间件传入到 applymiddleware 函数中
  applymiddleware(sagamiddleware)
)

// 动态执行saga,注意:run函数只能在store创建好之后调用
sagamiddleware.run(rootsaga)

export default store

四、middleware.run(sagas, …args)

动态执行sagas,用于applymiddleware阶段之后执行sagas

sagas: function: 一个 generator 函数 args: array: 提供给 saga 的参数 (除了 store 的 getstate 方法)

注意:动态执行saga语句 middleware.run(sagas) 必须要在store创建好之后才能执行,在 store 之前执行,程序会报错

以counterapp demo来看redux-saga具体使用方式

index.js

import react from 'react';
import reactdom from 'react-dom';
import {createstore, applymiddleware} from 'redux'
import createsagamiddleware from 'redux-saga'

import rootsaga from './sagas'
import counter from './counter'
import rootreducer from './reducers'

const sagamiddleware = createsagamiddleware()
let middlewares = []
middlewares.push(sagamiddleware)

const createstorewithmiddleware = applymiddleware(...middlewares)(createstore)
const store = createstorewithmiddleware(rootreducer)

sagamiddleware.run(rootsaga)

const action = type => store.dispatch({ type })

function render() {
  reactdom.render(
     action('increment')}
      ondecrement={() => action('decrement')}
      onincrementasync={() => action('increment_async')} />,
    document.getelementbyid('root')
  )
}

render()

store.subscribe(render)

sagas.js

import { put, call, take,fork } from 'redux-saga/effects';
import { takeevery, takelatest } from 'redux-saga'

export const delay = ms => new promise(resolve => settimeout(resolve, ms));

function* incrementasync() {
  // 延迟 1s 在执行 + 1操作
  yield call(delay, 1000);
  yield put({ type: 'increment' });
}

export default function* rootsaga() {
  // while(true){
  //   yield take('increment_async');
  //   yield fork(incrementasync);
  // }

  // 下面的写法与上面的写法上等效
  yield* takeevery("increment_async", incrementasync)
}

reducer.js

export default function counter(state = 0, action) {
  switch (action.type) {
    case 'increment':
      return state + 1
    case 'decrement':
      return state - 1
    case 'increment_async':
      return state
    default:
      return state
  }
}

从上面的代码结构可以看出,redux-saga的使用方式还是比较简单的,相比较之前的redux框架的counterapp,多了一个sagas的文件,reducers文件还是之前的使用方式