React Hooks 的实现 - useSetState

Published on
712 Words
Authors

需求背景

React 中已经有了一个 useState,为什么还要设计一个 useSetState 呢?

不可变数据 Immutable

在 React 中,状态被认为是只读的,因此应该替换它而不是直接改变现有对象——即不可变数据。比如在状态中保存了一个 form 对象,我们不能直接通过这种方式修改它,

form.firstName = 'Taylor'

而是要通过创建一个新对象来替换整个对象:

const [form, setForm] = useState(initialState)

// 使用新对象替换 state
setForm({
  ...form,
  firstName: 'Taylor',
})

这里涉及到不可变数据的概念,即

  • 可变数据(mutable):一个数据被创建之后,可以随时进行修改,修改之后会影响到原值,如 form.firstName = 'Taylor'
  • 不可变数据(Immutable): 一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改、添加或删除等操作都会返回一个新的 Immutable 对象。

setState 与 this.setState

在 class component 中,是使用 this.setState 来更新 React 组件的 state,其 api 如下,

setState(nextState, callback?)

nextState 接受一个对象或函数。 this.setState 的处理方式是将传入的对象 nextState 与当前状态的对象进行浅层合并。如果传入的是更新函数,是将其返回值与当前状态进行浅层合并到 this.state 中的对象。

实现需求

借鉴 this.setState 的对象浅层合并方式,为 setState hook 省去需要使用扩展运算符创建一个完整新对象的步骤,简化修改数据的方式。

即通过新的 useSetState hook 更新数据时,

setForm({
  firstName: 'Taylor',
})

实现以下修改的能力。

setForm({
  ...form,
  firstName: 'Taylor',
})

实现思路

入参与出参的设计

参考 useState 的接口设计,

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]

type Dispatch<A> = (value: A) => void
type SetStateAction<S> = S | ((prevState: S) => S)

其使用方式与 useState 保持完全相同,但不同点在于该 hook 仅支持对象类型。

function useSetState<S extends object>(initialValue: S  (() => S)) : [
  S,
  // 即 (patch: Partial<S> | (prevState: S) => Partial<S>) => void
  Dispatch<SetStateAction<S>>
]

type Dispatch<A> = (value: A) => void
type SetStateAction<S> = Partial<S> | ((prevState: S) => Partial<S>)

代码实现

支持传入数据的更新部分,或使用一个更新函数返回需要更新的部分,它将作为参数 patch 传入 setMergeState 函数中进行合并操作。

function useSetState<S extends object>(initialValue: S | (() => S) = {} as S) {
  const [state, setState] = useState(initialValue)

  // 通过额外的数据对象合并操作,支持传入当前对象的更新部分即可替换完整的数据对象
  const setMergeState = useCallback((patch: SetStateAction<S>) => {
    setState((prevState) => {
      const newState = isFunction(patch) ? patch(prevState) : patch
      // 返回新对象,保证原有数据不可变
      return Object.assign({}, prevState, newState)
    })
  }, [])

  return [state, setMergeState]
}

提供一个函数判断的工具函数 isFunction

export const isFunction = (value: unknown): boolean => typeof value === 'function'
// 或
export const isFunction = (value: unknown): boolean => value instanceof Function