使用useReducer替代useState

492 阅读2分钟

useReduceruseState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数

useUndo案例

这里引用github上的一个useUndo的作为案例,假如采用useState自己来实现这个例子,是这样的

import { useCallback, useState } from 'react'

export const useUndo = <T>(initialPresent: T) => {
  const [state, setState] = useState<{
    past: T[]
    present: T
    future: T[]
  }>({
    past: [],
    present: initialPresent,
    future: []
  })

  const canUndo = state.past.length !== 0
  const canRedo = state.future.length !== 0

  const undo = useCallback(() => {
    setState(curState => {
      const { past, present, future } = curState
      if (past.length === 0) return curState

      const previous = past[past.length - 1]
      const newPast = past.slice(0, past.length - 1)
      const newPresent = previous

      return {
        past: newPast,
        present: newPresent,
        future: [present, ...future]
      }
    })
  }, [])

  const redo = useCallback(() => {
    setState(curState => {
      const { past, present, future } = curState

      if (future.length === 0) return curState

      const next = future[0]
      const newFuture = future.slice(1)
      const newPast = [...past, present]

      return {
        past: [...newPast],
        present: next,
        future: [...newFuture]
      }
    })
  }, [])

  const set = useCallback((newPresent: T) => {
    setState(curState => {
      const { past, present } = curState
      return {
        past: [...past, present],
        present: newPresent,
        future: []
      }
    })
  }, [])

  const reset = useCallback((newPresent: T) => {
    setState(() => {
      return {
        past: [],
        present: newPresent,
        future: []
      }
    })
  }, [])

  return [state, { canUndo, canRedo, set, reset, undo, redo }] as const
}

可以看到,上面的例子中,三个状态past,present,future是相互依赖的,当改变任意一种情况都会影响其中任意一个子项的状态,每次更新状态都会依赖于上一次的state,并且会有一些复杂的逻辑处理出现在更新状态的代码中,下面用useReducer改造这个案例

with useReducer

import { useCallback, useReducer } from 'react'
interface UndoStateProps<T> {
  past: T[]
  present: T
  future: T[]
}
type ActionType = 'UNDO' | 'REDO' | 'SET' | 'RESET'
interface ActionProps<T> {
  type: ActionType
  newPresent?: T
}
const undoReducer = <T>(state: UndoStateProps<T>, action: ActionProps<T>) => {
  const { past, present, future } = state
  const { type, newPresent } = action
  switch (type) {
    case 'UNDO': {
      if (past.length === 0) return state
      const previous = past[past.length - 1]
      const newPast = past.slice(0, past.length - 1)
      const newPresent = previous
      return {
        past: newPast,
        present: newPresent,
        future: [present, ...future]
      }
    }
    case 'REDO': {
      if (future.length === 0) return state
      const next = future[0]
      const newFuture = future.slice(1)
      const newPast = [...past, present]
      return {
        past: [...newPast],
        present: next,
        future: [...newFuture]
      }
    }
    case 'SET': {
      if (newPresent === present) return state
      return {
        past: [...past, present],
        present: newPresent,
        future: []
      }
    }
    case 'RESET': {
      return {
        past: [...past, present],
        present: newPresent,
        future: []
      }
    }
  }
  return state
}
export const useUndo = <T>(initialPresent: T) => {
  const [state, dispatch] = useReducer(undoReducer,
 {
    past: [],
    present: initialPresent,
    future: []
  } as UndoStateProps<T>)
  const canUndo = state.past.length !== 0
  const canRedo = state.future.length !== 0
  const undo = useCallback(() => dispatch({ type: 'UNDO' }), [])
  const redo = useCallback(() => dispatch({ type: 'REDO' }), [])
  const set = useCallback(
    (newPresent: T) => dispatch({ type: 'SET', newPresent }),
    []
  )
  const reset = useCallback(
    (newPresent: T) => dispatch({ type: 'SET', newPresent }),
    []
  )
  return [state, { canUndo, canRedo, set, reset, undo, redo }] as const}

使用useReducer之后,将三个不同但又相互依赖的子项的状态的更新通过action去触发不同的逻辑处理返回新的状态,逻辑上更清晰,可以看到在useUndo中的代码逻辑让人一看就很清晰当前的状态更新是出于什么情况下的,至于具体的细节根据不同的action类型去判断处理。

我的理解就是单一状态管理使用useState,如果一个状态依赖另一个状态时使用useReducer,复杂业务逻辑场景使用useReducer