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實例是否還是掛載的狀態,可能導致內存泄漏,並且也會有相關的報錯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章