小编典典

在ES6生成器上使用redux-saga与在ES2017 async / await中使用redux-thunk的优缺点

reactjs

现在有很多关于redux镇上最新的孩子redux-saga / redux-saga的讨论。它使用生成器功能来侦听/调度动作。

在开始思考之前,我想知道使用优缺点的知识,redux-saga而不是下面使用redux-thunk异步/等待的方法。

组件可能看起来像这样,像往常一样调度动作。

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...

阅读 315

收藏
2020-07-22

共1个答案

小编典典

在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 });
  }
}

首先要注意的是,我们正在使用form调用api函数yield call(func, ...args)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
。即动作不断地 被推向 重击,重击无法控制何时停止处理这些动作。

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

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

  • 处理注销用户操作

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

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

您将如何以笨拙的方式实现这一目标?同时还提供整个流程的完整测试覆盖范围?Sagas的外观如下:

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后台任务。并且如果在通话过程authAndRefreshTokenOnExpiry中被阻止,则该call(authorize, {token})通话也会被取消。取消会自动向下传播。

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

2020-07-22