为什么在useReducer挂钩中使用switch语句来管理状态?

时间:2020-10-12 22:24:56

标签: javascript reactjs react-hooks use-reducer

让我们看一下使用useReducer钩子进行状态管理的以下两种方法,它们都具有相同的作用:单击添加按钮到+ 1,然后单击减去按钮到-1:

  1. 带开关:

const myReducer = (state, action) => {
    switch (action.type) {
        case 'add':
            return {
                count: state.count + 1
            }
        case 'subtract':
            return {
                count: state.count - 1
            }
        default:
            return state
    }
}

const Reducer = () => {
    const [state, dispatch] = useReducer(myReducer, { count: 0 });

    return (
        <>
            <button onClick={() => dispatch({ type: 'add' })}>Add</button>
            <button onClick={() => dispatch({ type: 'subtract' })}>Subtract</button>
            <p>{state.count}</p>
        </>
    )
}

  1. 不带开关

const Reducer2 = () => {
    const [state, setState] = useReducer(
        (state, newState) => ({ ...state, ...newState }),
        { count: 0 }
    );
    
    return (
        <>
            <button onClick={() => setState({count: state.count + 1})}>Add</button>
            <button onClick={() => setState({count: state.count - 1})}>Subtract</button>
            <p>{state.count}</p>
        </>
    )

}

哪个是管理国家的更好方法?我更喜欢2,因为它更简单,它允许我们以“类组件”的方式管理状态。我不明白为什么需要1:它需要一个switch语句,它很复杂;如果要添加状态,则需要一种新的情况。这一切看起来都很麻烦。

编辑:我知道这是一个简单的示例,不需要使用useReducer,而useState更好,但是我真正想讨论的是,当存在多个状态时,哪个是更好吗?

1 个答案:

答案 0 :(得分:1)

Switch语句通常在useReducer中用作redux中的reducer的残余。

第二个示例是在函数组件中使用近似this.setState的好方法,因为useState仅为单个值而设计,因为没有旧状态和新的。在此答案的结尾,我将其扩展了一步。

关于哪个问题最适合管理useReducer中的状态,这实际上取决于您要使用它的方式以及使用方式。您不仅限于这两种类型的事物:您可以在其中使用任何东西。我很幸运在useReducer中使用redux toolkit的createSlice作为Immer的类型安全的简化器,以简化不可变性。

我不明白为什么需要1:它需要一个switch语句,该语句 很复杂如果要添加状态,则需要一个新案例

如果为状态的每个部分编写一个减速器用例,则可以。这非常麻烦,我肯定会以其他方式来做。使用第一种方法的最佳方法是,当您需要处理更复杂的情况时,或者使用通用方法来处理更多状态选项时,该方法是最好的。

React docs中所述:

当您遇到复杂的情况时,

useReducer通常比useState更可取 涉及多个子值或下一个状态的状态逻辑 取决于前一个。 useReducer还可以让您优化 触发深层更新的组件的性能,因为您可以 向下传递调度而不是回调。

它们是功能组件的非常功能强大的补充,允许使用更简单的方法来处理复杂的逻辑或逻辑连接的值。当然,是否使用它完全取决于您,useReducer做的任何事情都可以用useState来完成,这些const { useRef, useReducer } = React; const dataReducer = (state, action) => { switch (action.type) { case 'toggle': return { ...state, [action.name]: !state[action.name], }; case 'change': return { ...state, [action.name]: action.value, }; default: return state; } }; function Example() { const [data, dispatch] = useReducer(dataReducer, { check1: false, check2: false, check3: false, input1: '', input2: '', input3: '', }); const throwErrorRef = useRef(null); const handleChange = function (e) { const { name, value } = e.currentTarget; dispatch({ type: 'change', name, value }); }; const handleToggle = function (e) { const { name } = e.currentTarget; dispatch({ type: 'toggle', name }); }; const checkBoxes = ['check1', 'check2', 'check3']; const inputs = ['input1', 'input2', 'input3']; return ( <div> {checkBoxes.map((name) => ( <label> {name} <input type="checkbox" name={name} onChange={handleToggle} checked={data[name]} /> </label> ))} <br /> {inputs.map((name) => ( <label> {name} <input type="text" name={name} onChange={handleChange} value={data[name]} /> </label> ))} </div> ); } ReactDOM.render(<Example />, document.getElementById('root'));具有不同的样板和逻辑。

使用许多状态属性的通用方法:

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
const { useRef, useReducer } = React;
const dataReducer = (state, action) => {
  switch (action.type) {
    case 'fetchStart':
      return {
        loading: true,
        data: null,
        error: null,
      };
    case 'fetchError':
      if (!state.loading) {
        return state;
      }
      return {
        loading: false,
        data: null,
        error: action.payload.error,
      };
    case 'fetchSuccess': {
      if (!state.loading) {
        return state;
      }
      return {
        loading: false,
        data: action.payload.data,
        error: null,
      };
    }
    default:
      return state;
  }
};
function Example() {
  const [{ loading, data, error }, dispatch] = useReducer(dataReducer, {
    loading: false,
    data: null,
    error: null,
  });
  const throwErrorRef = useRef(null);
  const handleFetch = function () {
    if (loading) {
      return;
    }
    dispatch({ type: 'fetchStart' });
    const timeoutId = setTimeout(() => {
      dispatch({ type: 'fetchSuccess', payload: { data: { test: 'Text' } } });
    }, 5000);
    throwErrorRef.current = () => {
      clearTimeout(timeoutId);
      dispatch({ type: 'fetchError', payload: { error: 'Oh noes!' } });
    };
  };
  const handleFetchError = function () {
    throwErrorRef.current && throwErrorRef.current();
  };
  return (
    <div>
      <button onClick={handleFetch}>Start Loading</button>
      <button onClick={handleFetchError}>Throw an error in the fetch!</button>
      <div>loading: {`${loading}`}</div>
      <div>error: {error}</div>
      <div>data: {JSON.stringify(data)}</div>
    </div>
  );
}

ReactDOM.render(<Example />, document.getElementById('root'));

对于稍微复杂的逻辑,这是数据获取的示例:

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
const [,forceUpdate] = useReducer((state)=>state+1,0);
// Example use: forceUpdate();

我使用的一个简单的方法是强制更新,它只是增加一个值以使组件重新呈现。

setState

我修改了您的示例2,以添加对更新状态的函数方法的支持,以便更接近使用useReducer进行的this.setState仿制。我想不出一种使回调工作正常的方法(const { useRef, useReducer } = React; const stateReducer = (state, action) => { if (typeof action === 'function') { action = action(state); } return { ...state, ...action }; }; const useMergeState = (initialState) => { return useReducer(stateReducer, initialState); }; function Example() { const [state, setState] = useMergeState({ loading: false, data: null, error: null, count: 0, }); const throwErrorRef = useRef(null); const handleFetch = function () { if (state.loading) { return; } setState({ loading: true }); const timeoutId = setTimeout(() => { setState({ data: { text: 'A super long text', loading: false, error: null }, }); }, 5000); throwErrorRef.current = () => { clearTimeout(timeoutId); setState({ error: 'Oh noes!', loading: false, data: null }); }; }; const handleFetchError = function () { throwErrorRef.current && throwErrorRef.current(); }; const incrementCount = function () { setState((state) => ({ count: state.count + 1 })); setState((state) => ({ count: state.count + 1 })); }; return ( <div> <button onClick={handleFetch}>Start Loading</button> <button onClick={handleFetchError}>Throw an error in the fetch!</button> <div>loading: {`${state.loading}`}</div> <div>error: {state.error}</div> <div>data: {JSON.stringify(state.data)}</div> <button onClick={incrementCount}>increase count by 2</button> <div>count: {state.count}</div> </div> ); } ReactDOM.render(<Example />, document.getElementById('root'));中的第二个参数)

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
--