react 中的userReducer

useReducer-基础概念篇

useReducer-使用篇

useReducer-配合useContext使用

我们在第一篇文章中介绍了JavaScript中的reducer以及他的一些特点,对reducer不熟悉的小伙伴可以先看看第一篇。

React Hook功能正式发布之后,允许在function component中拥有state和副作用(useEffect)。官方提供了两种state管理的hook:useState、useReducer。下面我们会通过一系列Demo逐步说明如何使用useReducer管理state。

useState版login
我们先看看登录页常规的使用useState的实现方式:

function LoginPage() {
    const [name, setName] = useState(''); // 用户名
    const [pwd, setPwd] = useState(''); // 密码
    const [isLoading, setIsLoading] = useState(false); // 是否展示loading,发送请求中
    const [error, setError] = useState(''); // 错误信息
    const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登录

    const login = (event) => {
        event.preventDefault();
        setError('');
        setIsLoading(true);
        login({ name, pwd })
            .then(() => {
                setIsLoggedIn(true);
                setIsLoading(false);
            })
            .catch((error) => {
                // 登录失败: 显示错误信息、清空输入框用户名、密码、清除loading标识
                setError(error.message);
                setName('');
                setPwd('');
                setIsLoading(false);
            });
    }
    return ( 
        //  返回页面JSX Element
    )
}

上面Demo我们定义了5个state来描述页面的状态,在login函数中当登录成功、失败时进行了一系列复杂的state设置。可以想象随着需求越来越复杂更多的state加入到页面,更多的setState分散在各处,很容易设置错误或者遗漏,维护这样的老代码更是一个噩梦。

useReducer版login
下面看看如何使用useReducer改造这段代码,先简单介绍下useReducer。

 const [state, dispatch] = useReducer(reducer, initState);
useReducer接收两个参数:

第一个参数:reducer函数,没错就是我们上一篇文章介绍的。第二个参数:初始化的state。返回值为最新的state和dispatch函数(用来触发reducer函数,计算对应的state)。按照官方的说法:对于复杂的state操作逻辑,嵌套的state的对象,推荐使用useReducer。

听起来比较抽象,我们先看一个简单的例子:

// 官方 useReducer Demo
// 第一个参数:应用的初始化
const initialState = {count: 0};

// 第二个参数:state的reducer处理函数
function reducer(state, action) {
    switch (action.type) {
        case 'increment':
          return {count: state.count + 1};
        case 'decrement':
           return {count: state.count - 1};
        default:
            throw new Error();
    }
}

function Counter() {
    // 返回值:最新的state和dispatch函数
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
        <>
            // useReducer会根据dispatch的action,返回最终的state,并触发rerender
            Count: {state.count}
            // dispatch 用来接收一个 action参数「reducer中的action」,用来触发reducer函数,更新最新的状态
            <button onClick={() => dispatch({type: 'increment'})}>+</button>
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
        </>
    );
}

了解了useReducer基本使用方法后,看看如何使用useReducer改造上面的login demo:

const initState = {
    name: '',
    pwd: '',
    isLoading: false,
    error: '',
    isLoggedIn: false,
}
function loginReducer(state, action) {
    switch(action.type) {
        case 'login':
            return {
                ...state,
                isLoading: true,
                error: '',
            }
        case 'success':
            return {
                ...state,
                isLoggedIn: true,
                isLoading: false,
            }
        case 'error':
            return {
                ...state,
                error: action.payload.error,
                name: '',
                pwd: '',
                isLoading: false,
            }
        default: 
            return state;
    }
}
function LoginPage() {
    const [state, dispatch] = useReducer(loginReducer, initState);
    const { name, pwd, isLoading, error, isLoggedIn } = state;
    const login = (event) => {
        event.preventDefault();
        dispatch({ type: 'login' });
        login({ name, pwd })
            .then(() => {
                dispatch({ type: 'success' });
            })
            .catch((error) => {
                dispatch({
                    type: 'error'
                    payload: { error: error.message }
                });
            });
    }
    return ( 
        //  返回页面JSX Element
    )
}

乍一看useReducer改造后的代码反而更长了,但很明显第二版有更好的可读性,我们也能更清晰的了解state的变化逻辑。

可以看到login函数现在更清晰的表达了用户的意图,开始登录login、登录success、登录error。LoginPage不需要关心如何处理这几种行为,那是loginReducer需要关心的,表现和业务分离。

另一个好处是所有的state处理都集中到了一起,使得我们对state的变化更有掌控力,同时也更容易复用state逻辑变化代码,比如在其他函数中也需要触发登录error状态,只需要dispatch({ type: ‘error’ })。

useReducer可以让我们将what和how分开。比如点击了登录按钮,我们要做的就是发起登陆操作dispatch({ type: ‘login’ }),点击退出按钮就发起退出操作dispatch({ type: ‘logout’ }),所有和how相关的代码都在reducer中维护,组件中只需要思考What,让我们的代码可以像用户的行为一样,更加清晰。

除此之外还有一个好处,我们在前文提过Reducer其实一个UI无关的纯函数,useReducer的方案是的我们更容易构建自动化测试用例。

总结
最后我们总结一下这篇文章的一些主要内容:使用reducer的场景

如果你的state是一个数组或者对象
如果你的state变化很复杂,经常一个操作需要修改很多state
如果你希望构建自动化测试用例来保证程序的稳定性
如果你需要在深层子组件里面去修改一些状态(关于这点我们下篇文章会详细介绍)
如果你用应用程序比较大,希望UI和业务能够分开维护
这篇文章我们介绍了使用useReducer,帮助我们集中式的处理复杂的state管理。但如果我们的页面很复杂,拆分成了多层多个组件,我们如果在子组件触发这些state变化呢,比如在LoginButton触发登录操作? 我们将在下篇文章介绍如何处理复杂组件树结构的reducer共享问题。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值