背景
React 的 useState 设置值是异步的,要获取最新的值只能通过回调
这非常愚蠢,在复杂逻辑中,很难分离逻辑,让我非常恶心
举个例子,下面的 run2 中,你拿不到最新的值
那么如何拿到最新的值呢?官方给的答案是使用回调
但是我的逻辑很多,分了很多函数,每次都传递个回调,或者拿个变量记录
这是不是太蠢了,我真受不了 React 的种种 API
初步方案选择
方案一
使用 useRef 存值,每次设置值时使用一个空的 setState 触发刷新
封装一下就是使用 Proxy 实现,类似 Vue 的 ref
但是对于代码的破坏性有点大,所以我后面就不用这种方案了
简单的实现如下
所以我要保证 API 的一致性,尽量少的破坏代码
我理想的情况是,仅仅改变函数名,比如 useState 改成 useGetState
其他的保持不变,即可享受所有新特性
方案二
于是我封装了一个 Hook,实现如下功能
- 可以同步的获取最新的值,无需通过啰嗦的回调
- 设置对象值时,可以仅传入某个属性,无需展开整个对象,更加优雅,如:
const [state, setState] = useState({ a: 1, b: 2 }) // 旧写法 setState(prev => ({ ...prev, a: 2, })) // 新写法 setState({ a: 2 })
- 根据参数自动推导返回类型
实现单属性合并对象
先从简单的开始说起,下面是最简单的实现,传值是否的函数我就先不判断了
免得大家读起来困难,最后再写上
不过有个类型的小问题,你传递的值,其实是可选的
还有返回值也要变成元组类型,才能推断
所以改一下函数 setState 签名即可,改成可选的成员
并且加上 as const,让返回值变成元组,而不是不知道具体类型的数组
实现同步获取最新的值
思路就是用个 ref 存起来,先来个最简版本,不考虑函数作为参数的情况
我在返回的 setState 上绑定了一个函数,这样就不会破坏原本 API
使用示例如图所示,在你想获取最新值时,仅需要调用 getLatest 函数即可
优化性能
其实在大多数情况下,我们都用不上这个功能,仅仅需要使用合并 setter 的功能
那么每次都用 ref 多存一份值,会让你的内存占用变为原来的两倍
所以我需要提供一个参数,是否需要存值
于是代码就加上新配置,同时加点判断
类型智能推断
这时候,问题就出现了
我配置填了 false,但是 TS 不提醒我你现在没有这个函数
实际运行会报错,所以这时就需要加个泛型推断了
现在加个返回值推断即可
可以看到,这时就有类型推断了,第二个参数填 true 才能过类型校验
完善代码边界逻辑
经过上面的层层险阻,代码已经成型了
但是还有很多校验没做,比如入参为函数
还有各种组合情况,如
- 函数 + 对象
- 函数 + 基础类型
- 函数 + 对象 + 是否存 ref 值 ...
还有很多,我就不例举了,这些代码没什么技术性
我就直接贴出来了,大家复制粘贴即可
下面导入的 @jl-org/tool 是我写的一个工具库,包含各种实用工具
有需要直接下载即可,这里就用到几个 is 判断而已,大家酌情考虑
pnpm i @jl-org/tool
import { isFn, isObj } from '@jl-org/tool'
import type { SetterFn, SetterParam, UseGetStateReturn } from './types'
/**
* - 让你能用 getter 获取最新的 state,而不用使用回调,这在逻辑拆分非常有用
* - 在 setState 对象时,会自动合并对象
* @param initState 初始值
* @param enableGetter 是否启用 getter,默认 false
*
* @example
* ```ts
* const [count, setCount] = useGetState(0, true)
*
* setCount(count + 1, true)
* // getLatest 获取最新值
* console.log(count, setCount.getLatest()) // 0 1
*
* // ================================================
*
* const [data, setData] = useGetState({ a: 1, b: 2 })
* setData({ a: 9 }) // 自动合并为 { a: 9, b: 2 }
*
* // ================================================
*
* // 开启 getter
* const [data, setData] = useGetState({ a: 1, b: 2 }, true)
*
* const latestState = setData.getLatest()
* latestState.a = 99
* setData(latestState)
* console.log(setData.getLatest()) // { a: 99, b: 2 }
* ```
*/
export function useGetState<T, V extends boolean = false>(
initState: T,
enableGetter: V = false as V
): UseGetStateReturn<T, V> {
const stateRef = useRef<T>(initState)
const [state, setState] = useState<T>(initState)
const setter = useCallback((value: SetterParam<T>) => {
let newVal = value as any
// 处理函数类型的 value
if (isFn(newVal)) {
// 记录值
if (enableGetter) {
const res = newVal(stateRef.current)
// 如果返回值是对象,则自动合并
if (isObj(res)) {
const merged = { ...stateRef.current, ...res }
stateRef.current = merged
setState(merged)
return
}
setState(res)
stateRef.current = res
return
}
setState(prevState => {
const res = newVal(prevState)
if (isObj(res)) {
return { ...prevState, ...res }
}
return res
})
return
}
// 自动合并对象
if (isObj(newVal)) {
// 记录值
if (enableGetter) {
const res = { ...stateRef.current, ...newVal }
stateRef.current = res
setState(res)
return
}
setState(prevState => ({ ...prevState, ...newVal }))
return
}
// 基本数据类型 value
// 记录值
if (enableGetter) {
setState(newVal)
stateRef.current = newVal
return
}
setState(newVal)
}, [])
const getLatest = useCallback(() => stateRef.current, [])
if (enableGetter) {
(setter as SetterFn<T>).getLatest = getLatest
}
return [state, setter] as UseGetStateReturn<T, V>
}
types.ts
export type SetterParam<T> =
| PrimitiveOrPartial<T>
| ((prevState: T) => PrimitiveOrPartial<T>)
export type SetterFn<T> = {
(value: SetterParam<T>): void
getLatest: () => T
}
export type UseGetStateReturn<T, V extends boolean> =
V extends false
? [T, (value: SetterParam<T>) => void]
: [T, SetterFn<T>]
export type PrimitiveOrPartial<T> = T extends object
? Partial<T>
: T
export type UseReqOpts<T> = {
onSuccess?: (data: T) => void
onError?: (error: any) => void
onFinally?: () => void
setLoading?: (loading: boolean) => void
/**
* 初始数据状态,类型和请求返回值一致
*/
initData: T
initLoading?: boolean
}