Skip to content

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

如有转载或 CV 的请标注本站原文地址