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 thunk from 'redux-thunk'
// 合并后的reducer
import reducer from './reducer'
// saga中间件
import createSagaMiddleware from 'redux-saga'
import mainSaga from './sagas'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(reducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))
// 运行saga
sagaMiddleware.run(mainSaga)
export default store
sagas.js:
// saga中间件 主saga,用于区别是否需要saga来处理异步操作,如果没有异步,则放行
function* mainSaga() {
}
// 监听saga,监听type类型为异步操作名称的,此saga会通过主saga分配过来
function* watchSaga() {
}
// 工作saga,监听saga得到任务后,把任务分配给工作saga
function* workSaga(action) {
}
// 主saga要对外暴露出去
export default mainSaga
3. redux-saga的使用
用 resux-saga 写一个延时计数器。
App.jsx:
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
// useSelector : 读取redux的state的数据
// useDispatch : 修改redux的state的数据
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'
// put 它是saga提供给我们,用于发送指令给reducer来完成同步操作
// takeEvery 监听每一次dispatch发送的指令
// 延时器
function delay(n = 1) {
return new Promise(_ => {
setTimeout(() => _(''), 1000 * n)
})
}
// saga中间件 主saga,用于区别是否需要saga来处理异步操作,如果没有异步,则放行
function* mainSaga() {
// 这样的调用,它只能监听一个saga,不能进行模块化
yield watchSaga()
}
// 监听saga,监听type类型为异步操作名称的,此saga会通过主saga分配过来
function* watchSaga() {
yield takeEvery('asyncAdd', workSaga)
}
// 工作saga,监听saga得到任务后,把任务分配给工作saga
// function* workSaga(action) {
function* workSaga({ payload }) {
// 异步请求
yield delay(2)
// 发送指令让reducer完成同步操作
yield put({ type: 'add', payload })
}
// 主saga要对外暴露出去
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 = []
// 如果你要在mock时想要接受post数据
req.on('data', chunk => bufferData.push(chunk))
req.on('end', () => {
// username=xxx&password=xx
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 }) => {
// reducer函数处理 userlogin 类型的 action
if ('userLogin' === type) return { ...state, ...payload }
return state
}
export default reducer
接下来是主 saga 文件:
sagas.js:
import { all } from 'redux-saga/effects'
// all方法,可以监听多个监听saga,它的功能和Promise.all方法一样
import userWatchSaga from './watchsagas/userSaga'
// saga中间件 主saga,用于区别是否需要saga来处理异步操作,如果没有异步,则放行
function* mainSaga() {
yield all([
// 监听 saga 中有 userWatchSaga 操作,所以会拦截这个 action
userWatchSaga()
])
}
export default mainSaga
监听 saga :
userSaga.js:
import { put, takeEvery, call } from 'redux-saga/effects'
// put 它是saga提供给我们,用于发送指令给reducer来完成同步操作
// takeEvery 监听每一次dispatch发送的指令
// call方法,调用Promise对象
// 引入网络请求方法
import { loginApi } from '@/api/userApi'
export default function* watchSaga() {
yield takeEvery('asyncLogin', login)
}
// 在此处完成网络请求就可以了
// generator的返回值,不是普通函数这样的返回值,这样在登录成功后,无法让前端的组件完成路由的切换,
// 切换的原则是登录成功后,才能能跳转,登录的过程它是一个异步的,所以此时工作就有点难受,所以需要用到库
function* login({ payload }) {
// call内部实现了 co 方法,可以将自己的返回值返回给 ret
let ret = yield call(loginApi, payload)
// 得到的数据同步到redux中
if (ret.code === 0) {
// 通过reducer完成redux中的数据更新 登录成功
yield put({ type: 'userLogin', payload: ret.data })
}
}
最后是我们的前台页面:
import React, { useEffect } from 'react'
// react-redux提供的hook工具函数
import { useDispatch, useSelector } from 'react-redux'
// useSelector : 读取redux的state的数据
// useDispatch : 修改redux的state的数据
const Login = ({history}) => {
const dispatch = useDispatch()
let num = useSelector(state => state.count.num)
let uid = useSelector(state => state.user.uid)
// hack处理方案,完成登录成功后,路由跳转
// 只要 uid 发生改变(由零变为2000),这个函数就被触发
useEffect(() => {
// uid初始值为0,只要你登录成功,则一定会大于0,表示登录成功,跳转到后台
if (uid > 0) history.push('/')
}, [uid])
const doLogin = () => {
// 进行登录,它是一个异步的,交给saga,saga会完成异步操作,通知reducer完成同步修改redux中的state数据改变
// reducer把state中的数据修改后,因为我在当前的组件中有通过useEffect来依赖此state中的值的变化,所以它只要变化了,我就可以来跳转,从而可以确认redux中的数据一定是存在后才跳转的
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 文件,并书写如下代码:
// history模块它是react-router-dom安装成功后就存在的,无需手动再安装 import { createBrowserHistory, createHashHistory } from 'history' const history = createBrowserHistory() // 告知当前路由的模式为 history模式 export default history
-
在入口文件中把原来的react-router-dom中定义路由模式组件更换:
import React from 'react' import ReactDOM from 'react-dom' // 路由 // import { BrowserRouter as Router, Switch, Route } from 'react-router-dom' // 使用connected-react-router库,就需要把原来的路由模式组件进行更换 import { ConnectedRouter as Router } from 'connected-react-router' import history from './history' // redux 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 的模块 router: connectRouter(history), user, count })
-
在redux入口文件中,以中间件的方式把connected-react-router包含到redux中
import { createStore, applyMiddleware } from 'redux' import { composeWithDevTools } from '@redux-devtools/extension' // 中间件 // import thunk from 'redux-thunk' // 合并后的reducer import reducer from './reducer' // saga中间件 import createSagaMiddleware from 'redux-saga' import mainSaga from './sagas' // redux中路由 import { routerMiddleware } from 'connected-react-router' import history from '@/history' const sagaMiddleware = createSagaMiddleware() // 在redux入口文件中,以中间件的方式把connected-react-router包含到redux中 const store = createStore(reducer, composeWithDevTools(applyMiddleware(routerMiddleware(history), sagaMiddleware))) // 运行saga sagaMiddleware.run(mainSaga) export default store
-
在 redux 中间件中就可以完成路由跳转:
userSaga.js:
import { put, takeEvery, call } from 'redux-saga/effects' // put 它是saga提供给我们,用于发送指令给reducer来完成同步操作 // takeEvery 监听每一次dispatch发送的指令 // 通过库,可以完成在redux中实现跳转跳转功能 import { push, replace, goBack } from 'connected-react-router' // call方法,调用Promise对象 // 引入网络请求方法 import { loginApi } from '@/api/userApi' export default function* watchSaga() { yield takeEvery('asyncLogin', login) } // 在此处完成网络请求就可以了 // generator的返回值,不是普通函数这样的返回值,这样在登录成功后,无法让前端的组件完成路由的切换, // 切换的原则是登录成功后,才能能跳转,登录的过程它是一个异步的,所以此时工作就有点难受,所以需要用到库 function* login({ payload }) { // call内部实现了 co 方法,可以将自己的返回值返回给 ret let ret = yield call(loginApi, payload) // 得到的数据同步到redux中 if (ret.code === 0) { // 通过reducer完成redux中的数据更新 登录成功 yield put({ type: 'userLogin', payload: ret.data }) // 跳转到后台首页 -- 在redux中间件中就可以完成路由的跳转 yield put(push('/')) } }
-
在前台页面不用使用 hack 方式,而是使用当前库实现路由跳转:文章来源:https://www.toymoban.com/news/detail-444440.html
import React from 'react' // react-redux提供的hook工具函数 import { useDispatch, useSelector } from 'react-redux' const Login = ({ history }) => { const dispatch = useDispatch() let num = useSelector(state => state.count.num) const doLogin = () => { // 进行登录,它是一个异步的,交给saga,saga会完成异步操作,通知reducer完成同步修改redux中的state数据改变 // reducer把state中的数据修改后,因为我在当前的组件中有通过useEffect来依赖此state中的值的变化,所以它只要变化了,我就可以来跳转,从而可以确认redux中的数据一定是存在后才跳转的 dispatch({ type: 'asyncLogin', payload: { username: 'admin', password: 'admin888' } }) } return ( <div> <h3> {num} </h3> <button onClick={doLogin}>进入系统</button> </div> ) } export default Login
文章来源地址https://www.toymoban.com/news/detail-444440.html
到了这里,关于React中的redux-saga详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!