redux-saga 使用详解说明
redux-saga
是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
可以想像为,一个 saga 就像是应用程序中一个单独的线程,它独自负责处理副作用。 redux-saga
是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action。
redux-saga 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试。通过这样的方式,这些异步的流程看起来就像是标准同步的 Javascript 代码。有点像 async
/await
功能。
那他跟我们以前使用 redux-thunk 有什么区别了,不再会遇到回调地狱了,你可以很容易地测试异步流程并保持你的 action 是干净的。
接下来我们详细的说明如何接入我们自己的项目。react 项目搭建我们这就不累赘,直接用 create-react-app 脚手架创建一个。这个网上很多教程,用起来也很简单,构建能力也很强,适合基本所有项目。后面代码标记加粗的部分是我们需要在脚手架基本上编辑的代码。
redux-saga 故名思议它是跟 redux 一起用的,是解决 react 状态管理副作用的一个中间件。下面我们以一些案例来学习这个中间件,开启我们的神奇之旅。
- 安装 react-redux redux-saga
npm i -S react-redux redux-saga
- src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import { composeWithDevTools } from 'redux-devtools-extension'; // 与谷歌插件配合可以更直观的在浏览器上查看redux state 状态
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers';
import mySaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
composeWithDevTools(
applyMiddleware(sagaMiddleware)
)
)
sagaMiddleware.run(mySaga)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
- 新建 sagas 目录 index.js
import { all } from 'redux-saga/effects';
import { add } from './counter';
export default function* rootSaga() {
yield all([
add()
])
}
- sagas/counter.js
import { put, takeEvery, delay } from 'redux-saga/effects';
function* increase() {
yield delay(1000); // 等待1秒
yield put({ type: 'INCREMENT' }); // 命令 middleware 向 Store 发起一个 action
}
// 监听异步自增事件
export function* add() {
yield takeEvery('INCREMENT_ASYNC', increase);
}
- 新增reducers目录 index.js
import { combineReducers } from 'redux';
import counter from './counter';
export default combineReducers({
counter
})
- reducers/counter.js
const counter = (state = 1, action = {}) => {
switch(action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
}
export default counter;
- 新建actions目录 index.js
// 自增
export const increate = () => {
return {
type: 'INCREMENT'
}
}
// 异步自增(等待1秒才触发自增action)
export const increateAsync = () => {
return {
type: 'INCREMENT_ASYNC'
}
}
- 自增组件
import React from 'react';
import { connect } from 'react-redux';
import { increate, increateAsync } from '../actions';
class CustomCounter extends React.Component {
render() {
return (
<div className="custom-counter" style={{padding: 50}}>
<div style={{ marginBottom: '20px' }}>
<p>{this.props.counter}</p>
<button onClick={()=>this.props.increate()}>自增</button>
<button onClick={()=>this.props.increateAsync()}>异步自增</button>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
counter: state.counter
}
}
export default connect(mapStateToProps, { increate, increateAsync })(CustomCounter);
这是一个相对简单的自增组件,利用到了 redux-saga。但是从代码不能看出他的 Generator 功能确实使代码看起来可读性要好一些。而且抽离一层出来,使得 redux 代码更加的清晰。下面我们来一个稍复杂一点的案例,异步请求接口获取用户信息。这里请求接口用到了 react 代理功能,react proxy 配置请参考:https://www.ifrontend.net/2021/07/react-proxy/
这里我们用到另个非常好用的 ajax 模块, axios,下面安装一下吧
npm i -S axios
- 新建一个 fetchUser.js 获取用户信息的 saga
import { call, takeEvery, put } from 'redux-saga/effects';
import axios from 'axios';
function* fetch_user() {
try {
const userInfo = yield call(axios.get, '/api/getUserInfo');
yield put({ type: 'FETCH_SUCCESS', userInfo: userInfo.data });
} catch (e) {
yield put({ type: 'FETCH_FAIL', error: e });
}
}
function* user() {
yield takeEvery('FETCH_REQUEST', fetch_user);
}
const rootUser = [
user()
]
export default rootUser;
- 新建一个 users.js reducer
const initialState = {
isFetch: false,
error: null,
userInfo: null
}
const user = (state = initialState, action = {}) => {
switch (action.type) {
case 'FETCH_REQUEST':
return {
...state,
isFetch: true
}
case 'FETCH_SUCCESS':
return {
...state,
isFetch: false,
userInfo: action.userInfo
}
case 'FETCH_FAIL':
return {
...state,
isFetch: false,
error: action.error
}
default:
return state;
}
}
export default user;
- 新建一个 users.js action
export const fetchUser = () => {
return {
type: 'FETCH_REQUEST'
}
}
- CustomUserInfo 组件
import React from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions/users.js';
class CustomUser extends React.Component {
render() {
const { isFetch, error, userInfo } = this.props.user;
let data = ''
if (isFetch) {
data = '正在加载中...'
} else if (userInfo) {
data = userInfo.name;
} else if (error) {
data = error.message;
}
return (
<div className="custom-user" style={{padding: 50}}>
<div style={{ marginBottom: '20px' }}>
<button onClick={()=>this.props.fetchUser()}>axios请求</button >
</div>
<h2>{data}</h2>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(mapStateToProps, { fetchUser })(CustomUser);
- 这里用到了一个 api/getUserInfo 的接口,笔者是采用 node.js koa 做的,这里也贴一下代码。
const koa = require('koa');
const app = new koa();
const Router = require('koa-router');
const router = new Router();
router.get('/api/getUserInfo', async (ctx, next) => {
ctx.body={state:"success",name: '伍哥的传说'};
})
// 启动路由
app.use(router.routes()).use(router.allowedMethods())
app.listen(3001, () => {
console.log('server is running at http://localhost:3001')
});
经过这个是不是越来越觉得 redux-saga 好用了,答案是肯定的,用 redux-thunk 时有异步操作时,除了有可能会回调地狱,ajax 请求的业务逻辑也是跟 action 柔在一起的。代码没有这么清晰。
当然 redux-saga 就这些作用,功能吗。肯定不止这些,下面我们一一来尝试!
- 监听未来的 action
日志记录器:Saga 将监听所有发起到 store 的 action,并将它们记录到控制台。
使用 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)
})
}
待更新…