为什么要使用React中的useReducer

导读:使用useReducer可以减少useState的使用,减少VDOM的render次数,也便于统一管理多变量

了解useReducer()这个hooks之前,首先得了解reduxredux中的reducer,dispatch,action,store概念。

关于redux的概念,推荐阅读这篇wikihttps://www.redux.org.cn/docs/basics/

在开发过程中,登录页面很常见,那么在函数组件中,如何使用hooks来编码实现呐?首先我们先看看第一个版本:

// 省略了部分代码
const React, { useState } = require("react")

const loginPage = () => {
    const [name, setName] = useState('');
    const [pwd, setPwd] = useState('');
    const [isLoading, setIsLoading] = useState(false);
    const [errorMsg, setErrorMsg] = useState('');
    const [isLoggedIn, setIsLoggedIn] = useState(false);

    const login = (event) => {
        event.preventDefault();
        event.stopPropagation();
        setIsLoading(true);
        setErrorMsg('');
        loginApi({name, pwd}).then(() => {
            // success
            setIsLoggedIn(true);
        }).catch(err => {
            // error
            setErrorMsg(err.message);
            setName('');
            setPwd('');
        }).finally(() => {
            setIsLoading(false);
        });
    };

    return (
        <div>
            <div>{isLoggedIn ? "已登录" : "未登录"}</div>
            <button onClick={(e) => login(e)}>点击登录</button>
            <div>{errorMsg}</div>
            {isLoading ? <div>loading....</div> : ""}
        </div>
    );
}

大家发现,使用useState我们需要维护很多的变量,不停地使用setXxxx() 去更新变量值。当另一个同事想接手你的代码时,发现这代码简直在后期无法维护,在判断逻辑中,倘若某个变量疏忽,就会产生错误,有什么方法可以将变量的赋值整合在一起呐?

当然有!React官方提供了useReducer这个hooks,就像使用redux状态管理一下使用即可,那么接下来看看第二个版本的代码:

import React, { useReducer } from "react";
import ReactDOM from "react-dom";
const initialState = {
  name: "",
  pwd: "",
  isLoading: false,
  errorMsg: "",
  isLoggedIn: false,
};

const loginReducer = (state = initialState, action) => {
  switch (action.type) {
    case "login":
      return {
        ...state,
        isLoading: true,
        errorMsg: "",
        isLoggedIn: false,
      };
    case "success":
      return {
        ...state,
        isLoading: false,
        isLoggedIn: true,
      };
    case "error":
      return {
        ...state,
        isLoading: false,
        errorMsg: action.payload.msg,
        name: "",
        pwd: "",
        isLoggedIn: false,
      };
    default:
      return state;
  }
};

// 模拟网络请求登录数据的API
const loginApi = (username, password) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const isError = Math.round(Math.random() * 100) % 2;
      if (isError) {
        reject({
          code: 400,
          message: "登录失败",
        });
      } else {
        resolve({
          code: 200,
        });
      }
    }, 3000);
  });
};

function Example() {
  const [state, dispatch] = useReducer(loginReducer, initialState);
  const { name, pwd, isLoading, errorMsg, isLoggedIn } = state;

  const login = (event) => {
    event.preventDefault();
    event.stopPropagation();
    dispatch({ type: "login" });
    loginApi({ name, pwd })
      .then(() => {
        // success
        dispatch({ type: "success" });
      })
      .catch((err) => {
        // error
        dispatch({ type: "error", payload: { msg: err.message } });
      });
  };

  return (
    <div>
      <div>{isLoggedIn ? "已登录" : "未登录"}</div>
      <button onClick={(e) => login(e)}>点击登录</button>
      <div>{errorMsg}</div>
      {isLoading ? <div>loading....</div> : ""}
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Example />, rootElement);

你会感到疑惑,第二个版本的代码的行数不是更多了么,难道不是更复杂了吗?答案当然是更简单了,除去一些DOM挂载代码,我们可以发现主要的逻辑采用dispatch来维护,有变量的改变,只需要在loginReducer中统一修改,大大地提升了可维护性。

发表评论 / Comment

用心评论~

金玉良言 / Appraise
37763496LV 1
2020-12-29 14:13
大佬如果能把代码贴全简直完美
如果是地中方案是不是可以用受控组件获取name和pwd的值
过时第二种方案是不是加上在设置initialState的时候就用useRef关联上,如果代码全了。简直完美。。。
幻想家LV 2
2020-09-03 11:11
更新频率爱了
DYBOY站长已认证
2020-08-29 15:54
另外跨组件的数据传递,且不想用props组层传递,则可以使用useContext包装
参考中文官方文档:https://zh-hans.reactjs.org/docs/context.html

使用经验:
- 如果你的页面state很简单,可以直接使用useState
- 如果你的页面state比较复杂(state是一个对象或者state非常多散落在各处)请使用userReducer
- 如果你的页面组件层级比较深,并且需要子组件触发state的变化,可以考虑useReducer + useContext

Warning: Cannot modify header information - headers already sent by (output started at /www/wwwroot/blog.dyboy.cn/content/templates/dyblog/footer.php:56) in /www/wwwroot/blog.dyboy.cn/include/lib/view.php on line 23