ham*_*son 449 javascript reactjs redux redux-thunk redux-saga
现在有很多关于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);
Run Code Online (Sandbox Code Playgroud)
然后我的行为看起来像这样:
// 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...
Run Code Online (Sandbox Code Playgroud)
// 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...
Run Code Online (Sandbox Code Playgroud)
Yas*_*afi 437
在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 });
}
}
Run Code Online (Sandbox Code Playgroud)
首先要注意的是我们使用表单调用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 })
)
Run Code Online (Sandbox Code Playgroud)
注意我们只是通过将模拟数据注入next
迭代器的方法来模拟api调用结果.模拟数据比模拟函数更简单.
要注意的第二件事是打电话给yield take(ACTION)
.动作创建者在每个新动作(例如LOGIN_REQUEST
)上调用Thunk .即动作不断被推到thunk,并且thunk无法控制何时停止处理这些动作.
在终极版,传奇,发电机拉的下一个动作.也就是说,他们可以控制什么时候听某些动作,什么时候不听.在上面的示例中,流指令被放置在while(true)
循环内部,因此它将侦听每个传入的操作,这有点模仿了thunk推送行为.
拉方法允许实现复杂的控制流程.例如,假设我们要添加以下要求
处理LOGOUT用户操作
在第一次成功登录时,服务器返回一个令牌,该令牌在存储在expires_in
字段中的某些延迟中到期.我们必须在每expires_in
毫秒后在后台刷新授权
考虑到在等待api调用的结果(初始登录或刷新)时,用户可以在中间注销.
你如何用thunk实现它; 同时还为整个流程提供全面的测试覆盖?以下是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) )
}
}
}
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,我们使用表达我们的并发性要求race
.如果take(LOGOUT)
赢得比赛(即用户点击了退出按钮).比赛将自动取消authAndRefreshTokenOnExpiry
后台任务.如果在通话authAndRefreshTokenOnExpiry
中途被阻止,call(authorize, {token})
它也将被取消.取消自动向下传播.
yjc*_*y12 89
除了图书馆作者的相当全面的答案之外,我将在生产系统中添加使用saga的经验.
Pro(使用传奇):
可测性.测试sagas非常容易,因为call()返回一个纯对象.测试thunk通常需要在测试中包含mockStore.
redux-saga附带了许多有关任务的有用辅助函数.在我看来,那传奇的概念是创造某种背景工人/线程您的应用程式,在反应Redux的架构(actionCreators和减速器必须是纯功能).这会导致下一个点充当一个缺失.
Sagas提供独立的处理所有副作用的地方.根据我的经验,修改和管理通常比thunk动作更容易.
缺点:
生成器语法.
要学习很多概念.
API稳定性.似乎redux-saga仍在添加功能(例如频道?),社区不是那么大.如果库有一天会进行非向后兼容的更新,则会引起关注.
Jon*_*han 29
2020 年 7 月更新:
在过去的 16 个月中,React 社区中最显着的变化可能是React hooks。
根据我的观察,为了获得与功能组件和钩子更好的兼容性,项目(即使是那些大的)会倾向于使用:
useQuery
useMutation
相比之下,redux-saga
目前与上述方法相比,在大多数 API 调用的正常情况下并没有真正提供显着的好处,同时通过引入许多 saga 文件/生成器增加了项目的复杂性(也因为最后一个版本 v1.1.1redux-saga
是在 9 月 18 日发布的) 2019 年,这是很久以前)。
但是,仍然redux-saga
提供了一些独特的功能,例如竞速效果和并行请求。因此,如果您需要这些特殊功能,redux-saga
仍然是一个不错的选择。
2019 年 3 月的原帖:
只是一些个人经验:
对于编码风格和可读性,过去使用 redux-saga 最显着的优势之一是避免了 redux-thunk 中的回调地狱?——不再需要使用许多嵌套 then/catch。但是现在随着 async/await thunk 的流行,在使用 redux-thunk 的时候也可以写出同步风格的 async 代码,这也算是对 redux-thunk 的一种改进吧。
使用 redux-saga 时可能需要编写更多样板代码,尤其是在 Typescript 中。例如,如果想要实现一个 fetch async 功能,数据和错误处理可以直接在 action.js 中的一个 thunk 单元中通过一个 FETCH 操作来执行。但是在 redux-saga 中,可能需要定义 FETCH_START、FETCH_SUCCESS 和 FETCH_FAILURE 动作及其所有相关的类型检查,因为 redux-saga 中的一个特性就是使用这种丰富的“令牌”机制来创建效果和指示redux store 便于测试。当然,人们可以在不使用这些动作的情况下编写 saga,但这会使其类似于 thunk。
在文件结构方面,redux-saga 在很多情况下似乎更加明确。人们可以很容易地在每个 sagas.ts 中找到与异步相关的代码,但在 redux-thunk 中,人们需要在操作中看到它。
轻松测试可能是 redux-saga 中的另一个加权特性。这真的很方便。但是需要澄清的一点是,redux-saga“调用”测试在测试中不会执行实际的API调用,因此需要为API调用后可能使用的步骤指定示例结果。因此,在使用 redux-saga 编写之前,最好详细规划一个 saga 及其对应的 sagas.spec.ts。
Redux-saga 还提供了许多高级功能,例如并行运行任务,并发助手如 takeLatest/takeEvery、fork/spawn,这些功能远比 thunk 强大。
总之,就个人而言,我想说:在许多正常情况下和中小型应用程序中,使用 async/await 风格的 redux-thunk。它将为您节省许多样板代码/操作/typedef,并且您不需要切换许多不同的 sagas.ts 并维护特定的 sagas 树。但是,如果您正在开发一个具有非常复杂的异步逻辑并且需要并发/并行模式等功能的大型应用程序,或者对测试和维护有很高的需求(尤其是在测试驱动开发中),那么 redux-sagas 可能会挽救您的生命.
无论如何,redux-saga 并不比 redux 本身更困难和复杂,并且它没有所谓的陡峭学习曲线,因为它的核心概念和 API 非常有限。花少量时间学习 redux-saga 可能在未来的某一天让自己受益。
mad*_*ox2 26
我只想从我的个人经历中添加一些评论(使用传奇和thunk):
Sagas非常适合测试:
Sagas更强大.您可以在一个thunk的动作创建者中执行所有操作,您也可以在一个传奇中执行,但反之亦然(或者至少不容易).例如:
take
)cancel
,takeLatest
,race
)take
,takeEvery
,...)Sagas还提供其他有用的功能,它们概括了一些常见的应用程序模式:
channels
监听外部事件源(例如websockets)fork
,spawn
)Sagas是伟大而强大的工具.然而,权力来自责任.当您的应用程序增长时,您可以通过确定谁正在等待调度操作,或者在调度某些操作时发生的一切情况而轻易丢失.另一方面,thunk更简单,更容易推理.选择一个或另一个取决于许多方面,如项目的类型和大小,项目必须处理的副作用类型或开发团队偏好.在任何情况下,只需保持您的应用程序简单和可预测.
根据我的经验,回顾了几个不同的大型 React/Redux 项目后,Sagas 为开发人员提供了一种更结构化的代码编写方式,更容易测试且更难出错。
是的,一开始有点奇怪,但大多数开发人员在一天之内就对它有足够的了解。我总是告诉人们不要担心从什么yield
开始,一旦你写了几个测试,它就会出现在你身上。
我见过几个项目,其中 thunk 被视为来自 MVC 模式的控制器,这很快就变成了一个无法维护的混乱。
我的建议是在需要 A 触发与单个事件相关的 B 类型内容的地方使用 Sagas。对于可以跨越多个动作的任何事情,我发现编写自定义中间件并使用 FSA 动作的元属性来触发它更简单。
Thunks 与 Sagas
Redux-Thunk
并且Redux-Saga
在一些重要方面有所不同,两者都是 Redux 的中间件库(Redux 中间件是拦截通过dispatch() 方法进入存储的操作的代码)。
操作实际上可以是任何内容,但如果您遵循最佳实践,操作就是带有类型字段以及可选负载、元和错误字段的纯 JavaScript 对象。例如
const loginRequest = {
type: 'LOGIN_REQUEST',
payload: {
name: 'admin',
password: '123',
}, };
Run Code Online (Sandbox Code Playgroud)
Redux-Thunk
除了调度标准操作之外,Redux-Thunk
中间件还允许您调度特殊函数,称为thunks
.
Thunk(在 Redux 中)通常具有以下结构:
export const thunkName =
parameters =>
(dispatch, getState) => {
// Your application logic goes here
};
Run Code Online (Sandbox Code Playgroud)
也就是说,athunk
是一个函数,它(可选)接受一些参数并返回另一个函数。内部函数采用 adispatch function
和 agetState
函数——这两个函数都将由中间件提供Redux-Thunk
。
Redux-Saga
Redux-Saga
中间件允许您将复杂的应用程序逻辑表达为称为 sagas 的纯函数。从测试的角度来看,纯函数是可取的,因为它们是可预测和可重复的,这使得它们相对容易测试。
Sagas 是通过称为生成器函数的特殊函数来实现的。这些都是 的新功能ES6 JavaScript
。基本上,只要你看到yield 语句,执行就会跳进跳出生成器。将yield
语句视为导致生成器暂停并返回生成值的语句。稍后,调用者可以在yield
.
生成器函数是这样定义的。请注意 function 关键字后面的星号。
function* mySaga() {
// ...
}
Run Code Online (Sandbox Code Playgroud)
一旦登录传奇注册到Redux-Saga
. 但是yield
第一行的操作将暂停传奇,直到将具有类型的操作'LOGIN_REQUEST'
分派到商店。一旦发生这种情况,执行将继续。
归档时间: |
|
查看次数: |
99108 次 |
最近记录: |