0

让我们看看以下两种使用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更好的简单示例,但我真正想讨论的是,当有多个状态时,哪个更好?

4

1 回答 1

2

Switch 语句通常用作useReducerredux 中 reducer 的残余。

this.setState您的第二个示例是在函数组件中使用近似值的好方法,因为useState它只是为单个值设计的,因为没有旧状态和新状态的浅合并。在此答案的末尾,我已将其扩展为更进一步。

至于您在 a 中哪个最好管理状态的问题useReducer,这实际上取决于您想将它用于什么以及如何使用它。您不仅限于这两种类型的东西:您可以在其中使用任何东西。我很幸运在 a 中使用redux 工具包的 createSlice 来useReducer使用带有 Immer 的类型安全的 reducer,从而使不变性更容易。

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

如果你为 state 的每个部分写一个 reducer case,是的。这非常麻烦,我肯定会以不同的方式来做。使用第一种方法的最佳方式是当您需要处理更复杂的情况或处理更多状态选项的通用方法时。

正如React 文档中所写:

当您具有涉及多个子值的复杂状态逻辑或下一个状态取决于前一个状态时,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();

我修改了您的示例 2,以添加对更新状态的函数方法的支持,因此它更接近于setState使用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"/>

于 2020-10-13T03:20:52.407 回答