redux-saga
redux-saga 是 redux 一个中间件,它是基于ES6 的 Generator 功能实现,用于解决异步问题(让redux中可以直接进行异步操作)。
安装:
npm i -S redux-saga
项目中使用
store/sagas.js
// saga中间件 主saga,用于区别是否需要saga来处理异步操作,如果没有异步,则放行
function* mainSaga() {
}
// 监听saga,监听type类型为异步操作名称的,此saga会通过主saga分配过来
function* watchSaga() {
}
// 工作saga,监听saga得到任务后,把任务分配给工作saga
function* workSaga() {
}
export default mainSaga
三步走运行起来:saga
import {
createStore,applyMiddleware } from "redux";
import {
composeWithDevTools } from '@redux-devtools/extension'
import reducer from "@/store/reducer/index"
import mainSaga from "./sagas";
import createSagaMiddleware from "redux-saga";
const SagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
composeWithDevTools(applyMiddleware(SagaMiddleware))
)
// 运行saga
SagaMiddleware.run(mainSaga)
// 导出store
export default store
使用saga
获取数据
let num = useSelector((store)=>{
console.log(store);//获取数据源
return store.count.num
})
发送dispatch
const dispatch = useDispatch()
const onclickHandler = ()=>{
dispatch({
type:"asyncadd",payload:10})
}
- takeEvery 监听每一次dispatch发送的指令
- put 它是saga提供给我们,用于发送指令给reducer来完成同步操作
- all方法,可以监听多个监听saga,它的功能和Promise.all方法一样,用在模块化
延迟触发查看是否能接受异步操作;
import {
takeEvery} from "redux-saga/effects"
function delay(n=3){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve('')
},1000*n)
})
}
function* mainSaga() {
yield watchSaga()
}
function* watchSaga() {
yield takeEvery('asyncadd', workSaga)
}
function* workSaga({
payload}) {
/*收到发过的的dispatch */
yield delay();//延时函数,看触发,模拟异步
yield console.log(payload);
yield put({
type:"add",payload})
}
export default mainSaga
状态源:
const initState = {
num :100
}
export default (state=initState,{
type,payload})=>{
if(type == "add"){
return {
...state,num:state.num+payload}
}
return state
}
像上面的调用写法只能监听一个saga
这样的调用,它只能监听一个saga,不能进行模块化
- all方法,可以监听多个监听saga,它的功能和Promise.all方法一样,用在模块化
import {
all} from "redux-saga/effects"
import loginSaga from "./watchsaga/login"
function* mainSaga() {
yield all([
loginSaga()
])
}
export default mainSaga
拆分这个监听saga函数
import {
takeEvery,put} from "redux-saga/effects"
function delay(n=1){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve('')
},1000*n)
})
}
function* watchSaga() {
yield takeEvery('asyncadd', addSaga)
//可以写多个
}
function* addSaga({
payload}) {
/*收到发过的的dispatch */
yield delay();//延时函数,看触发,模拟异步,完成网络请求
yield console.log(payload);
yield put({
type:"add",payload})
}
export default watchSaga
saga进行网络请求
这里就有个参数必须得使用:
call方法,调用Promise对象
第一个参数是函数名,而不是去执行函数,后面跟着的就是参数
import {
takeEvery,put,call} from "redux-saga/effects"
import {
post} from "@/utils/http"
function* watchSaga() {
yield takeEvery('asyncadd', addSaga)
}
/* 在此处完成网络请求 */
function* addSaga({
payload}) {
/*收到发过的的dispatch saga帮我们实现了这个co赋值过程*/
let ret = yield call(post,payload)
if(ret.code == 200){
yield put({
type:"loginadd",payload:ret.data})//全局更新登录信息
}
}
export default watchSaga
实地测试模拟异步的状态变化,引起视图的改变。
import {
takeEvery,put,call} from "redux-saga/effects"
function delay(n=2){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({
code:200,data:"ok"})
},1000*n)
})
}
function* watchSaga() {
yield takeEvery('asyncadd', addSaga)
}
/* 在此处完成网络请求 */
function* addSaga({
payload}) {
/*收到发过的的dispatch */
let ret = yield call(delay,5)
console.log(ret);
if(ret.code == 200){
yield put({
type:"loginadd",payload:{
data:ret.data}})//全局更新登录信息
}
}
export default watchSaga
两种跳转路由的方式:
思路一:hack登录状态成功,在 修改dispatch之后就把state修改,同步修改之后,直接在redux中获取判断,是否需要跳转。
或者:
进行登录,dispatch是它是一个异步的,交给saga,saga会完成异步操作,通知reducer完成同步修改redux中的state数据改变, reducer把state中的数据修改后,因为我在当前的组件中有通过useEffect来依赖此state中的值的变化,所以它只要变化了,我就可以来跳转,从而可以确认redux中的数据一定是存在后才跳转的
因为:generator的返回值,不是普通函数这样的返回值,这样在登录成功后,无法让前端的组件完成路由的切换,切换的原则是登录成功后,才能能跳转,登录的过程它是一个异步的,所以此时工作就有点难受。说白了,在使用thunk中间件时,异步hook函数useDispatch的dispatch方法之后会有普通函数的返回值,而使用saga中间件,generator的返回值就不能使用。
思路二:使用插件
安装
connected-react-router
在接收到异步数据之后, 在redux中成功后的路由跳转。