React 中的 useReducer Hook 的使用场景、工作原理以及与 useState 的对比

42 阅读2分钟

useReducer 是 React 中用于处理复杂状态逻辑的 Hook,它基于 Redux 的设计理念。让我们通过实例深入理解:

1. 基本语法和工作原理

import React, { useReducer } from 'react';

// 定义 reducer 函数
const counterReducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    case 'RESET':
      return { count: 0 };
    default:
      return state;
  }
};

// 初始状态
const initialState = { count: 0 };

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
}

2. 处理复杂状态逻辑

import React, { useReducer } from 'react';

// 定义 action types
const ACTIONS = {
  ADD_TODO: 'ADD_TODO',
  TOGGLE_TODO: 'TOGGLE_TODO',
  DELETE_TODO: 'DELETE_TODO',
  UPDATE_TODO: 'UPDATE_TODO'
};

// 定义 reducer
const todoReducer = (state, action) => {
  switch (action.type) {
    case ACTIONS.ADD_TODO:
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: Date.now(),
            text: action.payload,
            completed: false
          }
        ]
      };
      
    case ACTIONS.TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
      
    case ACTIONS.DELETE_TODO:
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };
      
    case ACTIONS.UPDATE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload.id
            ? { ...todo, text: action.payload.text }
            : todo
        )
      };
      
    default:
      return state;
  }
};

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, { todos: [] });
  const [input, setInput] = React.useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (input.trim()) {
      dispatch({ type: ACTIONS.ADD_TODO, payload: input });
      setInput('');
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Add todo"
        />
        <button type="submit">Add</button>
      </form>

      <ul>
        {state.todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{
                textDecoration: todo.completed ? 'line-through' : 'none'
              }}
              onClick={() => dispatch({
                type: ACTIONS.TOGGLE_TODO,
                payload: todo.id
              })}
            >
              {todo.text}
            </span>
            <button
              onClick={() => dispatch({
                type: ACTIONS.DELETE_TODO,
                payload: todo.id
              })}
            >
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

3. 使用 useReducer 处理异步操作

import React, { useReducer, useEffect } from 'react';

const dataReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_START':
      return {
        ...state,
        isLoading: true,
        error: null
      };
    case 'FETCH_SUCCESS':
      return {
        isLoading: false,
        error: null,
        data: action.payload
      };
    case 'FETCH_ERROR':
      return {
        isLoading: false,
        error: action.payload,
        data: null
      };
    default:
      return state;
  }
};

function DataFetcher({ url }) {
  const [state, dispatch] = useReducer(dataReducer, {
    isLoading: false,
    error: null,
    data: null
  });

  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: 'FETCH_START' });
      try {
        const response = await fetch(url);
        const data = await response.json();
        dispatch({ type: 'FETCH_SUCCESS', payload: data });
      } catch (error) {
        dispatch({ type: 'FETCH_ERROR', payload: error.message });
      }
    };

    fetchData();
  }, [url]);

  if (state.isLoading) return <div>Loading...</div>;
  if (state.error) return <div>Error: {state.error}</div>;
  if (!state.data) return null;

  return (
    <div>
      {/* 渲染数据 */}
      <pre>{JSON.stringify(state.data, null, 2)}</pre>
    </div>
  );
}

4. useReducer vs useState 对比

  1. 使用 useState 的场景
function SimpleCounter() {
  const [count, setCount] = useState(0);
  
  // 简单状态更新
  const increment = () => setCount(count + 1);
  
  return (
    <button onClick={increment}>
      Count: {count}
    </button>
  );
}
  1. 使用 useReducer 的场景
function ComplexForm() {
  const [formState, dispatch] = useReducer(formReducer, {
    username: '',
    password: '',
    email: '',
    errors: {},
    isSubmitting: false
  });

  const handleSubmit = async (e) => {
    e.preventDefault();
    dispatch({ type: 'SUBMIT_START' });
    try {
      await submitForm(formState);
      dispatch({ type: 'SUBMIT_SUCCESS' });
    } catch (error) {
      dispatch({ type: 'SUBMIT_ERROR', payload: error.errors });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* 表单内容 */}
    </form>
  );
}

选择 useReducer 的情况:

  1. 状态逻辑复杂

    • 状态包含多个子值
    • 状态更新涉及复杂的逻辑
    • 需要基于之前的状态进行更新
  2. 相关状态更新

    • 多个状态需要同时更新
    • 状态更新之间有依赖关系
  3. 状态更新可预测

    • 状态更新遵循固定的模式
    • 需要清晰的状态变更追踪
  4. 状态共享需求

    • 需要在深层组件树中共享状态
    • 需要将状态更新逻辑提取到组件外

最佳实践:

  1. Action 类型常量化
const ACTIONS = {
  UPDATE_FIELD: 'UPDATE_FIELD',
  VALIDATE_FORM: 'VALIDATE_FORM',
  SUBMIT_FORM: 'SUBMIT_FORM',
  // ...
};
  1. Action 创建函数
const createUpdateAction = (field, value) => ({
  type: ACTIONS.UPDATE_FIELD,
  payload: { field, value }
});
  1. Reducer 模块化
const reducers = {
  [ACTIONS.UPDATE_FIELD]: (state, action) => ({
    ...state,
    [action.payload.field]: action.payload.value
  }),
  // ...其他 reducers
};

const mainReducer = (state, action) => {
  const handler = reducers[action.type];
  return handler ? handler(state, action) : state;
};

useReducer 是一个强大的状态管理工具,特别适合处理复杂的状态逻辑。它提供了更清晰的状态更新流程,使代码更易维护和测试。但对于简单的状态管理,useState 可能是更好的选择。选择哪个 Hook 应该基于具体的使用场景和需求。