与ES6发电机vs还原的传奇
现在有很多人在谈论最新的孩子,REDX-SAGA / REDX-SAGA。 它使用生成器函数来监听/分派操作。
在我将头围绕在它上面之前,我想知道使用redux-saga
优缺点,而不是下面的方法,我使用redux-thunk
是异步/等待的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...
在缩略语中,上面的例子相当于
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
)上调用LOGIN_REQUEST
。 即行动不断被推到thunk,并thwks无法控制什么时候停止处理这些行动。
在还原传奇中,发电机组将采取下一步行动。 即他们可以控制何时听取某个动作,何时不听。 在上面的例子中,流程指令被放置在while(true)
循环中,所以它会监听每个传入的动作,这有点模仿了thunk推送行为。
拉方法允许实施复杂的控制流程。 假设我们想要添加以下要求
处理注销用户操作
在第一次成功登录时,服务器会返回一个存储在expires_in
字段中的某个延迟过期的令牌。 我们必须在每个expires_in
毫秒后在后台刷新授权
考虑到当等待api调用的结果(初始登录或刷新)时,用户可以在其间注销。
你会怎样用thunk来实现它; 同时还为整个流程提供全面的测试覆盖率? 以下是萨加斯的外观:
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})
调用中被阻止,它也会被取消。 取消自动向下传播。
您可以找到上述流程的可运行演示
除了图书馆作者的全面回答之外,我还将在生产系统中加入我的经验。
Pro(使用传奇):
可测性。 随着call()返回一个纯粹的对象,测试传统非常容易。 测试thunk通常要求您在测试中包含一个mockStore。
redux-saga带有许多有用的帮助函数。 在我看来,传奇的概念是为你的应用创建某种背景工作者/线程,这是作为反应减少架构中的缺失部分(actionCreators和reducer必须是纯函数)。这导致下一点。
萨加斯提供独立的地方来处理所有的副作用。 根据我的经验,修改和管理通常比thunk操作更容易。
缺点:
生成器语法。
很多要学习的概念。
API稳定性。 看起来还有传奇还在增加功能(例如频道?),而社区并不大。 如果图书馆有一天会进行非向后兼容的更新,那么有一个问题。
我只想从我的个人经历中添加一些评论(同时使用传说和thunk):
萨加斯是伟大的测试:
萨加斯更强大。 所有你可以在一个thunk的动作创造者中做的事情,你也可以在一个传奇中做,但反之亦然(或者至少不容易)。 例如:
take
) cancel
, takeLatest
, race
) take
, takeEvery
,...) 萨加斯还提供了其他有用的功能,它概括了一些常见的应用程序模式:
channels
(例如websockets) fork
, spawn
) 萨加斯是伟大而强大的工具。 然而,权力是责任。 当您的应用程序增长时,您可以通过计算谁在等待要发送的操作,或者在发送某个操作时发生的所有事情,从而轻松地找到它。 另一方面,thunk更简单,更容易推理。 选择一个或另一个取决于诸如项目的类型和大小,项目必须处理什么类型的副作用或开发团队偏好等多个方面。 无论如何,只要保持你的应用程序简单和可预测。
链接地址: http://www.djcxy.com/p/59049.html