redux-saga
中的effect
常用的几个是有区分出阻塞与非阻塞的,这里主要看下call
和fork
两者的区别。
实现效果
- 非阻塞的task执行,不用等到登录成功后请求的list接口完成,点击退出按钮可以立即退出
- 阻塞task的执行,必须等到登录成功后请求的list接口完成,点击退出按钮才有效。下面展示的点击效果,开始点击退出前几次是没有用的,没有反应。
以上两种方式都有各自使用场景,具体可以根据自己的需求去开发 - 第一种就是,登录后可以立即退出
- 第二种就是,登录后会请求列表接口,只有等接口请求完成了,点击退出才会有效。
代码逻辑
- 页面组件SagaLogin.js
import { Form, Input, Button } from "antd";
import { useState, useEffect } from "react";
import { LoginContainer } from "./style.js";
import { useSelector, useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import * as actionTypes from "../../store/sagas/actionTypes";
function SagaLogin() {const [username, setUsername] = useState("");const [password, setPassword] = useState("");const [isLoading, setIsLoading] = useState(false);const isLogin = useSelector((state) => state.getIn(["sys", "isLogin"]));const navigator = useNavigate();const dispatch = useDispatch();const handleChangeName = (e) => {setUsername(e.target.value);dispatch({type: actionTypes.CHANGE_USERNAME + "@@SAGA@@",value: e.target.value,});};const handleChangePwd = (e) => {setPassword(e.target.value);dispatch({type: actionTypes.CHANGE_PASSWORD + "@@SAGA@@",value: e.target.value,});};const onFinish = (values) => {console.log(values);setIsLoading(true);dispatch({ type: actionTypes.LOGIN + "@@SAGA@@", value: values });};useEffect(() => {// 监听登录if (isLogin) {setIsLoading(false);navigator("/saga-list", {replace: true,});}return () => {};}, [isLogin]);return (<LoginContainer><div className="main"><h2>Saga-Login</h2><br />{isLogin}<Form onFinish={onFinish}><Form.Itemlabel="用户名:"name="username"rules={[{ required: true, message: "Please input your username!" }]}><Inputplaceholder="请输入用户名"value={username}onChange={handleChangeName}/></Form.Item><Form.Itemlabel="密码:"name="password"rules={[{ required: true, message: "Please input your password!" }]}><Inputplaceholder="请输入密码"type="password"value={password}onChange={handleChangePwd}/></Form.Item><Form.Item><Button block type="primary" htmlType="submit" loading={isLoading}>登录</Button></Form.Item></Form></div></LoginContainer>);
}export default SagaLogin;
- style.js
import styled from "styled-components";
import bg from "./image.png";
export const LoginContainer = styled.div`height: 100vh;background: url(${bg}) no-repeat center;background-size: cover;.main {background: rgba(255, 255, 255, 0.5);height: 100vh;display: flex;justify-content: center;align-items: center;flex-direction: column;h2 {margin-bottom: 20px;}form {width: 60%;}}
`;
- 跳转列表页面
import { useSelector, useDispatch } from "react-redux";
import { Button, Space, FloatButton } from "antd";
import * as actionTypes from "../../store/sagas/actionTypes";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { AticleContainerContainer } from "./style";
function SagaList() {const list = useSelector((state) => state.getIn(["sys", "articleList"]));const userInfo = useSelector((state) => state.getIn(["sys", "userInfo"]));const isLogin = useSelector((state) => state.getIn(["sys", "isLogin"]));const navigator = useNavigate();const dispatch = useDispatch();const handleLoginOut = () => {dispatch({type: actionTypes.LOGIN_OUT + "@@SAGA@@",});};useEffect(() => {// 监听退出if (!isLogin) {navigator("/saga-login");}return () => {};}, [isLogin]);return (<AticleContainerContainer><h1>Saga-List</h1><p>This is the SagaList page.</p><Space><p>{userInfo.get("username")}</p>{userInfo.get("password")}</Space><div><FloatButtontype="primary"description={"退出"}onClick={handleLoginOut}>退出登录</FloatButton></div><ul>{list.map((item) => {return (<li key={item.username}>{item.username} --- {item.channelName}</li>);})}</ul></AticleContainerContainer>);
}
export default SagaList;
- style.js
import styled from "styled-components";
export const AticleContainerContainer = styled.div`height: 100vh;display: flex;justify-content: center;align-items: center;flex-direction: column;ul {list-style: none;li {margin: 10px 0;}}
`;
- store/index.js
import { createStore, applyMiddleware } from "redux";
import { combineReducers } from "redux-immutable";
import createSagaMiddleware from "redux-saga";
import defAllSags from "./sagas/index.js";
import CounterReducer from "./sagas/counterReducer";
import TaskOkReucer from "./sagas/taskOkReucer.js";
import sysLoginReducer from "./sagas/sysReucers.js";const sagaMiddleware = createSagaMiddleware();const reducers = combineReducers({count: CounterReducer,task: TaskOkReucer,sys: sysLoginReducer, // 系统登录的reducer
});const store = createStore(reducers, applyMiddleware(sagaMiddleware));sagaMiddleware.run(defAllSags);export default store;
- store/sagas/sysReucers.js
import { fromJS } from "immutable";
import * as actionTypes from "./actionTypes";
const initState = fromJS({isLogin: false,articleList: [],userInfo: {username: "",password: "",},
});function sysLoginReducer(state = initState, action) {console.log("🚀 ~ sysLoginReducer ~ action:", action);switch (action.type) {case actionTypes.LOGIN_SUCCESS:return state.set("isLogin", true);case actionTypes.LOGIN_OUT:return state.set("isLogin", false);case actionTypes.CHANGE_USERNAME:return state.mergeIn(["userInfo"], { username: action.value });case actionTypes.CHANGE_PASSWORD:return state.mergeIn(["userInfo"], { password: action.value });case actionTypes.UPDATA_LIST:return state.set("articleList", action.value);default:return state;}
}
export default sysLoginReducer;
- store/sagas/sysSaga.js
import * as actionTypes from "./actionTypes";
import {takeLatest,fork,all,delay,put,call,takeEvery,
} from "redux-saga/effects";
// 修改名称
function* handleChangeName(action) {console.log("change name", action);// yield delay(2000);yield put({ type: actionTypes.CHANGE_USERNAME, value: action.value });
}function* watchChangeName() {yield takeLatest(actionTypes.CHANGE_USERNAME + "@@SAGA@@", handleChangeName);
}function* handleChangePwd(action) {console.log("change pwd", action);// yield delay(2000);yield put({ type: actionTypes.CHANGE_PASSWORD, value: action.value });
}function* watchChangePwd(action) {console.log("change pwd", action);yield takeLatest(actionTypes.CHANGE_PASSWORD + "@@SAGA@@", handleChangePwd);
}function* getList() {try {yield delay(3000);const activityList = [{ username: "username1", channelName: "channelName1" },{ username: "username2", channelName: "channelName2" },{ username: "username3", channelName: "channelName3" },{ username: "username4", channelName: "channelName4" },];yield put({ type: actionTypes.UPDATA_LIST, value: activityList });} catch (error) {yield put({ type: "update_list_error", error });}
}function* handleLogout() {console.log("logout");yield put({ type: actionTypes.LOGIN_OUT });
}function* handleLogin(action) {yield delay(2000);if (action.value.username === "admin" && action.value.password === "123456") {yield put({ type: actionTypes.LOGIN_SUCCESS });// 获取列表数据yield call(getList);} else {yield put({ type: actionTypes.LOGIN_FAIL });}// 监听登出yield takeEvery(actionTypes.LOGIN_OUT + "@@SAGA@@", handleLogout);
}
function* watchLogin(action) {console.log("login", action);yield takeLatest(actionTypes.LOGIN + "@@SAGA@@", handleLogin);
}function* sysSaga() {console.log("SYS SAGA");// 监听所有的dispatchyield all([fork(watchChangeName), fork(watchChangePwd), fork(watchLogin)]);
}
export default sysSaga;
在handleLogin
中,我们模拟判断账户密码,触发登录成功更改登录状态,在去请求getList
,这个是阻塞的。
这样一看没有什么问题,但是注意call
方法调用是会阻塞主线程的,具体来说:
- 在
call
方法调用结束之前,call
方法之后的语句是无法执行的- 如果
call(getList)
存在延迟,call(getList)
之后的语句yield takeEvery(actionTypes.LOGIN_OUT + "@@SAGA@@", handleLogout);
在call方法返回结果之前无法执行- 在延迟期间的登出操作会被忽略。
无阻塞调用
将yield call(getList)
改为 yield fork(getList)
,通过fork方法不会阻塞主线程,在getlist
接口未返回结果前点击登出,可以立刻响应登出功能,从而返回登陆页面。
- store/sagas/actionTypes.js
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export const REQUEST_FAIL = "REQUEST_FAIL";
export const CHANGE_NAME = "CHANGE_NAME";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN = "LOGIN";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const CHANGE_USERNAME = "CHANGE_USERNAME";
export const CHANGE_PASSWORD = "CHANGE_PASSWORD";
export const UPDATA_LIST = "UPDATA_LIST";
export const LOGIN_OUT = "LOGIN_OUT";
总结
通过前面的案例分析,我们可以概括出redux-saga
做为redux
中间件的全部优点:
- 统一
action
的形式,在redux-saga
中,从UI中dispatch
的action
为原始对象 - 集中处理异步等存在副作用的逻辑
- 通过转化
effects
函数,可以方便进行单元测试 - 完善和严谨的流程控制,可以较为清晰的控制复杂的逻辑。