增强react的useState

69 阅读2分钟

useState使用时的几个痛点

1、更改某一个值时不方便

const [state, setState] = useState({a:1, b:{c: 2}})

// 更改a
setState(s=>{
  return {...s, a: 2}
})
// 更改c
setState(s=>{
  return {
    ...s,
    b: {
       ...s.b,
       c:3
    }
  }
})

希望这样使用

// 更改a
setState({a: 2})

// 更改c
setState(s=>{
  s.b.c=3;
})

2、更改一个值后,无法立即取到最新值

react本身就没有提供获取到最新值的方法

const [state, setState] = useState({a:1, b:{c: 2}})
const demo = () => {
  setState(s=>{
    return {...s, a: 2}
  })
  console.log(state.a) // => 1 
}

希望

const [state, setState] = useState({a:1, b:{c: 2}})
const demo = async () => {
 const newState = await setState(s=>{
    return {...s, a: 2}
  })
 console.log(newState.a) // => 2 
}
// 或者 通过回调
  setState(s=>{
    return {...s, a: 2}
 }, newState => {
   console.log(newState.a) // => 2 
})

3、希望useState可以重置值

在弹窗关闭重置数据时 非常有用

const [state, setState, reset] = useState({
    a: 1,
    b: { c: 2 }
})
// 获取所有最新值,且不会触发重新渲染
const newState = await setState()

// 是否还可以还原为初始值
reset() // 全部还原
reset("a") // 还原具体一项
reset(["a", ""]) // 还原指定具体项

实现

import { useState, useCallback } from "react";
import immer from "immer";

type SetStateAction<T> = Partial<T> | ((prevState: T) => void);
type Callback<T> = (values: T) => void
type Dispatch<T> = (values?: SetStateAction<T>, cb?: Callback<T>) => Promise<T>;
type Reset<T> = (valueKeys?: keyof T | (keyof T)[]) => void

/**
 * 批量处理更新
 * @param initialValue 对象类型
 * @returns [values, dispatch, reset]
 */
function useValues<T extends Record<string, any>>(initialValue: T): [T, Dispatch<T>, Reset<T>] {
    const [values, dispatch] = useState<T>(initialValue);

    const setDispatch = useCallback((_values?: SetStateAction<T>, cb?: Callback<T>) => {
        return new Promise<T>((resolve, reject) => {
            try {
                if (typeof _values === "function") {
                    dispatch((state: T) => {
                        const newValues = immer(state, _values)
                        if (typeof cb === 'function') {
                            Promise.resolve(newValues).then(cb)
                        }
                        resolve(newValues)
                        return newValues
                    });
                } else if (typeof _values === "object") {
                    dispatch((values: T) => {
                        const newValues = { ...values, ..._values }
                        if (typeof cb === 'function') {
                            Promise.resolve(newValues).then(cb)
                        }
                        resolve(newValues)
                        return newValues
                    })
                    // 获取全部值
                } else if (_values == null && typeof initialValue === 'object') {
                    dispatch((values: T) => {
                        resolve(values)
                        return values
                    })
                }
            } catch (error) {
                reject(error)
            }
        })
    }, [dispatch]);

    const reset = useCallback((valueKeys?: keyof T | (keyof T)[]) => {

        const initialKey = Object.keys(initialValue)

        // 全部重置
        if (valueKeys == null) {
            dispatch(() => initialValue)
            // 根据数组中字段,对应重置
        } else if (Array.isArray(valueKeys)) {
            dispatch((state: T) => {
                const resetInitialValue = valueKeys.reduce((prev, next) => {
                    if (initialKey.includes(next as string)) {
                        prev[next] = initialValue[next]
                    }
                    return prev
                }, {} as T)

                return {
                    ...state,
                    ...resetInitialValue
                }
            })
            // 指定字段重置
        } else if (typeof valueKeys === 'string') {
            const isExit = initialKey.includes(valueKeys)
            if (!isExit) {
                return
            }
            dispatch((state: T) => ({
                ...state,
                [valueKeys]: initialValue[valueKeys]
            }))
        }
    }, [])

    return [values, setDispatch, reset];
}

export default useValues;

注意事项:切记不要在useEffect中返回 useValues,因为useValues是一个函数

// 错误用法
useEffect(()=>{
    return useValues()
}, [])