javascript - 为什么 useReducer hook 中使用 switch 语句来管理状态?

标签 javascript reactjs react-hooks use-reducer

让我们看一下以下两种使用 useReducer 钩子(Hook)进行状态管理的方法,它们都做同样的事情:点击添加按钮到 + 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>
        </>
    )
}

  • 不带开关
  • 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 语句;如果想添加状态,就需要一个新的案例。这一切看起来都很麻烦。

    编辑:我知道这是一个简单的例子,不需要使用 useReduceruseState 更好,但我真正想讨论的是,当有多个状态,哪个更好?

    最佳答案

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

    你的第二个例子是在函数组件中使用 this.setState 的近似值的好方法,因为 useState 实际上只是为单个值设计的,因为有新旧状态没有浅层融合。我在本答案的末尾将这一点进一步扩展。

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

    I don't understand why 1 is needed: it needs a switch statement which is complex; if one wants to add state, a new case is needed

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

    React docs 中所写:

    useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

    它们是对功能组件的一个非常强大的补充,并允许以更简单的方式处理复杂的逻辑或逻辑连接的值。当然,是否使用它取决于您,使用 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,添加了对更新状态的函数方法的支持,因此它更接近使用 useReducer 的完整 setState 仿制品。我想不出一个合适的方法来使回调工作(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"/>

    关于javascript - 为什么 useReducer hook 中使用 switch 语句来管理状态?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64326095/

    相关文章:

    javascript - 是否有可能禁用 ionic 2 中的按钮?

    javascript - React setState 在获取数据后不会重新渲染

    node.js - Dockerized NGINX 配置与在 Azure 上运行的 ReactJS 应用程序(容器实例)

    javascript - 已迁移工作流模型和脚本以进行 6.3 到 6.5 AEM 升级,但未拾取脚本

    c# - 开发者工具 (F12) Internet Explorer 空控制台,但 Firefox 不支持

    javascript - 我如何在 Ember 应用程序中使用 Glimmer 组件?

    javascript - Facebook 调试器不拾取 Next.js 的 next-seo 元标记

    reactjs - React Hooks (useState) 和 Mobx [无 mobx-react-lite]

    function - 如何在React功能组件中访问ag-Grid API(useState hook)?

    javascript - componentWillUnmount 与 React useEffect Hook