1. 介绍
redux-saga 是 redux 一个中间件,它是基于ES6 的 Generator 功能实现,用于解决异步问题(让redux中可以直接进行异步操作)。
组件会发送一个 action 对象给 redux-saga,redux-saga(主saga) 就会分析监听 saga 中有没有当前 action 对应的 type 类型操作,如果在监听 saga 中找到了,说明当前操作是一个异步操作,然后就会走下面的异步操作流程,最后 action 会被交给 redux,也就是交给 reducer 完成修改。
如果主 saga 在监听 saga 没有找到对应 type 的实现,则说明当前操作是一个同步操作,就会直接交给 redux。
2. redux-saga安装和在项目中引入配置
安装:
yarn add redux-saga
在项目中引入:
store/index.js:
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from '@redux-devtools/extension'
import reducer from './reducer'
import createSagaMiddleware from 'redux-saga'
import mainSaga from './sagas'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(reducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))
sagaMiddleware.run(mainSaga)
export default store
sagas.js:
function* mainSaga() {
}
function* watchSaga() {
}
function* workSaga(action) {
}
export default mainSaga
3. redux-saga的使用
用 resux-saga 写一个延时计数器。
App.jsx:
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
const Login = () => {
const dispatch = useDispatch()
let num = useSelector(state => state.count.num)
return (
<div>
<h3>{num}</h3>
<button onClick={() => dispatch({ type: 'asyncAdd', payload: 10 })}>进入系统</button>
</div>
)
}
export default Login
sagas.js:
import { takeEvery, put } from 'redux-saga/effects'
function delay(n = 1) {
return new Promise(_ => {
setTimeout(() => _(''), 1000 * n)
})
}
function* mainSaga() {
yield watchSaga()
}
function* watchSaga() {
yield takeEvery('asyncAdd', workSaga)
}
function* workSaga({ payload }) {
yield delay(2)
yield put({ type: 'add', payload })
}
export default mainSaga
count.js:
const initState = {
num: 100
}
const reducer = (state = initState, { type, payload }) => {
if ('add' === type) return { ...state, num: state.num + payload }
return state
}
export default reducer
4. saga模块化拆分
在这一章节中,我们将要实现 saga 的模块化拆分。我们的需求是,点击登录系统之后,可以获取到 uid ,获取到 uid 之后可以跳转到后台首页。
首先我们需要 mock 一下后台的用户数据:
user.js:
module.exports = app => {
app.post('/api/login', (req, res) => {
let bufferData = []
req.on('data', chunk => bufferData.push(chunk))
req.on('end', () => {
let postString = Buffer.concat(bufferData).toString('utf-8')
res.send({
code: 0,
msg: 'ok',
data: {
uid: 2000,
token: 'fwe;fjewlfjlwfjlefewlfelffewlfewjfe',
nickname: '张英',
}
})
})
})
}
然后是 api 接口的书写:
userApi.js:
import { post } from '@/utils/http'
export const loginApi = userData => post('/api/login', userData)
然后是 redux 中的 reducer 函数:
user.js:
const initState = {
uid: 0,
token: '',
nickname: ''
}
const reducer = (state = initState, { type, payload }) => {
if ('userLogin' === type) return { ...state, ...payload }
return state
}
export default reducer
接下来是主 saga 文件:
sagas.js:
import { all } from 'redux-saga/effects'
import userWatchSaga from './watchsagas/userSaga'
function* mainSaga() {
yield all([
userWatchSaga()
])
}
export default mainSaga
监听 saga : userSaga.js:
import { put, takeEvery, call } from 'redux-saga/effects'
import { loginApi } from '@/api/userApi'
export default function* watchSaga() {
yield takeEvery('asyncLogin', login)
}
function* login({ payload }) {
let ret = yield call(loginApi, payload)
if (ret.code === 0) {
yield put({ type: 'userLogin', payload: ret.data })
}
}
最后是我们的前台页面:
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
const Login = ({history}) => {
const dispatch = useDispatch()
let num = useSelector(state => state.count.num)
let uid = useSelector(state => state.user.uid)
useEffect(() => {
if (uid > 0) history.push('/')
}, [uid])
const doLogin = () => {
dispatch({ type: 'asyncLogin', payload: { username: 'admin', password: 'admin888' } })
}
return (
<div>
<h3>
{num} -- {uid}
</h3>
<button onClick={doLogin}>进入系统</button>
</div>
)
}
export default Login
5. connected-react-router
描述:
此库可以让redux中完成路由跳转相关的功能。
安装:yarn add connected-react-router
使用步骤:
-
在 src 目录下创建 history.js 文件,并书写如下代码:
import { createBrowserHistory, createHashHistory } from 'history'
const history = createBrowserHistory()
export default history
-
在入口文件中把原来的react-router-dom中定义路由模式组件更换: import React from 'react'
import ReactDOM from 'react-dom'
import { ConnectedRouter as Router } from 'connected-react-router'
import history from './history'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>,
document.getElementById('root')
)
-
在 reducer 模块中,定义一个 router 的模块 import { combineReducers } from 'redux'
import { connectRouter } from 'connected-react-router'
import history from '@/history'
import user from './user'
import count from './count'
export default combineReducers({
router: connectRouter(history),
user,
count
})
-
在redux入口文件中,以中间件的方式把connected-react-router包含到redux中 import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from '@redux-devtools/extension'
import reducer from './reducer'
import createSagaMiddleware from 'redux-saga'
import mainSaga from './sagas'
import { routerMiddleware } from 'connected-react-router'
import history from '@/history'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(reducer, composeWithDevTools(applyMiddleware(routerMiddleware(history), sagaMiddleware)))
sagaMiddleware.run(mainSaga)
export default store
-
在 redux 中间件中就可以完成路由跳转: userSaga.js: import { put, takeEvery, call } from 'redux-saga/effects'
import { push, replace, goBack } from 'connected-react-router'
import { loginApi } from '@/api/userApi'
export default function* watchSaga() {
yield takeEvery('asyncLogin', login)
}
function* login({ payload }) {
let ret = yield call(loginApi, payload)
if (ret.code === 0) {
yield put({ type: 'userLogin', payload: ret.data })
yield put(push('/'))
}
}
-
在前台页面不用使用 hack 方式,而是使用当前库实现路由跳转: import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
const Login = ({ history }) => {
const dispatch = useDispatch()
let num = useSelector(state => state.count.num)
const doLogin = () => {
dispatch({ type: 'asyncLogin', payload: { username: 'admin', password: 'admin888' } })
}
return (
<div>
<h3>
{num}
</h3>
<button onClick={doLogin}>进入系统</button>
</div>
)
}
export default Login
|