useReducer+useContext+自定义Hook的完美交响曲

108 阅读5分钟

在React的世界里,状态管理如同乐团的指挥棒,决定着应用的和谐与流畅。今天,我们将深入探索useReduceruseContext这对黄金搭档,看看它们如何携手打造优雅的全局状态管理方案。本文是继 📢 React状态管理新姿势:useReducer与纯函数的完美协奏曲 写得,感兴趣的可以看看这篇文章,更加能够理解useReducer的用法

今天我们要实现的具体效果,是我们老生常谈的todoList功能,涉及自定义Hook、useReducer、useContext的组合使用,废话不多说,我们先来看效果:

1.gif

第一乐章:useReducer - 状态管理的精密机器

想象你有一个复杂的国家需要治理(即应用状态),直接修改国家属性容易引发混乱。useReducer就像建立一套完善的法律体系,通过预定义的规则管理状态变化。

export default function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, {
        id: Date.now(),
        text: action.text,
        done: false
      }]
    case 'TOGGLE_TODO':
      return state.map(todo => 
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      )
    case 'REMOVE_TODO':
      return state.filter(todo => todo.id !== action.id)
    default:
      return state
  }
}

这个reducer函数是状态管理的核心宪法:

  • ADD_TODO:添加新待办事项,使用时间戳作为唯一ID
  • TOGGLE_TODO:切换完成状态,巧妙使用map避免直接修改原数组
  • REMOVE_TODO:过滤删除指定事项,保持数组纯净

通常在开发中,我们把reducer函数,都放在reducers目录下,方便管理

image.png

在自定义hook中,我们封装了reducer的使用:

export function useTodos(initial = []) {
  const [todos, dispatch] = useReducer(todoReducer, initial)

  const addTodo = text => dispatch({ type: 'ADD_TODO', text })
  const toggleTodo = id => dispatch({ type: 'TOGGLE_TODO', id })
  const removeTodo = id => dispatch({ type: 'REMOVE_TODO', id })

  return { todos, addTodo, toggleTodo, removeTodo }
}

比喻时刻:把useReducer想象成一家高级寿司店的操作手册。主厨(reducer)只接受特定格式的订单(action对象),如{type: '切鱼', amount: 3}。学徒(组件)不能随意操作食材(状态),必须通过传票(dispatch)下单。这样确保每份寿司(状态变更)都符合标准流程。

同样的自定义Hook也是,放在统一目录下方便管理,这里我们封装了两个自定义Hook,一个是useTodoContext(用来封装 createContext和useContext的使用方便我们的复用) 一个是useTodos(也就是封装reducer的使用):

image.png

第二乐章:useContext - 跨层级通信的秘密通道

当应用组件树越来越深,props逐层传递就像用竹篮打水——费力且低效。useContext就是为此而生的通信隧道。

首先建立上下文:

// TodoContext.js
export const TodoContext = createContext(null)

然后在顶层提供数据:

// App.jsx
function App() {
  const todosHook = useTodos() //封装好的reducer和状态
  
  return (
    <TodoContext.Provider value={todosHook}>
      <h1>Todo App</h1>
      <AddTodo />
      <TodoList />
    </TodoContext.Provider>
  )
}

最后在任何子组件中获取:

这里我们是封装好的useTodoContext

// hooks/useTodoContext.js
export function useTodoContext() {
  return useContext(TodoContext)
}

比喻时刻:想象useContext是魔法世界的猫头鹰邮政系统。霍格沃茨(App组件)有个中央邮局(Provider),任何学生(子组件)只需说一句"Accio context!"(useContext),就能直接收到包裹(状态),无需经过级长(中间组件)转手。

第三乐章:双剑合璧 - 全局状态管理的交响曲

useReduceruseContext结合,便奏响了状态管理的完美和弦,在组件中使用如行云流水:

// components/AddTodo/index.jsx
export default function AddTodo() {
  const [text, setText] = useState('')
  const { addTodo } = useTodoContext()
  
  const handleSubmit = (e) => {
    e.preventDefault()
    if (text.trim()) {
      addTodo(text.trim())  // 直接调用全局方法
      setText('')
    }
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button type="submit">Add</button>
    </form>
  )
}

列表组件同样优雅:

// components/TodoList/index.jsx
export default function TodoList() {
  const { todos, removeTodo, toggleTodo } = useTodoContext()
  
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <input
            type="checkbox"
            checked={todo.done}
            onChange={() => toggleTodo(todo.id)}
          />
          <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
          <button onClick={() => removeTodo(todo.id)}>remove</button>
        </li>
      ))}
    </ul>
  )
}

比喻时刻:这就像城市供电系统。发电厂(useReducer)生产电力(状态),电网(Context)将电力输送到千家万户(组件)。当你按下开关(触发action),电流(状态变更)立即点亮整个城市的灯泡(UI更新),无需自己购买发电机(本地状态)。


第四乐章:自定义Hook - 状态逻辑的封装艺术

自定义Hook是React的瑞士军刀,让状态逻辑可复用且解耦:

// hooks/useTodos.js
export function useTodos(initial = []) {
  const [todos, dispatch] = useReducer(todoReducer, initial)
  
  // 封装操作函数
  const addTodo = text => dispatch({ type: 'ADD_TODO', text })
  const toggleTodo = id => dispatch({ type: 'TOGGLE_TODO', id })
  const removeTodo = id => dispatch({ type: 'REMOVE_TODO', id })

  return { todos, addTodo, toggleTodo, removeTodo }
}

这带来了三大优势:

  1. 关注点分离:组件专注UI渲染,逻辑由hook处理
  2. DRY原则:避免在多个组件中重复相同的状态逻辑
  3. 可测试性:纯状态逻辑可以独立于组件进行测试

比喻时刻:把自定义Hook看作智能手机的操作系统。组件是手机APP(微信、相机),hook是底层系统服务(GPS、蓝牙)。APP不需要知道定位如何实现,只需调用"获取位置"接口。换手机(组件)时,系统服务(hook)保持稳定不变。


终章:模式优势与适用场景

模式优势对比表

特性useState本地状态Redux全局状态useReducer+Context
学习曲线★☆☆☆☆ (简单)★★★★☆ (复杂)★★☆☆☆ (中等)
代码量中等
跨组件通信困难优秀优秀
中间件支持丰富需手动实现
适用场景简单组件大型应用中小型应用

落幕:思考与展望

通过今天的探索,我们见证了useReduceruseContext如何优雅地解决React状态管理难题。它们像阴阳两极——useReducer提供结构化的状态更新,useContext提供高效的跨组件通信。

在项目实践中,我发现这种模式特别适合:

  1. 当props drilling超过三层组件时
  2. 状态更新逻辑复杂涉及多个子值时
  3. 需要重用相同状态逻辑的多个组件

随着React生态发展,虽然出现了像Recoil、Jotai等新锐状态管理库,但useReducer+Context作为官方解决方案,凭借其零依赖、概念简洁、与React深度集成的优势,依然是中小型应用的最佳选择。