当前位置: 首页 > 知识库问答 >
问题:

使用ES6生成器的redux-saga与使用ES2017 Async/Await的redux-thunk的利弊

颛孙和颂
2023-03-14

现在有很多关于redux镇最新的孩子的讨论,Redux-Saga/Redux-Saga。它使用生成器函数来监听/调度操作。

在仔细考虑它之前,我想知道使用redux-saga而不是下面使用redux-thunk和Async/await的方法的利弊。

组件可能如下所示,像往常一样分派操作。

import { login } from 'redux/auth';

class LoginForm extends Component {

  onClick(e) {
    e.preventDefault();
    const { user, pass } = this.refs;
    this.props.dispatch(login(user.value, pass.value));
  }

  render() {
    return (<div>
        <input type="text" ref="user" />
        <input type="password" ref="pass" />
        <button onClick={::this.onClick}>Sign In</button>
    </div>);
  } 
}

export default connect((state) => ({}))(LoginForm);

然后我的行为如下所示:

// auth.js

import request from 'axios';
import { loadUserData } from './user';

// define constants
// define initial state
// export default reducer

export const login = (user, pass) => async (dispatch) => {
    try {
        dispatch({ type: LOGIN_REQUEST });
        let { data } = await request.post('/login', { user, pass });
        await dispatch(loadUserData(data.uid));
        dispatch({ type: LOGIN_SUCCESS, data });
    } catch(error) {
        dispatch({ type: LOGIN_ERROR, error });
    }
}

// more actions...
// user.js

import request from 'axios';

// define constants
// define initial state
// export default reducer

export const loadUserData = (uid) => async (dispatch) => {
    try {
        dispatch({ type: USERDATA_REQUEST });
        let { data } = await request.get(`/users/${uid}`);
        dispatch({ type: USERDATA_SUCCESS, data });
    } catch(error) {
        dispatch({ type: USERDATA_ERROR, error });
    }
}

// more actions...

共有1个答案

丁光华
2023-03-14

在redux-saga中,与上述示例等效的是

export function* loginSaga() {
  while(true) {
    const { user, pass } = yield take(LOGIN_REQUEST)
    try {
      let { data } = yield call(request.post, '/login', { user, pass });
      yield fork(loadUserData, data.uid);
      yield put({ type: LOGIN_SUCCESS, data });
    } catch(error) {
      yield put({ type: LOGIN_ERROR, error });
    }  
  }
}

export function* loadUserData(uid) {
  try {
    yield put({ type: USERDATA_REQUEST });
    let { data } = yield call(request.get, `/users/${uid}`);
    yield put({ type: USERDATA_SUCCESS, data });
  } catch(error) {
    yield put({ type: USERDATA_ERROR, error });
  }
}

首先要注意的是,我们使用yield call(func,...args)的形式调用api函数。call不执行效果,它只创建一个类似{type:'call',func,args}的普通对象。执行委托给redux-saga中间件,该中间件负责执行函数并用其结果恢复生成器。

主要优点是可以使用简单的相等性检查在Redux之外测试生成器

const iterator = loginSaga()

assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))

// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
  iterator.next(mockAction).value, 
  call(request.post, '/login', mockAction)
)

// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
  iterator.throw(mockError).value, 
  put({ type: LOGIN_ERROR, error: mockError })
)

注:我们只是通过将模拟数据注入迭代器的next方法来模拟api调用结果。模拟数据比模拟函数简单得多。

需要注意的第二件事是调用yield take(ACTION)。每个新动作(例如login_request)由动作创建者调用thunk。也就是说,动作不断地被推到thunks,thunks无法控制何时停止处理这些动作。

在redux-saga中,发电机拉动下一个动作。也就是说,他们可以控制什么时候听什么行动,什么时候不听。在上面的示例中,流指令被放置在while(true)循环中,因此它将监听每个传入的操作,这在某种程度上模仿了thunk push行为。

pull方法允许实现复杂的控制流。例如,假设我们要添加以下需求

>

  • 处理注销用户操作

    在第一次成功登录时,服务器返回一个在expires_in字段中存储的延迟后过期的令牌。我们必须在每个expires_in毫秒内在后台刷新授权

    考虑到当等待api调用的结果(初始登录或刷新)时,用户可能会在两者之间注销。

    你将如何用Thunks实现这一点;同时还为整个流提供完整的测试覆盖?以下是传奇故事的外观:

    function* authorize(credentials) {
      const token = yield call(api.authorize, credentials)
      yield put( login.success(token) )
      return token
    }
    
    function* authAndRefreshTokenOnExpiry(name, password) {
      let token = yield call(authorize, {name, password})
      while(true) {
        yield call(delay, token.expires_in)
        token = yield call(authorize, {token})
      }
    }
    
    function* watchAuth() {
      while(true) {
        try {
          const {name, password} = yield take(LOGIN_REQUEST)
    
          yield race([
            take(LOGOUT),
            call(authAndRefreshTokenOnExpiry, name, password)
          ])
    
          // user logged out, next while iteration will wait for the
          // next LOGIN_REQUEST action
    
        } catch(error) {
          yield put( login.error(error) )
        }
      }
    }
    

    在上面的示例中,我们使用race来表达并发需求。如果take(LOGOUT)赢得比赛(即用户单击了LOGOUT按钮)。比赛将自动取消AuthAndreFreshTokenOnExpiry后台任务。如果AuthAndreFreshTokenonExpiry调用(authorize,{token})调用中被阻塞,它也将被取消。取消自动向下传播。

    您可以找到上述流的可运行演示

  •  类似资料:
    • 问题内容: 现在有很多关于redux镇上最新的孩子redux-saga / redux-saga的讨论。它使用生成器功能来侦听/调度动作。 在开始思考之前,我想知道使用优缺点的知识,而不是下面使用异步/等待的方法。 组件可能看起来像这样,像往常一样调度动作。 然后我的动作如下所示: 问题答案: 在redux-saga中,上述示例的等效项是 首先要注意的是,我们正在使用form调用api函数。不执行

    • Redux Thunk和Redux Saga都是Redux的中间件。两者之间的区别是什么?如何确定何时使用Redux Thunk或Redux Saga?

    • 本文向大家介绍redux-saga和redux-thunk有什么本质的区别?相关面试题,主要包含被问及redux-saga和redux-thunk有什么本质的区别?时的应答技巧和注意事项,需要的朋友参考一下 saga 自己基本上完全弄了一套 asyc 的事件监听机制。虽然好的一方面是将来可以扩展成 worker 相关的模块,甚至可以做到 multiple threads 同时执行,但代码量大大增加

    • 我有一个redux saga设置,工作正常。我的一个分派任务是创建一个新订单,然后一旦创建了订单,我就想用更新后的状态做一些事情。 由于 createOrder 操作触发调用 API 的重订传奇,因此存在延迟,因此在我的函数 do 之前不会更新此 .props.user 命令某些内容被调用。我可以设置一个超时,但这似乎不是一个可持续的想法。 我已经阅读了有关Stack Overflow的类似问题,

    • 问题内容: 以下两种方法有什么区别? 什么时候需要使用fork,什么时候不需要? 问题答案: 通常,当传奇需要启动非阻塞任务时很有用。这里的非阻塞意味着:调用方启动任务并继续执行,而无需等待任务完成。 在许多情况下这可能有用,但主要有2种情况: 按逻辑域对Sagas进行分组 保留对任务的引用,以便能够取消/加入该任务 您的顶级传奇故事可能是第一个用例的示例。您可能会遇到类似: 其中可能包括了诸如:

    • 本文向大家介绍redux的thunk作用是什么?相关面试题,主要包含被问及redux的thunk作用是什么?时的应答技巧和注意事项,需要的朋友参考一下 redux-thunk的作用:不使用中间件的情况,action只能是个对象,thunk帮助我们异步请求数据给store