React Hooks 的分析 - useSetState

Published on
519 Words
Authors

react-use 与 ahooks 中对 useSetState 的实现方式很相似。

react-use

代码实现:useSetState

文档/Demo:useSetState

入参上,setState 支持传入函数或一个对象值,内部通过 Object.assign() 方法浅层合并数据,整体逻辑非常简单。

import { useCallback, useState } from 'react'

const useSetState = <T extends object>(
  initialState: T = {} as T
): [T, (patch: Partial<T> | ((prevState: T) => Partial<T>)) => void] => {
  const [state, set] = useState<T>(initialState)

  const setState = useCallback((patch) => {
    set((prevState) =>
      Object.assign({}, prevState, patch instanceof Function ? patch(prevState) : patch)
    )
  }, [])

  return [state, setState]
}

export default useSetState

ahooks

代码实现:useSetState

文档/Demo:useSetState

与 react-use 基本相同,setMergeState 的入参也支持传入函数或一个对象值,只不过内部通过是通过对象拓展运算符进行浅层合并数据。

接口定义:

export type SetState<S extends Record<string, any>> = <K extends keyof S>(
  state: Pick<S, K> | null | ((prevState: Readonly<S>) => Pick<S, K> | S | null)
) => void

代码实现:

const useSetState = <S extends Record<string, any>>(
  initialState: S | (() => S)
): [S, SetState<S>] => {
  const [state, setState] = useState<S>(initialState)

  const setMergeState = useCallback((patch) => {
    setState((prevState) => {
      const newState = isFunction(patch) ? patch(prevState) : patch
      return newState ? { ...prevState, ...newState } : prevState
    })
  }, [])

  return [state, setMergeState]
}

export default useSetState

总结

react-use 与 ahooks 对于这个 hook 的实现只有细微处理上的差别,大同小异。

另外,在处理不可变数据时,使用 immer 是一个不错的选择,它简化了不可变数据结构的处理。 同时 immer 也为 React 中也提供了一个 useImmer hook,下面是一个使用 demo,

import React, { useCallback } from "react";
import { useImmer } from "use-immer";

const TodoList = () => {
  const [todos, setTodos] = useImmer([
    {
      id: "React",
      title: "Learn React",
      done: true
    },
    {
      id: "Immer",
      title: "Try Immer",
      done: false
    }
  ]);

  const handleToggle = useCallback((id) => {
    setTodos((draft) => {
      const todo = draft.find((todo) => todo.id === id);
      todo.done = !todo.done; // 可变数据的修改方式:直接修改数据属性
    });
  }, []);

  const handleAdd = useCallback(() => {
    setTodos((draft) => {
      draft.push({
        id: "todo_" + Math.random(),
        title: "A new todo",
        done: false
      });
    });
  }, []);

经过 immer 的封装之后,可以像使用 setState 一样自如的使用 useImmer,同时可以做到可变数据的修改方式。