现在有很多关于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...

推荐答案

在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.i、 e.行动持续pushed到thunks,thunks无法控制何时停止处理这些行动.

在redux传奇中,下一个动作是什么.i、 e.他们可以控制何时倾听,何时不倾听.在上面的例子中,流指令被放置在一个while(true)循环中,因此它将监听每个传入的动作,这在某种程度上模拟了thunk推送行为.

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

  • 处理注销用户操作

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

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

您将如何使用Tunks实现这一点;同时还为整个流程提供完整的测试覆盖范围?以下是佐贺的情况:

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)赢得比赛(即用户点击注销按钮).比赛将自动取消authAndRefreshTokenOnExpiry后台任务.如果authAndRefreshTokenOnExpirycall(authorize, {token})呼叫中被阻塞,它也将被取消.取消会自动向下传播.

你可以找到一辆runnable demo of the above flow

Javascript相关问答推荐

为什么JavaScript双边字符串文字插值不是二次的?

如何解决这个未能在响应上执行json:body stream已读问题?

是什么原因导致此Angular 16电影应用程序中因类型错误而不存在属性?

React:未调用useState变量在调试器的事件处理程序中不可用

Chart.js V4切换图表中的每个条,同时每个条下有不同的标签.怎么做?

当运行d3示例代码时,没有显示任何内容

CheckBox作为Vue3中的一个组件

对路由DOM嵌套路由作出react

在Java中寻找三次Bezier曲线上的点及其Angular

有条件重定向到移动子域

使用getBorbingClientRect()更改绝对元素位置

Reaction组件在本应被设置隐藏时仍显示

在画布中调整边上反弹框的大小失败

如何在HTMX提示符中设置默认值?

删除加载页面时不存在的元素(JavaScript)

无法读取未定义的属性(正在读取合并)-react RTK

FileReader()不能处理Firefox和GiB文件

如果对象中的字段等于某个值,则从数组列表中删除对象

在对象的嵌套数组中添加两个属性

与find()方法一起使用时,Mongoose中的$or运算符没有提供所有必需的数据