为什么用 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?

相关推荐
qingyun9895 分钟前
Web Components 实战:创建自定义比例条组件
前端
前端小超超5 分钟前
ionic + vue3 + capacitor遇到backButton问题
前端·javascript·vue.js
GIS之路6 分钟前
GDAL 空间关系解析
前端
布列瑟农的星空33 分钟前
WebAssembly入门(一)——Emscripten
前端·后端
贵州数擎科技有限公司37 分钟前
一批优质 AI 域名转让(.ai)|适合 AI 创业 / 产品 / 公司品牌
前端
小二·43 分钟前
微前端架构完全指南:qiankun 与 Module Federation 双方案深度对比(Vue 3 + TypeScript)
前端·架构·typescript
EndingCoder1 小时前
枚举类型:常量集合的优雅管理
前端·javascript·typescript
Electrolux1 小时前
[wllama]纯前端实现大语言模型调用:在浏览器里跑 AI 是什么体验。以调用腾讯 HY-MT1.5 混元翻译模型为例
前端·aigc·ai编程
sanra1231 小时前
前端定位相关技巧
前端·vue
起名时在学Aiifox1 小时前
从零实现前端数据格式化工具:以船员经验数据展示为例
前端·vue.js·typescript·es6