WEBKT

React Hooks useReducer 快速上手教程:从入门到实践,案例驱动

156 0 0 0

useReducer 是 React Hooks 中一个非常强大的 Hook,但对于初学者来说,它可能会显得比较抽象。本文将通过一系列由浅入深的例子,帮助你快速掌握 useReducer 的用法,并了解它在实际项目中的应用。

1. 什么是 useReducer?

useReduceruseState 的一个替代方案。它更适用于状态逻辑较为复杂,且包含多个子状态或者下一个 state 依赖于之前的 state 的情况。useReducer 接收一个 reducer 函数和一个初始 state 作为参数,返回当前 state 和一个 dispatch 函数。

const [state, dispatch] = useReducer(reducer, initialArg, init);
  • reducer: 一个函数,接收 state 和 action,返回一个新的 state。
  • initialArg: 初始 state。
  • init: 一个可选的函数,用于延迟初始化 state。
  • state: 当前的 state。
  • dispatch: 一个函数,用于触发 state 的更新。

2. useReducer vs useState:如何选择?

useState 适合管理简单的、单一的状态。例如,一个简单的计数器,或者一个表单的输入框。

const [count, setCount] = useState(0);

useReducer 更适合管理复杂的状态,特别是当状态的更新逻辑比较复杂,或者需要依赖之前的状态时。例如,一个包含多个状态的表单,或者一个需要进行复杂计算的状态。

简单来说:

  • 状态简单,更新逻辑简单: 选择 useState
  • 状态复杂,更新逻辑复杂: 选择 useReducer

3. useReducer 的基本使用

3.1. 计数器示例

我们先从一个最简单的计数器示例开始,来理解 useReducer 的基本用法。

import React, { useReducer } from 'react';

// 1. 定义初始 state
const initialState = { count: 0 };

// 2. 定义 reducer 函数
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
};

function Counter() {
  // 3. 使用 useReducer Hook
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

export default Counter;

代码解释:

  1. 定义初始 state: initialState 定义了计数器的初始值为 0。
  2. 定义 reducer 函数: reducer 函数接收当前的 state 和一个 action,根据 action.type 来更新 state。这里定义了两个 action:incrementdecrement,分别用于增加和减少计数器的值。
  3. 使用 useReducer Hook: useReducer Hook 接收 reducer 函数和 initialState 作为参数,返回当前的 statedispatch 函数。
  4. dispatch 函数: dispatch 函数用于触发 state 的更新。它接收一个 action 对象作为参数,这个 action 对象会被传递给 reducer 函数。

3.2. 延迟初始化 state

useReducer 还可以接收第三个参数 init,用于延迟初始化 state。这在初始 state 的计算比较复杂时非常有用。

import React, { useReducer } from 'react';

const initialState = { count: 0 };

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + action.payload };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return initialState;
    default:
      throw new Error();
  }
};

// 初始化函数
const init = (initialState) => {
  return {count: initialState.count + 10};
};

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

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment', payload: 5 })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

export default Counter;

代码解释:

  • init 函数接收 initialState 作为参数,并返回一个新的 state。在这个例子中,我们将初始的 count 值加上 10。
  • dispatch 的 action 中可以携带 payload,方便传递数据给 reducer。例如,在 increment action 中,我们传递了一个 payload 值为 5,表示每次增加 5。

4. useReducer 的高级用法

4.1. TODO List 示例

接下来,我们用 useReducer 实现一个简单的 TODO List,来展示 useReducer 在更复杂场景下的应用。

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

// 定义 action 类型
const ACTIONS = {
  ADD_TODO: 'add_todo',
  TOGGLE_TODO: 'toggle_todo',
  DELETE_TODO: 'delete_todo'
}

// reducer 函数
function reducer(todos, action) {
  switch (action.type) {
    case ACTIONS.ADD_TODO:
      return [...todos, newTodo(action.payload.name)]
    case ACTIONS.TOGGLE_TODO:
      return todos.map(todo => {
        if (todo.id === action.payload.id) {
          return { ...todo, complete: !todo.complete }
        }
        return todo
      })
    case ACTIONS.DELETE_TODO:
      return todos.filter(todo => todo.id !== action.payload.id)
    default:
      return todos
  }
}

// 创建 Todo 对象
function newTodo(name) {
  return { id: Date.now(), name: name, complete: false }
}

function TodoList() {
  const [name, setName] = useState('')
  const [todos, dispatch] = useReducer(reducer, [])

  const handleSubmit = (e) => {
    e.preventDefault()
    dispatch({ type: ACTIONS.ADD_TODO, payload: { name: name } })
    setName('')
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={name}
          onChange={e => setName(e.target.value)}
        />
      </form>
      {todos.map(todo => {
        return <Todo key={todo.id} todo={todo} dispatch={dispatch} />
      })}
    </div>
  )
}

function Todo({ todo, dispatch }) {
  return (
    <div>
      <span style={{ color: todo.complete ? '#AAA' : '#000' }}>
        {todo.name}
      </span>
      <button onClick={() => dispatch({ type: ACTIONS.TOGGLE_TODO, payload: { id: todo.id } })}>
        Toggle
      </button>
      <button onClick={() => dispatch({ type: ACTIONS.DELETE_TODO, payload: { id: todo.id } })}>
        Delete
      </button>
    </div>
  )
}

export default TodoList;

代码解释:

  1. 定义 action 类型: 使用常量来定义 action 的类型,可以避免拼写错误。
  2. reducer 函数: reducer 函数接收 todos 数组和 action,根据 action.type 来更新 todos 数组。这里定义了三个 action:ADD_TODOTOGGLE_TODODELETE_TODO,分别用于添加、切换完成状态和删除 TODO。
  3. TodoList 组件: 使用 useReducer Hook 管理 todos 数组。同时使用 useState Hook 管理输入框的值。
  4. Todo 组件: 展示单个 TODO,并提供切换完成状态和删除 TODO 的按钮。

4.2. 使用 Context 共享 State 和 Dispatch

当你的应用比较复杂,需要在多个组件之间共享 state 和 dispatch 时,可以使用 Context API。

import React, { useReducer, createContext, useContext } from 'react';

// 定义 action 类型
const ACTIONS = {
  INCREMENT: 'increment',
  DECREMENT: 'decrement',
};

// 创建 Context
const CounterContext = createContext();

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

// 创建 Provider 组件
function CounterProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <CounterContext.Provider value={{ state, dispatch }}>
      {children}
    </CounterContext.Provider>
  );
}

// 创建 Consumer Hook
function useCounter() {
  return useContext(CounterContext);
}

// 使用 Counter 组件
function Counter() {
  const { state, dispatch } = useCounter();

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

// 使用 AnotherCounter 组件
function AnotherCounter() {
  const { state, dispatch } = useCounter();

  return (
    <div>
      Another Counter: {state.count}
      <button onClick={() => dispatch({ type: ACTIONS.INCREMENT })}>+</button>
      <button onClick={() => dispatch({ type: ACTIONS.DECREMENT })}>-</button>
    </div>
  );
}

function App() {
  return (
    <CounterProvider>
      <Counter />
      <AnotherCounter />
    </CounterProvider>
  );
}

export default App;

代码解释:

  1. 创建 Context: 使用 createContext 创建一个 Context 对象。
  2. 创建 Provider 组件: CounterProvider 组件使用 useReducer Hook 管理 state 和 dispatch,并将它们通过 Context 传递给子组件。
  3. 创建 Consumer Hook: useCounter Hook 使用 useContext Hook 消费 Context,并返回 state 和 dispatch。
  4. Counter 和 AnotherCounter 组件: 这两个组件都可以使用 useCounter Hook 访问和修改同一个 state。

5. 总结

useReducer 是一个非常强大的 Hook,可以帮助你更好地管理复杂的状态逻辑。通过本文的例子,你应该已经掌握了 useReducer 的基本用法和高级用法。希望你能在实际项目中灵活运用 useReducer,提升你的 React 开发效率。

要点回顾:

  • useReducer 适合管理复杂状态,尤其是当状态的更新逻辑比较复杂,或者需要依赖之前的状态时。
  • useReducer 接收一个 reducer 函数和一个初始 state 作为参数,返回当前 state 和一个 dispatch 函数。
  • dispatch 函数用于触发 state 的更新。它接收一个 action 对象作为参数,这个 action 对象会被传递给 reducer 函数。
  • 可以使用 Context API 在多个组件之间共享 state 和 dispatch。

希望这篇文章能够帮助你快速上手 useReducer。 如果你觉得这篇文章对你有帮助,欢迎点赞、评论和分享!

Hook大师 React HooksuseReducer前端开发

评论点评