为什么用 useReducer 而不用 useState?

一、简单场景:用 useState

1. 只有一个简单的计数器

适合: 状态简单,就一两个值

scss 复制代码
 const [count, setCount] = useState(0)

 setCount(count + 1)  // 简单!

二、 复杂场景:必须用 useReducer

场景1:状态逻辑复杂

用 useState(乱七八糟)

scss 复制代码
const [items, setItems] = useState([])
const [total, setTotal] = useState(0)
const [discount, setDiscount] = useState(0)
const [tax, setTax] = useState(0)

 // 添加商品 - 要改4个地方!
 const addItem = (item) => {
    setItems([...items, item])
    setTotal(total + item.price)
    setTax((total + item.price) * 0.1)
    setDiscount(calculateDiscount(total + item.price))
    // 容易漏改,容易出 bug!
 }

// 删除商品 - 又要改4个地方!
const removeItem = (id) => {
    const item = items.find(i => i.id === id)
    setItems(items.filter(i => i.id !== id))
    setTotal(total - item.price)
    setTax((total - item.price) * 0.1)
    setDiscount(calculateDiscount(total - item.price))
    // 太容易出错了!
}

用 useReducer(清晰)

php 复制代码
 const [state, dispatch] = useReducer(reducer, {
   items: [],
   total: 0,
   discount: 0,
   tax: 0
})

 // 添加商品 - 只需要一句话!
const addItem = (item) => {
   dispatch({ type: 'ADD_ITEM', payload: item })
   // 所有逻辑都在 reducer 里,不会漏
}

// 删除商品 - 也是一句话!
const removeItem = (id) => {
   dispatch({ type: 'REMOVE_ITEM', payload: id })
   // 简单清晰
}

// reducer 里统一处理所有逻辑
function reducer(state, action) {
    switch(action.type) {
      case 'ADD_ITEM':
        const newTotal = state.total + action.payload.price
        return {
          items: [...state.items, action.payload],
          total: newTotal,
          tax: newTotal * 0.1,
          discount: calculateDiscount(newTotal)
        }
      case 'REMOVE_ITEM':
        // 所有相关逻辑都在这里,集中管理
    }
}

场景2:多个操作修改同一个状态

用 useState(代码重复)

scss 复制代码
// 修改名字
const updateName = (name) => {
    setUser({ ...user, name })
}

// 修改年龄
const updateAge = (age) => {
    setUser({ ...user, age })
}

// 修改城市
const updateCity = (city) => {
    setUser({ ...user, city })
}

// 重置
const reset = () => {
    setUser({ name: '', age: 0, city: '' })
}

// 每个函数都在写 setUser({...user, ...}),好烦!

用 useReducer(统一管理)

php 复制代码
 const [user, dispatch] = useReducer(userReducer, { name: '', age: 0, city: '' })

// 一个函数搞定所有操作
const update = (field, value) => {
    dispatch({ type: 'UPDATE', field, value })
}

// 或者更清晰的方式
dispatch({ type: 'UPDATE_NAME', payload: '张三' })
dispatch({ type: 'UPDATE_AGE', payload: 25 })
dispatch({ type: 'RESET' })

// reducer 统一处理
function userReducer(state, action) {
    switch(action.type) {
      case 'UPDATE_NAME':
        return { ...state, name: action.payload }
      case 'UPDATE_AGE':
        return { ...state, age: action.payload }
      case 'UPDATE_CITY':
        return { ...state, city: action.payload }
      case 'RESET':
        return { name: '', age: 0, city: '' }
    }
}

场景3:下一个状态依赖当前状态

用 useState(可能出错)

scss 复制代码
const [count, setCount] = useState(0)

// 快速点3次按钮
onClick={() => {
    setCount(count + 1)  // count = 0,变成 1
    setCount(count + 1)  // count 还是 0,又变成 1
    setCount(count + 1)  // count 还是 0,还是变成 1
    // 最后 count = 1,而不是 3!
  }}

// 需要用函数式更新
setCount(prev => prev + 1)  // 这样才对

用 useReducer(不会出错)

php 复制代码
const [state, dispatch] = useReducer(reducer, { count: 0 })

onClick={() => {
    dispatch({ type: 'INCREMENT' })
    dispatch({ type: 'INCREMENT' })
    dispatch({ type: 'INCREMENT' })
    // 不会出错,肯定是 +3
}}

function reducer(state, action) {
    switch(action.type) {
      case 'INCREMENT':
        return { count: state.count + 1 }  // 总是用最新的 state
    }
}

三、 什么时候用 useReducer or useState?

1. 用 useState ✅

scss 复制代码
 // 1. 简单值
const [name, setName] = useState('')
const [age, setAge] = useState(0)
const [isOpen, setIsOpen] = useState(false)

// 2. 独立的状态
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)

// 3. 不相关的多个值
const [count1, setCount1] = useState(0)
const [count2, setCount2] = useState(0)

2. 用 useReducer ✅

php 复制代码
// 1. 购物车(多个相关值)
const [cart, dispatch] = useReducer(cartReducer, {
    items: [],
    total: 0,
    discount: 0
})

// 2. 表单(多个字段)
const [form, dispatch] = useReducer(formReducer, {
    name: '',
    email: '',
    phone: '',
    address: ''
})

// 3. 复杂的状态机
const [state, dispatch] = useReducer(reducer, {
    status: 'idle',  // idle -> loading -> success -> error
    data: null,
    error: null
})

四、形象比喻

1. useState = 自己管钱

scss 复制代码
// 你有3个钱包
const [工资, set工资] = useState(5000)
const [奖金, set奖金] = useState(1000)
const [存款, set存款] = useState(10000)

// 发工资了
set工资(6000)
set存款(存款 + 1000)  // 还要记得更新存款

2.useReducer = 请财务帮你管钱

scss 复制代码
 const [账户, dispatch] = useReducer(财务管理, {
    工资: 5000,
    奖金: 1000,
    存款: 10000
})

// 发工资了,告诉财务一声
dispatch({ type: '发工资', 金额: 1000 })

// 财务会自动帮你:
// 1. 工资加1000
// 2. 存款也加1000
// 3. 更新账户余额
// 不会漏掉任何步骤!

五、总结

为什么用 useReducer 而不用 useState?

相关推荐
Mr Xu_5 分钟前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝8 分钟前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions17 分钟前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发17 分钟前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
程序员猫哥_24 分钟前
HTML 生成网页工具推荐:从手写代码到 AI 自动生成网页的进化路径
前端·人工智能·html
龙飞0525 分钟前
Systemd -systemctl - journalctl 速查表:服务管理 + 日志排障
linux·运维·前端·chrome·systemctl·journalctl
我爱加班、、30 分钟前
Websocket能携带token过去后端吗
前端·后端·websocket
AAA阿giao30 分钟前
从零拆解一个 React + TypeScript 的 TodoList:模块化、数据流与工程实践
前端·react.js·ui·typescript·前端框架
杨超越luckly37 分钟前
HTML应用指南:利用GET请求获取中国500强企业名单,揭秘企业增长、分化与转型的新常态
前端·数据库·html·可视化·中国500强
hedley(●'◡'●)1 小时前
基于cesium和vue的大疆司空模仿程序
前端·javascript·vue.js·python·typescript·无人机