Hook改变的React Component写法思路(4) - useReducer

其实一开始看到useReducer的时候,还以为它跟react的好伙伴redux有什么关系。然而也就是借鉴了redux的设计模式的状态操作工具而已。

目前使用Redux的小伙伴们可能还需要用原来的react-redux提供的connect HOC一段时间。尽管facebook已经在做相关的hook,目前还处于不稳定的状态,使用后果自负(项目地址:https://github.com/facebookincubator/

回到useReducer,它的存在是为一步操作更新多个状态设计。

举个不太恰当的拉数据的例子。通常拉数据的时候需要显示表示正在“加载中”的动画,加载数据出错则需要提示相关的错误,成功则显示数据。这里我们假设有2个状态值isLoading和 error,加载成功的数据则是由react-redux从props里传进来:

  • 加载中:isLoading = true; error = undefined; data = undefined | { ... };
  • 成功:isLoading = false; error = undefined; data = { ... };
  • 失败:isLoading = false; error = ‘Error message.’; data = { ... };

比较容易想到的做法是用useState,如下:

function MyComponent({ loadDataAction, data }) {
   const [isLoading, setIsLoading] = useState(false);
   const [error, setError] = useState(undefined);
   const loadData = useCallback(async () => {
        setIsLoading(true);
        setError(undefined);
        try {
            await loadDataAction();
        } catch(err) {
            setError(err);
        } finally {
            setIsLoading(false);
        }
   });
   return (
        <div>
            <button onClick={loadData} disabled={isLoading}>Load Data</button>
            {error ? (
                <p>{error}</p>
                ) :  
                data ? (
                    <h3>Loaded data below</h3>
                    <div>{data}</div>
                ) : null
        </div>
   );
}

改成useReducer的话每种操作对状态造成什么影响会更加清晰,并且易于重用:

function loadStateReducer(state, action) {
    switch (action.type) {
        case ‘loading’: 
            return {
                isLoading: true,
                error: undefined
            };
        case ‘success’;
            return {
                isLoading: false,
                error: undefined
            };
        case ‘error’:
            return {
                isLoading: false,
                error: action.error
            };
    }
    return state;
}

function MyComponent({ loadDataAction, data }) {
   const [state, dispatch] = useReducer(loadStateReducer, {
        isLoading: false,
        error: undefined
   });
   const { isLoading, error } = state;
   const loadData = useCallback(async () => {
        dispatch({ type: ‘loading’ });
        try {
            await loadDataAction();
            dispatch({ type: ‘success’ });
        } catch(err) {
            dispatch({ type: ‘error’, error: err });
        }
   });
   return (
        <div>
            <button onClick={loadData} disabled={isLoading}>Load Data</button>
            {error ? (
                <p>{error}</p>
                ) :  
                data ? (
                    <h3>Loaded data below</h3>
                    <div>{data}</div>
                ) : null
        </div>
   );
}

讲真,用useState设置一个object state是等同的操作。所以用哪个看个人喜好吧。

这里要注意的是,当两个state值有相关性(比如B是根据A计算得到的结果),那就有必要考虑用object类型的state或者useReducer,否则容易遇到上下文不一致导致出现非自己期望的结果(遇到过一次,容我想到恰当的例子再谈这个问题)。

另外,我说这个例子不恰当是因为这个例子中没有在使用异步操作后考虑component实例是否还是挂载的状态,可能导致内存泄漏,并且也会有相关的报错。

你可能感兴趣的:(学习笔记)