如果您对redux-saga学习进阶篇一和redux-saga原理感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解redux-saga学习进阶篇一的各种细节,并对redux-saga原理进行深入
如果您对redux-saga 学习进阶篇一和redux-saga原理感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解redux-saga 学习进阶篇一的各种细节,并对redux-saga原理进行深入的分析,此外还有关于2021 年应该用什么 redux-saga 或 redux-thunk?、Api 调用 redux-saga 导致 redux-devtools-extension 崩溃、react - redux/react-redux/redux-saga/mobx、React 实践项目 (二) redux + immutable + redux-saga的实用技巧。
本文目录一览:- redux-saga 学习进阶篇一(redux-saga原理)
- 2021 年应该用什么 redux-saga 或 redux-thunk?
- Api 调用 redux-saga 导致 redux-devtools-extension 崩溃
- react - redux/react-redux/redux-saga/mobx
- React 实践项目 (二) redux + immutable + redux-saga
redux-saga 学习进阶篇一(redux-saga原理)
今日学习目录
一、错误处理 try/catch
二、 take/takeEvery 监听未来的 action
三、无阻塞调用 fork、canceled
四、同时执行多个任务
一、错误处理 try/catch
我们可以使用熟悉的 try/catch 语法在 Saga 中捕获错误。
import Api from ''./path/to/api''
import { call, put } from ''redux-saga/effects''
// ...
function* fetchProducts() {
try {
const products = yield call(Api.fetch, ''/products'')
yield put({ type: ''PRODUCTS_RECEIVED'', products })
}
catch(error) {
yield put({ type: ''PRODUCTS_REQUEST_FAILED'', error })
}
}
为了测试故障案例,我们将使用 Generator 的 throw 方法。
// 在这个案例中,我们传递一个模拟的 error 对象给 throw,
// 这会引发 Generator 中断当前的执行流并执行捕获区块(catch block)。
import { call, put } from ''redux-saga/effects''
import Api from ''...''
const iterator = fetchProducts()
// 期望一个 call 指令
assert.deepEqual(
iterator.next().value,
call(Api.fetch, ''/products''),
"fetchProducts should yield an Effect call(Api.fetch, ''./products'')"
)
// 创建一个模拟的 error 对象
const error = {}
// 期望一个 dispatch 指令
assert.deepEqual(
iterator.throw(error).value,
put({ type: ''PRODUCTS_REQUEST_FAILED'', error }),
"fetchProducts should yield an Effect put({ type: ''PRODUCTS_REQUEST_FAILED'', error })"
)
当然了,你并不一定得在 try/catch 区块中处理错误,你也可以让你的 API 服务返回一个正常的含有错误标识的值。例如, 你可以捕捉 Promise 的拒绝操作,并将它们映射到一个错误字段对象。
import Api from ''./path/to/api''
import { call, put } from ''redux-saga/effects''
function fetchProductsApi() {
return Api.fetch(''/products'')
.then(response => ({ response }))
.catch(error => ({ error }))
}
function* fetchProducts() {
const { response, error } = yield call(fetchProductsApi)
if (response)
yield put({ type: ''PRODUCTS_RECEIVED'', products: response })
else
yield
二、 take/takeEvery 监听未来的 action
// 1. 使用 takeEvery(''*'')(使用通配符 * 模式),我们就能捕获发起的所有类型的 action。
import { select, takeEvery } from ''redux-saga/effects''
function* watchAndLog() {
yield takeEvery(''*'', function* logger(action) {
const state = yield select()
console.log(''action'', action)
console.log(''state after'', state)
})
}
// 2. 使用 take Effect 来实现和上面相同的功能:
import { select, take } from ''redux-saga/effects''
function* watchAndLog() {
while (true) {
const action = yield take(''*'')
const state = yield select()
console.log(''action'', action)
console.log(''state after'', state)
}
}
take 就像我们更早之前看到的 call 和 put。它创建另一个命令对象,告诉 middleware 等待一个特定的 action。 正如在 call Effect 的情况中,middleware 会暂停 Generator,直到返回的 Promise 被 resolve。 在 take 的情况中,它将会暂停 Generator 直到一个匹配的 action 被发起了。 在以上的例子中,watchAndLog 处于暂停状态,直到任意的一个 action 被发起。
使用 take 组织代码有一个小问题。在 takeEvery 的情况中,被调用的任务无法控制何时被调用, 它们将在每次 action 被匹配时一遍又一遍地被调用。并且它们也无法控制何时停止监听。
// 主动拉取 action 的另一个好处是我们可以使用熟悉的同步风格来描述我们的控制流。
function* loginFlow() {
while (true) {
yield take(''LOGIN'')
// ... perform the login logic
yield take(''LOGOUT'')
// ... perform the logout logic
}
}
take Effect 让我们可以在一个集中的地方更好地去描述一个非常规的流程。
三、无阻塞调用 fork、canceled
在上面这个例子中,使用已拥有的 effects 实现上述
// 下面代码存在一个小问题
import { take, call, put } from ''redux-saga/effects''
import Api from ''...''
function* authorize(user, password) {
try {
const token = yield call(Api.authorize, user, password)
yield put({type: ''LOGIN_SUCCESS'', token})
return token
} catch(error) {
yield put({type: ''LOGIN_ERROR'', error})
}
}
function* loginFlow() {
while(true) {
const {user, password} = yield take(''LOGIN_REQUEST'')
const token = yield call(authorize, user, password)
if(token) {
yield call(Api.storeItem({token}))
yield take(''LOGOUT'')
yield call(Api.clearItem(''token''))
}
}
}
存在的一个问题是:
当 loginFlow 在 authorize 中被阻塞了,最终发生在开始调用和收到响应之间的 LOGOUT 将会被错过, 因为那时 loginFlow 还没有执行 yield take (''LOGOUT'')。
所以为了让 loginFlow 不错过一个并发的 LOGOUT,我们不应该使用 call 调用 authorize 任务,而应该使用 fork。并且,自从 authorize 的 action 在后台启动之后,我们获取不到 token 的结果(因为我们不应该等待它)。 所以我们需要将 token 存储操作移到 authorize 任务内部。
import { fork, call, take, put } from ''redux-saga/effects''
import Api from ''...''
function* authorize(user, password) {
try {
const token = yield call(Api.authorize, user, password)
yield put({type: ''LOGIN_SUCCESS'', token})
} catch(error) {
yield put({type: ''LOGIN_ERROR'', error})
}
}
function* loginFlow() {
while(true) {
const {user, password} = yield take(''LOGIN_REQUEST'')
yield fork(authorize, user, password)
yield take([''LOGOUT'', ''LOGIN_ERROR''])
yield call(Api.clearItem(''token''))
}
}
但是还没完。
如果我们在 API 调用期间收到一个 LOGOUT action,我们必须要 取消 authorize 处理进程,否则将有 2 个并发的任务, 并且 authorize 任务将会继续运行,并在成功的响应(或失败的响应)返回后发起一个 LOGIN_SUCCESS action(或一个 LOGIN_ERROR action),而这将导致状态不一致。
// 为了取消 fork 任务,我们可以使用一个指定的 Effect cancel。
import { take, put, call, fork, cancel } from ''redux-saga/effects''
// ...
function* loginFlow() {
while(true) {
const {user, password} = yield take(''LOGIN_REQUEST'')
// fork return a Task object
const task = yield fork(authorize, user, password)
const action = yield take([''LOGOUT'', ''LOGIN_ERROR''])
if(action.type === ''LOGOUT'')
yield cancel(task)
yield call(Api.clearItem(''token''))
}
}
假设在我们收到一个 LOGIN_REQUEST action 时,我们在 reducer 中设置了一些 isLoginPending 标识为 true,以便可以在界面上显示一些消息或者旋转 loading。 如果此时我们在 Api 调用期间收到一个 LOGOUTaction,并通过 杀死它(即任务被立即停止)简单粗暴地中止任务。 那我们可能又以不一致的状态结束了。因为 isLoginPending 仍然是 true,而 reducer 还在等待一个结果 action(LOGIN_SUCCESS 或 LOGIN_ERROR)。
// 幸运的是,cancel Effect 不会粗暴地结束我们的 authorize 任务,
// 相反它会给予一个机会执行清理的逻辑。
import { take, call, put, cancelled } from ''redux-saga/effects''
import Api from ''...''
function* authorize(user, password) {
try {
const token = yield call(Api.authorize, user, password)
yield put({type: ''LOGIN_SUCCESS'', token})
yield call(Api.storeItem, {token})
return token
} catch(error) {
yield put({type: ''LOGIN_ERROR'', error})
} finally {
if (yield cancelled()) {
// ... put special cancellation handling code here
}
}
}
四、同时执行多个任务
// 错误写法,effects 将按照顺序执行
const users = yield call(fetch, ''/users''),
repos = yield call(fetch, ''/repos'')
// 由于第二个 effect 将会在第一个 call 执行完毕才开始。所以我们需要这样写:
import { call } from ''redux-saga/effects''
// 正确写法, effects 将会同步执行
const [users, repos] = yield [
call(fetch, ''/users''),
call(fetch, ''/repos'')
]
2021 年应该用什么 redux-saga 或 redux-thunk?
如何解决2021 年应该用什么 redux-saga 或 redux-thunk??
我应该使用哪一种?有关更多上下文,我使用 redux-toolkit。感谢您提供有助于了解哪种工具更适合的建议
解决方法
对于大多数异步逻辑,Redux 样式指南非常清楚recommends thunks over slices。如果您有非常复杂和交织的逻辑,saga 可能是有意义的,但大多数应用程序没有这种逻辑。
此外,您可能应该尝试使用 RTK-Query,这会大大减少您对异步任务中任一中间件的需求。
Api 调用 redux-saga 导致 redux-devtools-extension 崩溃
如何解决Api 调用 redux-saga 导致 redux-devtools-extension 崩溃?
我正在尝试使用 axios 和 redux-saga 进行一些 api 调用。这是我以前从未做过的事情,redux devtools 通常可以很好地处理这个问题。出于某种原因,在我正在处理的当前应用程序中,任何触发 saga 的操作,然后进行 api 调用,似乎都会使我的 redux-devtools-extension 崩溃。我知道 redux-devtools-extension 总是有点问题,但我无法弄清楚为什么 这些 操作可能会导致它崩溃。这是一个典型的传奇:
function* serverRefresh(): Generator {
try {
yield call(axios.get,"/api/restart"); // <------ crashes devtools extension
} catch (e) {
console.log(e);
}
}
function* watchServerRefresh(): Generator {
yield takeEvery(ActionTypes.RESTART_SERVER,serverRefresh);
}
export function* serverSagas(): Generator {
yield all([fork(watchServerRefresh)]);
}
请注意,如果我注释掉 axios 调用,则扩展可以正常工作,正确注册操作。其他不是来自传奇的动作没有问题。从 axios 切换到 fetch 无济于事。这里没有太多可能导致无限循环或触发 cpu 过载的逻辑——它是一个简单的 api 调用。
以下是我设置商店和 devtools 扩展的方法:
function* rootSaga(): Generator {
yield all([fork(serverSagas),fork(campaignSagas)]);
}
const sagaMiddleware = createSagaMiddleware();
const rootReducer = combineReducers({ ...reducers });
export const store = createStore(
rootReducer,composeWithDevTools(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);
为什么非 saga 操作或 saga 非 api 调用操作可以正常工作,但几乎所有使用 fetch 或 axios 进行的 api 调用都会导致 devtools 冻结和崩溃?
解决方法
我在问题中没有提到的问题是,redux 存储会在操作 X 之后发生的任何操作上冻结。操作 X 是一个操作,它存储对存储中循环对象的引用。 (对于那些有兴趣的人来说,它是一个传单地图实例。)虽然该操作没有使 devtools 崩溃,而且我什至可以使用 devtools 检查传单地图实例圆形对象,但之后发生的任何操作 该圆形对象存储在商店中。所以这真的与传奇或 api 调用无关,而是与在存储中保持循环对象有关,我们都知道这是一个 bad idea。我想我可以将该对象移至反应上下文对象。
react - redux/react-redux/redux-saga/mobx
redux 用法
1、安装 redux
yarn add redux
2、创建一个store.js文件
import { createStore } from ''redux''
// 创建reducer
function couter (state, action) {
switch(action.type){
case ''INCREMENT'': return state + 1;
case ''DRCREMENT'': return state - 1;
default: return state;
}
}
// 创建 store
const store = createStore(couter)
// 暴露 store
export default store
3、index.js
import React from ''react'';
import ReactDOM from ''react-dom'';
import ''./index.css'';
import App from ''./App'';
import * as serviceWorker from ''./serviceWorker'';
import store from ''./store''
function render (){
ReactDOM.render(
<App />,
document.getElementById(''root'')
);
}
// 调整render的方式
render()
// 订阅 store
store.subscribe(render)
serviceWorker.unregister();
4、TestStore.js 使用 store
import React, { Component } from ''react''
import { Button } from ''antd''
import store from ''./store''
export default class TestStore extends Component {
increment(){
store.dispatch({
type: ''INCREMENT''
})
}
drcrement(){
store.dispatch({
type: ''DRCREMENT''
})
}
render() {
return (
<div>
<h3>{store.getState()}</h3>
<Button onClick={this.increment}>+1</Button>
<Button onClick={this.drcrement}>-1</Button>
</div>
)
}
}
5、在app.js里面引入TestStore
import React, { Component } from ''react'';
import ''./App.css''
import TestStore from ''./TestStore''
class App extends Component {
render() {
// let self = this
return (
<div>
<TestStore/>
</div>
);
}
}
export default App;
以上是 redux
的用法,显而易见你每次对 store
进行 dispatch(action)
都会触发 subscribe
注册的函数调用比较麻烦,其实就类似于 vue
的 computed
。
每个应用只能有一个state
,但是可以拥有多个reducers
,多个reducers可以使用 combineReducers
React 实践项目 (二) redux + immutable + redux-saga
React在Github上已经有接近70000的 star 数了,是目前最热门的前端框架。而我学习React也有一段时间了,现在就开始用 React+Redux 进行实战!
React 实践项目 (一)
本次实践代码
部署好的网址
上回说到用React写了一个带Header的首页,我们这次实践就使用Redux进行状态管理
Rudex
应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。
惟一改变 state 的办法是触发 action,一个描述发生什么的对象。
为了描述 action 如何改变 state 树,你需要编写 reducers。
我们接下来开始开始进行登陆与注册的状态管理
首先在 src
目录下创建 redux
文件夹,目录如下
digag ├── README.md ├── node_modules ├── package.json ├── .gitignore ├── public │ └── favicon.ico │ └── index.html │ └── manifest.json └── src └── components └── Index └── Header.js └── LoginDialog.js └── RegisterDialog.js └── containers └── App └── App.js └── App.css └── redux └── action └── users.js └── reducer └── auth.js └── users.js └── sagas └── api.js └── sagas.js └── selectors.js.js └── users.js └── store └── store.js └── App.test.js └── index.css └── index.js └── logo.svg └── registerServiceWorker.js
代码可从此获取
记得在 package.json
中更新依赖
接下来我会开始解释关键代码
action
action/users.js
/* * action 类型 */ export const REGISTER_USER = 'REGISTER_USER'; // 省略其他action 类型 /* * action 创建函数 */ export const registeraction = (newUser) => { return{ type:REGISTER_USER,data: newUser,} }; // 省略其他 action 创建函数
reducer
reducer/users.js
//Immutable Data 就是一旦创建,就不能再被更改的数据。 //对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。 import Immutable from 'immutable'; //从 action 导入需要的 action 类型 import {REGISTER_USER,REGISTER_USER_SUCCESS,REGISTER_USER_FAILURE} from '../action/users'; // 初始化状态 const initialState = Immutable.fromJS({ newUser: null,error: null,saveSuccess: false,}); // reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。 export const users = (state = initialState,action = {}) => { switch (action.type) { // 判断 action 类型 case REGISTER_USER: return state.merge({ // 更新状态 'newUser': action.data,'saveSuccess': false,'error': null,}); case REGISTER_USER_SUCCESS: return state.set('saveSuccess',action.data); case REGISTER_USER_FAILURE: return state.set('error',action.data); default: return state } };
store
store/store.js
import {createStore,combineReducers,applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga' import * as reducer from '../reducer/users'; import rootSaga from '../sagas/sagas'; const sagaMiddleware = createSagaMiddleware(); const store = createStore( combineReducers(reducer),applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(rootSaga); export default store;
然后在入口文件使用 store
src/index.js
import {Provider} from 'react-redux'; import store from './redux/store/store'; // 省略其他 ReactDOM.render( <Provider store={store}> <App /> </Provider>,document.getElementById('root') );
在 App.js 中获取 action 和 状态
import {registeraction,loginAction} from '../../redux/action/users'; import {connect} from "react-redux"; import {bindActionCreators} from "redux"; //省略其他 class App extends Component { render(){ return( <div className="App"> //省略 </div> ) } } export default connect( (state) => { // 获取状态 state.users 是指 reducer/users.js 文件中导出的 users // 可以 `console.log(state);` 查看状态树 return { users: state.users } },(dispatch) => { return { // 创建action registeractions: bindActionCreators(registeraction,dispatch),loginActions: bindActionCreators(loginAction,} })(App); // 在App 组件的props里就有 this.props.users this.props.registeractions this.props.loginActions 了 // 需要注意的是这里this.props.users是Immutable 对象,取值需要用this.props.users.get('newUser') // 也可在 reducer 里改用 js 普通对象
装饰器版本:
需要在Babel中开启装饰器
装饰器插件babel-plugin-transform-decorators-legacy
@connect( (state) => { console.log(state); return ({ users: state.users,}); },{registeractions: registeraction,loginActions: loginAction} )
最后把 registeractions
传给RegisterDialog子组件,
src/components/Index/RegisterDialog.js
// 省略其他代码 handleSubmit = (e) => { e.preventDefault(); // 验证表单数据 this.refs.user.validate((valid) => { if (valid) { // this.state.user 为表单收集的 用户注册数据 this.props.registeractions(this.state.user); this.setState({loading: true}); } }); };
流程是:
-
调用 action
`this.props.registeractions(this.state.user);` 返回action 为
{ type:REGISTER_USER,data: this.state.user,}
reducer 根据action类型更新状态
switch (action.type) { case REGISTER_USER: return state.merge({ 'newUser': action.data,}); //省略其他代码
这时我们的store里的状态 newUser
就被更新为 注册弹窗里收集的数据
到这里都还是同步的action,而注册是一个异步的操作。
下篇文章会介绍如何使用 redux-saga 进行异步操作。
redux-saga 已经在使用了,有兴趣的可以自行查看代码理解。
记得点star:)
项目代码地址:https://github.com/DigAg/diga...
vue2版项目代码地址:https://github.com/DigAg/diga...
相应后端项目代码地址:https://github.com/DigAg/diga...
关于redux-saga 学习进阶篇一和redux-saga原理的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于2021 年应该用什么 redux-saga 或 redux-thunk?、Api 调用 redux-saga 导致 redux-devtools-extension 崩溃、react - redux/react-redux/redux-saga/mobx、React 实践项目 (二) redux + immutable + redux-saga的相关信息,请在本站寻找。
本文标签: