现在有很多人在谈论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...
2020年7月更新:
在过去的16个月里,React社区最显著的变化可能是React钩子。
根据我的观察,为了获得与功能组件和钩子更好的兼容性,项目(即使是那些大型项目)倾向于使用:
Hook + async thunk (Hook使一切都非常灵活,所以你可以将async thunk放在你想要的地方,并将其作为普通函数使用,例如,仍然在操作中编写thunk。ts然后使用dispatch()来触发坦克:https://stackoverflow.com/a/59991104/5256695),
useRequest,
GraphQL/Apollo useQuery useMutation
react-fetching-library
其他流行的数据获取/API调用库、工具、设计模式等
相比之下,redux-saga在大多数API调用的正常情况下并没有比上面的方法提供显著的好处,同时通过引入许多saga文件/生成器增加了项目的复杂性(也是因为redux-saga的最后一个版本v1.1.1是在2019年9月18日,这是很久以前的事了)。
但是redux-saga仍然提供了一些独特的功能,如赛车效果和并行请求。因此,如果您需要这些特殊功能,redux-saga仍然是一个不错的选择。
2019年3月原帖:
这是一些个人经历:
For coding style and readability, one of the most significant advantages of using redux-saga in the past is to avoid callback hell in redux-thunk — one does not need to use many nesting then/catch anymore. But now with the popularity of async/await thunk, one could also write async code in sync style when using redux-thunk, which may be regarded as an improvement in redux-thunk.
One may need to write much more boilerplate codes when using redux-saga, especially in Typescript. For example, if one wants to implement a fetch async function, the data and error handling could be directly performed in one thunk unit in action.js with one single FETCH action. But in redux-saga, one may need to define FETCH_START, FETCH_SUCCESS and FETCH_FAILURE actions and all their related type-checks, because one of the features in redux-saga is to use this kind of rich “token” mechanism to create effects and instruct redux store for easy testing. Of course one could write a saga without using these actions, but that would make it similar to a thunk.
In terms of the file structure, redux-saga seems to be more explicit in many cases. One could easily find an async related code in every sagas.ts, but in redux-thunk, one would need to see it in actions.
Easy testing may be another weighted feature in redux-saga. This is truly convenient. But one thing that needs to be clarified is that redux-saga “call” test would not perform actual API call in testing, thus one would need to specify the sample result for the steps which may be used after the API call. Therefore before writing in redux-saga, it would be better to plan a saga and its corresponding sagas.spec.ts in detail.
Redux-saga also provides many advanced features such as running tasks in parallel, concurrency helpers like takeLatest/takeEvery, fork/spawn, which are far more powerful than thunks.
In conclusion, personally, I would like to say: in many normal cases and small to medium size apps, go with async/await style redux-thunk. It would save you many boilerplate codes/actions/typedefs, and you would not need to switch around many different sagas.ts and maintain a specific sagas tree. But if you are developing a large app with much complex async logic and the need for features like concurrency/parallel pattern, or have a high demand for testing and maintenance (especially in test-driven development), redux-sagas would possibly save your life.
无论如何,redux-saga并不比redux本身更困难和复杂,而且它没有所谓的陡峭的学习曲线,因为它的核心概念和api非常有限。花少量的时间学习redux-saga可能在未来的某一天对你有益。
我最近加入了一个大量使用redux-saga的项目,因此也有兴趣了解更多关于saga方法的好处。
说实话,我还在找。读了这篇文章和许多喜欢它的人,“优点”是难以捉摸的。以上的答案似乎可以总结为:
可测试性(忽略实际的API调用),
很多辅助函数,
熟悉服务器端编码的开发人员。
许多其他的说法似乎过于乐观,误导或根本是错误的!我看到过许多不合理的说法,例如“坦克不能做X”。但是坦克是功能。如果一个函数不能做X,那么javascript也不能做X,所以saga也不能做X。
对我来说,缺点是:
confounding of concerns by using generator functions. Generators in JS return custom iterators. That is all. They do not have any special ability to handle async calls or to be cancellable. Any loop can have a break-out condition, any function can handle async requests, and any code can make use of a custom iterator. When people say thing like: generators have control when to listen for some action or generators are cancellable, but async calls are not then it creates confusion by implying that these qualities are inherent in - or even unique to - generator functions.
unclear use-cases: AFAIK the SAGA pattern is for handling concurrent transaction issues across services. Given that browsers are single-threaded, it is hard to see how concurrency presents a problem that Promise methods can't handle. BTW: it is also hard to see why that class of problem should ever be handled in the browser.
code traceability: By using redux middleware to turn dispatch into a kind of event-handling, Sagas dispatch actions that never reach the reducers, and so never get logged by Redux tools. While other libraries also do this, it is often unnecessarily complicated, given that browsers have event-handling built in. The advantage of the indirection is again elusive, when calling the saga directly would be more obvious.
如果这篇文章让我对传奇故事感到沮丧,那是因为我对传奇故事感到沮丧。它们似乎是一个寻找问题的伟大解决方案。国际海事组织。
坦克大战萨加斯
Redux- thunk和Redux- saga在一些重要的方面有所不同,它们都是Redux的中间件库(Redux中间件是通过dispatch()方法拦截进入商店的操作的代码)。
一个动作可以是任何东西,但是如果您遵循最佳实践,那么一个动作就是一个简单的javascript对象,其中包含一个类型字段,以及可选的有效负载、元和错误字段。如。
const loginRequest = {
type: 'LOGIN_REQUEST',
payload: {
name: 'admin',
password: '123',
}, };
Redux-Thunk
除了分派标准的动作,redux -坦克中间件还允许你分派特殊的函数,称为坦克。
坦克(在Redux中)通常有以下结构:
export const thunkName =
parameters =>
(dispatch, getState) => {
// Your application logic goes here
};
也就是说,一个thunk是一个(可选的)接受一些参数并返回另一个函数的函数。内部函数接受一个分派函数和一个getState函数——这两个函数都将由Redux-Thunk中间件提供。
Redux-Saga
Redux-Saga中间件允许您将复杂的应用程序逻辑表示为称为saga的纯函数。从测试的角度来看,纯函数是可取的,因为它们是可预测和可重复的,这使得它们相对容易测试。
saga是通过称为生成器函数的特殊函数实现的。这些是ES6 JavaScript的新特性。基本上,在任何看到yield语句的地方,执行都会在生成器中跳跃。将yield语句看作是导致生成器暂停并返回yield值的语句。稍后,调用者可以在yield之后的语句处恢复生成器。
生成器函数是这样定义的。注意function关键字后面的星号。
function* mySaga() {
// ...
}
一旦登录传奇注册到Redux-Saga。但是在第一行上的yield take将暂停saga,直到带有“LOGIN_REQUEST”类型的操作被分派到商店。一旦发生这种情况,执行将继续进行。
欲了解更多细节,请参阅本文。
我只是想从我的个人经验(使用sagas和thunk)中补充一些评论:
传奇故事很适合测试:
您不需要模拟用效果包装的函数
因此,测试是干净的,可读的,易于编写的
在使用saga时,动作创建者通常会返回简单的对象字面量。与thunk的承诺不同,它也更容易测试和断言。
传说更有力量。你在一个坦克动作创造者中可以做到的所有事情,你也可以在一个传奇中做到,但反之亦然(至少不容易)。例如:
等待一个/多个动作被分派(采取)
取消现有的例程(取消,taklatest, race)
多个例程可以监听相同的动作(take, takeEvery,…)
Sagas还提供了其他有用的功能,它概括了一些常见的应用程序模式:
监听外部事件源的通道(例如websockets)
Fork模型(Fork, spawn)
节气门
...
Sagas are great and powerful tool. However with the power comes responsibility. When your application grows you can get easily lost by figuring out who is waiting for the action to be dispatched, or what everything happens when some action is being dispatched. On the other hand thunk is simpler and easier to reason about. Choosing one or another depends on many aspects like type and size of the project, what types of side effect your project must handle or dev team preference. In any case just keep your application simple and predictable.