Say Bye to useState

401 阅读3分钟

前言:在最近的业务组件中,我完全使用useReducer代替了useState,并且因此感受到较大的开发快感,以至于我觉得所有关联性高的状态都应该用useReducer聚合在一起管理,应该尽可能地使用useReducer来代替useState

why not useState?

我们先说下useState哪里不好。

以一个列表的展示为例,简单的发请求然后渲染列表,我们看看有哪些我们需要关心的state:

  • isLoading: 当true时,我们要展示Loading组件
  • listData: 当获取到后端返回的数据后,如果是空列表,展示NoData组件,如果是有值,则展示List组件渲染列表数据
  • error: 当请求失败时获得,展示Error组件

如果看看每个state都用一个useState来创建代码会是怎样的:

const [loading, setLoading] = useState(false);
cosnt [listData, setListData] = useState([]);
const [error, setError] = useState(null);

const getListData = () => {
	setLoading(true);
        service.post(url, params).then(res => {
            setListData(res.data.listData);
            setLoading(false);
        }).catch(err => {
            setError(error);
            setLoading(false);
        })
}

这样的代码的问题在哪里呢?

这种代码,当代码执行到某个改变state的节点时,阅读者仅仅能确定某个state被set成了新值,但不明确这个/这些set动作具体的含义是什么,也不明确set之后组件整体是什么样子的

比如我执行到catch中的的代码的时候,我知道你执行了setError(error); setLoading(false);,但是我问你,两个set具体描述了一个什么动作,set之后整个组件是个什么状态?

对于这个例子,你数秒内就能说出来,这是show error的动作,set之后组件是一个展示Error子组件的状态。

然而你之所以可以这么快的说出来仅仅是因为这个例子太简单了,如果这里一坨if else, set了一坨state,你还有信心能这么快回答出这个问题吗?

理清状态的改变是读懂业务代码的关键所在,相信绝大多数人都浪费过时间在理清业务代码的状态上,并感受到不爽。

然而useReducer就能有效地帮助我们解决这个问题,书写出状态流转明确的代码。

how useReducer work?

在我的理解中useReducer这个Api最重要的两个作用就是:

  • 给set动作起名,用一个动作表示多条状态的变更
  • 给set之后的状态起名,用一个状态名表示多条状态的组合 让我们看看用useReducer的思路:
const reducer = (state, action) => {
	switch (action.actionType) {
                case 'REQUEST_SRART':
                    return {
                    	...state,
                        stateType: 'LOADING',
                        loading: true
                    };
                case 'REQUEST_SUCCESS':
                    return {
                        ...state,
                        stateType: action.listData.length === 0 ? 'NO_DATA' : 'HAS_DATA',
                        loading: false,
                        listData: action.listData
                    };
                case 'REQUEST_FAIL':
                    return {
                        ...state,
                        stateType: 'ERROR',
                        loading: false,
                        error: action.error
                    }
                default:
                    return state;
         }
}

const [state, dispatch] = useReducer(reducer, {});

const getListData = () => {
	dispatch({
    	actionType: 'REQUEST_SRART'
    })
    service.post(url, params).then(res => {
        dispatch({
            actionType: 'REQUEST_SUCCESS',
            listData: res.data.listData
        })
    }).catch(err => {
        dispatch({
            actionType: 'REQUEST_FAIL',
            error
        })
    })
}

通过useReducer,任何的状态变更的动作都通过actionType进行了命名,变更后的组件状态也有了stateType进行描述,这使得代码的可读性大大提升。

more about useReducer...

最后我还想分享一下我使用useReducer书写业务组件的一些相关经验。

通常来说,对于一个不过于复杂的功能,我们可以在这个功能模块的根组件中使用useReducer来产生整个组件需要的state和修改state的dispatch方法,我们首先通过书写reducer来理清整个组件的状态流转过程,然后再通过useEffect来写effect逻辑。

组件内产生并仅向下传递的状态就使用一个useReducer创建一个state就可以了,理想情况下子组件消费的都仅仅是这个state的衍生状态。

如果子组件也需要修改这个唯一state,那么就在父组件中把dispatch传到子组件供其调用来修改。

以上就是我这篇文章想表达的全部内容了,Thank you for reading~