【react】 useReducer 集中式管理组件的状态

78 阅读3分钟

useReducer 集中式管理组件的状态

1️⃣ 基本概念

useReducer 是 React 提供的 管理组件状态的一种 Hook,功能上类似于 useState,但是它更适合状态逻辑复杂或依赖前一个状态的情况

它的核心思想:

  • 状态(state) 是不可变的。
  • 状态更新逻辑 写在一个函数里(Reducer)。
  • 通过 dispatch(action) 来触发状态更新,而不是直接 setState。

1.1 函数签名

const [state, dispatch] = useReducer(reducer, initialState, init?)

参数解释:

参数说明
reducer一个函数 (state, action) => newState,决定状态如何更新
initialState初始状态
init可选函数,用于惰性初始化状态(通常不需要)

返回值:

  • state:当前状态(只读)
  • dispatch:派发 action 的函数,用来触发状态更新

1.2 Reducer 函数

Reducer 是状态更新逻辑的核心:

function reducer(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;
  }
}

特点:

  • 不直接修改 state,而是返回一个新的状态对象
  • 通常使用 switch(action.type) 来判断不同动作。
  • action 可以是对象,也可以是任意自定义格式(推荐对象,包含 type 字段)。

1.3 最简单的例子

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(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;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, 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>
  );
}

export default Counter;

✅ 可以看到:

  • 所有状态更新逻辑都集中在 reducer 里。
  • 组件内部不需要关心更新细节,只需要 dispatch 对应的 action。

2️⃣ useReducer vs useState

方面useStateuseReducer
适用场景简单状态(计数器、输入框)复杂状态逻辑、多个子值依赖前一个状态
状态更新setState(newValue)dispatch(action)
状态逻辑分散在多个 setState集中在 reducer 函数
可读性简单状态逻辑清晰、易于维护
性能优化不如 useReducerdispatch 不会改变引用,可避免重复渲染

总结

  • 简单场景用 useState 就够了。
  • 状态更新复杂(多分支、多子值依赖)用 useReducer 更清晰。

3️⃣ 更复杂的例子:表单管理

假设有一个复杂表单:

import React, { useReducer } from 'react';

const initialState = {
  username: '',
  email: '',
  password: '',
};

function reducer(state, action) {
  switch (action.type) {
    case 'updateField':
      return { ...state, [action.field]: action.value };
    case 'reset':
      return initialState;
    default:
      return state;
  }
}

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

  const handleChange = (e) => {
    dispatch({ type: 'updateField', field: e.target.name, value: e.target.value });
  };

  const handleReset = () => {
    dispatch({ type: 'reset' });
  };

  return (
    <form>
      <input name="username" value={state.username} onChange={handleChange} placeholder="Username" />
      <input name="email" value={state.email} onChange={handleChange} placeholder="Email" />
      <input name="password" type="password" value={state.password} onChange={handleChange} placeholder="Password" />
      <button type="button" onClick={handleReset}>Reset</button>
    </form>
  );
}

export default Form;

✅ 优点:

  • 所有字段更新逻辑集中在 reducer 中。
  • 方便扩展,比如添加表单校验、提交状态等。

4️⃣ useReducer 的惰性初始化

有时候初始化 state 很复杂,可以用第三个参数 init

function init(initialCount) {
  return { count: initialCount };
}

const [state, dispatch] = useReducer(reducer, 0, init);

好处:

  • 性能优化:只有第一次 render 会调用 init
  • 避免每次 render 都执行复杂计算。

5️⃣ 总结

  1. useReducer 是 useState 的增强版,适合复杂状态逻辑。

  2. 核心概念

    • Reducer:决定状态如何变化
    • State:组件状态(只读)
    • Dispatch:触发状态更新
  3. 优点

    • 状态更新逻辑集中,易维护
    • 方便管理复杂或嵌套状态
    • 和 Redux 思想一致,易迁移
  4. 使用场景

    • 多个状态值相互依赖

    • 多分支的状态更新逻辑

    • 想用 dispatch(action) 统一管理

总结

useReducer 管理的 state 本身只会在 dispatch 被调用时改变。每次组件重新渲染时,整个组件函数会重新执行,但 state 不会自动变化,仍然保持最新的值

import FileUpload from './components/FileUpload'
import './App.css'
import { useReducer } from 'react'

const initData = [
  { name: '小满(只)', price: 100, count: 1, id: 1, isEdit: false, editPrice: '' },
  { name: '中满(只)', price: 200, count: 1, id: 2, isEdit: false, editPrice: '' },
  { name: '大满(只)', price: 300, count: 1, id: 3, isEdit: false, editPrice: '' }
]
const reducer = (state, action) => {
  switch (action.type) {
    case 'addCount':
      return state.map(item => item.id === action.payload.id ? {...item,count:item.count+1} : item)
    case 'subCount':
      return state.map(item => item.id === action.payload.id ? {...item,count:item.count-1} : item)
    case 'delete':
      return state.filter(item => item.id !== action.payload.id)
    case 'edit':
      return state.map(item => 
        item.id === action.payload.id 
          ? {...item, isEdit: true, editPrice: String(item.price)} 
          : item
      )
    case 'updatePrice':
      return state.map(item => 
        item.id === action.payload.id 
          ? {...item, editPrice: action.payload.price} 
          : item
      )
    case 'save':
      return state.map(item => 
        item.id === action.payload.id 
          ? {
              ...item, 
              isEdit: false, 
              price: Number(item.editPrice) || 0,
              editPrice: ''
            } 
          : item
      )
    default:
      return state
  }
}




function App() {
  const [data, dispatch] = useReducer(reducer, initData)
  return (
    <div className="app">
      <table border="1">
      <thead>
        <tr>
          <th>商品名称</th>
          <th>商品价格</th>
          <th>商品数量</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        {data.map(item => (
          <tr key={item.id}>
            <td>{item.name}</td>
            <td>
              {item.isEdit ? (
                <input 
                  type="number" 
                  value={item.editPrice}  
                  onChange={(e) => dispatch({
                    type: 'updatePrice', 
                    payload: { id: item.id, price: e.target.value }
                  })}
                />
              ) : (
                item.price
              )}
            </td>
            <td>{item.count}</td>
            <td>
              <button onClick={() => {
                if (item.isEdit) {
                  dispatch({ type: 'save', payload: item })
                } else {
                  dispatch({ type: 'edit', payload: item })
                }
              }}>
                {item.isEdit ? '保存' : '编辑'}
              </button>
              <button onClick={() => dispatch({ type: 'delete', payload: item })}>删除</button>
              <button onClick={() => dispatch({ type: 'addCount', payload: item })}>数量加1</button>
              <button onClick={() => dispatch({ type: 'subCount', payload: item })}>数量减1</button>
            </td>
          </tr>
        ))}
      </tbody>
      </table>
    </div>
  )
}

export default App