react18中redux-saga实战系统登录功能及阻塞与非阻塞的性能优化

redux-saga中的effect常用的几个是有区分出阻塞与非阻塞的,这里主要看下callfork两者的区别。

实现效果

  • 非阻塞的task执行,不用等到登录成功后请求的list接口完成,点击退出按钮可以立即退出
  • 阻塞task的执行,必须等到登录成功后请求的list接口完成,点击退出按钮才有效。下面展示的点击效果,开始点击退出前几次是没有用的,没有反应。

    以上两种方式都有各自使用场景,具体可以根据自己的需求去开发
  • 第一种就是,登录后可以立即退出
  • 第二种就是,登录后会请求列表接口,只有等接口请求完成了,点击退出才会有效。

代码逻辑

  • 页面组件SagaLogin.js
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.Item
            label="用户名:"
            name="username"
            rules={[{ required: true, message: "Please input your username!" }]}
          >
            <Input
              placeholder="请输入用户名"
              value={username}
              onChange={handleChangeName}
            />
          </Form.Item>
          <Form.Item
            label="密码:"
            name="password"
            rules={[{ required: true, message: "Please input your password!" }]}
          >
            <Input
              placeholder="请输入密码"
              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
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%;
    }
  }
`;
  • 跳转列表页面
js 复制代码
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>
        <FloatButton
          type="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
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
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
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
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");
  // 监听所有的dispatch
  yield 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
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中dispatchaction为原始对象
  • 集中处理异步等存在副作用的逻辑
  • 通过转化effects函数,可以方便进行单元测试
  • 完善和严谨的流程控制,可以较为清晰的控制复杂的逻辑。
相关推荐
Gnevergiveup3 分钟前
2024网鼎杯青龙组Web+Misc部分WP
开发语言·前端·python
你不讲 wood22 分钟前
使用 Axios 上传大文件分片上传
开发语言·前端·javascript·node.js·html·html5
Iqnus_1231 小时前
vue下载安装
前端·vue.js·笔记
网安_秋刀鱼1 小时前
CSRF防范及绕过
前端·安全·web安全·网络安全·csrf·1024程序员节
新知图书1 小时前
Django 5企业级Web应用开发实战-分页
前端·python·django
GDAL1 小时前
HTML入门教程8:HTML格式化
前端·html
清灵xmf2 小时前
UI 组件的二次封装
前端·javascript·vue.js·element-plus
聪明的墨菲特i2 小时前
Vue组件学习 | 二、Vuex组件
前端·vue.js·学习·前端框架·1024程序员节
海盗12342 小时前
Web前端开发工具和依赖安装
前端
小何学计算机2 小时前
Nginx配置基于端口的 Web 服务器
服务器·前端·nginx