useControllableValue
作用
在某些组件开发时,我们需要组件的状态既可以自己管理,也可以被外部控制,useControllableValue 就是帮你管理这种状态的 Hook。
原理
判断当前组件是否为受控组件(如果
props
中有value
,则是受控)。如果是非受控,则维护内部state
状态。假如是受控组件,则由父级接管控制state
。只要props
中有trigger
字段,则在state
变化时,就会触发trigger
函数
源码
ts
import { useMemo, useRef } from 'react'
import type { SetStateAction } from 'react'
import { isFunction } from '../utils'
import useMemoizedFn from '../useMemoizedFn'
import useUpdate from '../useUpdate'
export interface Options<T> {
defaultValue?: T
defaultValuePropName?: string
valuePropName?: string
trigger?: string
}
export type Props = Record<string, any>
export interface StandardProps<T> {
value: T
defaultValue?: T
onChange: (val: T) => void
}
function useControllableValue<T = any>(props: StandardProps<T>): [T, (v: SetStateAction<T>) => void]
function useControllableValue<T = any>(
props?: Props,
options?: Options<T>,
): [T, (v: SetStateAction<T>, ...args: any[]) => void]
function useControllableValue<T = any>(props: Props = {}, options: Options<T> = {}) {
const {
defaultValue,
defaultValuePropName = 'defaultValue',
valuePropName = 'value',
trigger = 'onChange',
} = options
// 获取当前的 value
const value = props[valuePropName] as T
// 判断是否为受控组件
const isControlled = props.hasOwnProperty(valuePropName)
// 初始化 value
const initialValue = useMemo(() => {
// 如果是受控组件,则返回 value
if (isControlled) {
return value
}
// 如果是非受控组件,则返回 defaultValue
if (props.hasOwnProperty(defaultValuePropName)) {
return props[defaultValuePropName]
}
return defaultValue
}, [])
// 使用 useRef 保存 初始化 value
const stateRef = useRef(initialValue)
// 如果是受控组件,则将 value 保存到 stateRef 中
if (isControlled) {
stateRef.current = value
}
// 强制更新
const update = useUpdate()
// 设置值的函数
function setState(v: SetStateAction<T>, ...args: any[]) {
// 如果 v 是函数,则执行函数
const r = isFunction(v) ? v(stateRef.current) : v
// 如果是非受控组件,则更新 stateRef
if (!isControlled) {
stateRef.current = r
update()
}
// 如果 props 中有 trigger,则执行 trigger 函数
if (props[trigger]) {
props[trigger](r, ...args)
}
}
// 返回值和更改值的方法
return [stateRef.current, useMemoizedFn(setState)] as const
}
export default useControllableValue