Skip to content

useReducer

作者:guo-zi-xin
更新于:2 个月前
字数统计:1.3k 字
阅读时长:5 分钟

useReducer是React提供的一个高级Hook, 没有它我们也可以正常开发,但是useReducer可以使我们的代码具有更好的可读性和可维护性。

useReduceruseState一样,都是帮我们去管理组件的状态的,但是与useState不同的是,useReducer集中式管理状态的。

用法

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

参数

reducer

reducer是一个处理函数,用于更新状态,reducer里面包含了两个参数,第一个参数是state, 第二个参数是actionreducer会返回一个新的state

initialArg

initialArg 是 state的初始值

init

init是一个可选的参数,用于初始化state,如果编写了init函数,则默认值使用init函数的返回值,否则使用initialArg

返回值

useReducer返回一个由两个值组成的数组:

当前的state

初次渲染时, 他是init(initialArg) 或者initialArg(如果没有init函数)

dispatch函数

用于更新state并触发组件的重新渲染

typescript
import { useReducer } from 'react';

// 根据旧的状态处理 oldState, 处理完成之后返回新的状态 newState
// reducer 只有被dispatch的时候才会被调用,刚进入页面的时候是不会执行的
// oldState 仍然是只读的

const reducer = (oldState, action) => {
  // ...
  return newState
}

const MyComponent = () => {
  const [state, dispatch] = useReducer(reducer, {age: 42, name: 'xxx'});
  // ...
}

计数器案例

  • 初始状态(initState):
typescript
const initialState = { count: 0 }

这里定义了一个初始状态对象,包含一个count属性,初始值为0.

  • reducer函数
typescript
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()
  }
}
  1. reducer 是一个根据不同的 action 来更新状态的纯函数。
  2. 它接收当前状态(state)和一个动作对象(action),根据action.type来决定如何更新state。
  3. 如果action.type是'increment',则 count 增加1; 如果是'decrement', 则count 减少1.
  4. 如果action.type不匹配任何已定义的情况,则抛出一个错误。
tsx
const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

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

export default App
  1. 当点击"+"按钮时, 调用dispatch({ type: 'increment'}), 使 count 增加
  2. 当点击"-"按钮时, 调用dispatch({ type: 'decrement'}), 使 count 减少

购物车案例

初始数据(initData)

typescript
const initData = [
  { name: '张三', price: 100, count: 1, id: 1, isEdit: false },
  { name: '李四', price: 200, count: 1, id: 2, isEdit: false },
  { name: '王五', price: 300, count: 1, id: 3, isEdit: false },
]

initData是一个数组,表示初始的商品列表。每个商品具有以下属性:

  • name:商品名称
  • price: 单价
  • count: 数量
  • id: 商品唯一标识符
  • isEdit:表示商品是否处于编辑状态,默认为false

类型定义

typescript
type List = typeof initData

interface Action {
  type: 'ADD' | 'SUB' | 'DELETE' | 'EDIT' | 'UPDATE_NAME';
  id: number;
  newName?: string
}
  • List 是商品数组的类型, 直接从initData推断

  • Action 接口定义了不同的操作类型

    • ADD: 增加某个商品的数量
    • SUB: 减少某个商品的数量
    • DELETE: 删除某个商品
    • EDIT: 切换某个商品的编辑状态
    • UPDATE_NAME: 更新某个商品的名称
    • id: 需要操作的商品ID
    • newName: 用于UPDATE_NAME操作时, 新的商品名称

Reducer函数

tsx
const reducer = (state:List, action: Action) => {
  // 找到需要操作的商品
  const item = state.find(item => item.id === action.id)!

  switch (action.type) {
    case 'ADD':
      item.count++;
      return [...state]
    case 'SUB':
      item.count--
      return [...state]
    case 'DELETE':
      return state.filter(item => item.id !== action.id)
    case 'EDIT':
      item.isEdit = !item.isEdit;
      return [...state]
    case 'UPDATE_NAME':
      item.name = action.newName!
      return [...state]
    default:
      return state
  }
}

reducer函数根据传入的action更新商品列表的状态。查找要操作的商品item

对不同的action.type执行相应的操作:

  • ADD 将商品数量增加1
  • SUB 将商品数量减少1
  • DELETE 删除指定商品
  • EDIT 切换商品的编辑状态
  • UPDATE_NAME 更新商品的名称

App组件

tsx
const App = () => {
  let [data, dispatch] = useReducer(reducer, initData)
  
  return (<>
    <table cellPadding={0} cellSpacing={0} width={600} border={1}>
      <thead>
        <tr>
          <th>物品</th>
          <th>价格</th>
          <th>数量</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        {data.map((item) => {
          return (
            <tr key={item.id}>
              <td align="center">
                {item.isEdit ?
                  <input
                    onBlur={e => dispatch({ type: 'EDIT', id: item.id })}
                    onChange={e => dispatch({ type: 'UPDATE_NAME', id: item.id, newName: e.target.value })}
                    value={item.name}
                  />
                  :
                  <span>{item.name}</span>
                }
              </td>
              <td align="center">{item.price * item.count}</td>
              <td align="center">
                <button onClick={() => dispatch({ type: 'SUB', id: item.id })}>-</button>
                <button onClick={() => dispatch({ type: 'ADD', id: item.id })}>+</button>
              </td>
              <td align="center">
                <button onClick={() => dispatch({ type: 'EDIT', id: item.id })}>编辑</button>
                <button onClick={() => dispatch({ type: 'DELETE', id: item.id })}>删除</button>
              </td>
            </tr>
          )
        })}
      </tbody>
      <tfoot>
        <tr>
          <td colSpan={3}></td>
          <td align="center">总价:{data.reduce((prev, next) => prev + next.price * next.count, 0)}</td>
        </tr>
      </tfoot>
    </table>
  </>)
}
  • App 组件使用useReducer来管理 data 状态,它从 initData 初始化, 并通过 dispatch 分发动作来改变商品列表

  • 商品列表通过 table 渲染, 每个商品显示以下信息

    • 物品: 如果该商品的isEdit为true, 显示一个输入框用于需改名称; 否则显示商品名称
    • 价格: 显示商品的总价(price * count)
    • 数量: 显示商品的数量,提供 - 和 + 按钮来减少或增加数量
    • 操作: 提供 编辑 按钮切换名称的编辑状态、 删除 按钮可以删除该商品
    • tfoot部分显示购物车的总价, 通过reduce方法计算所有商品的总价

参考文档

小满zs useReducer
https://message163.github.io/react-docs/hooks/useReducer.html

人生没有捷径,就像到二仙桥必须要走成华大道。