这一次完全搞定useReducer-使用篇

useReducer-基础概念篇html

useReducer-使用篇前端

useReducer-配合useContext使用react

咱们在第一篇文章中介绍了JavaScript中的reducer以及他的一些特色,对reducer不熟悉的小伙伴能够先看看第一篇git

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

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分散在各处,很容易设置错误或者遗漏,维护这样的老代码更是一个噩梦。app

useReducer版login

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

const [state, dispatch] = useReducer(reducer, initState);
复制代码

useReducer接收两个参数:post

第一个参数: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可让咱们将whathow分开。好比点击了登陆按钮,咱们要作的就是发起登录操做dispatch({ type: 'login' }),点击退出按钮就发起退出操做dispatch({ type: 'logout' }),全部和how相关的代码都在reducer中维护,组件中只须要思考What,让咱们的代码能够像用户的行为同样,更加清晰。

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

总结

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

  • 若是你的state是一个数组或者对象
  • 若是你的state变化很复杂,常常一个操做须要修改不少state
  • 若是你但愿构建自动化测试用例来保证程序的稳定性
  • 若是你须要在深层子组件里面去修改一些状态(关于这点咱们下篇文章会详细介绍)
  • 若是你用应用程序比较大,但愿UI和业务可以分开维护

这篇文章咱们介绍了使用useReducer,帮助咱们集中式的处理复杂的state管理。但若是咱们的页面很复杂,拆分红了多层多个组件,咱们若是在子组件触发这些state变化呢,好比在LoginButton触发登陆操做? 咱们将在下篇文章介绍如何处理复杂组件树结构的reducer共享问题。

若是有小伙伴看过thinking-in-react可能会有疑问,官方不是推荐State应该有子组件本身维护么,为何还要集中式的处理?

其实咱们并非推荐全部的state放一块儿,而是若是确实有不少state跨多个组件公用须要放到page级别维护,这时候能够考虑使用useReducer。

PS:推荐两篇React State管理的文章

最后惯例,欢迎你们star咱们的人人贷大前端团队博客,全部的文章还会同步更新到知乎专栏掘金帐号,咱们每周都会分享几篇高质量的大前端技术文章。若是你喜欢这篇文章,但愿能动动小手给个赞。

参考连接

相关文章
相关标签/搜索