All files / react useControllableValue.ts

100% Statements 20/20
91.67% Branches 11/12
100% Functions 5/5
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122                                                                                                                                                          13x 13x   13x 6x 4x   2x 1x   1x     13x   13x 2x 2x       13x 5x       4x   5x       1x           13x 1x     13x    
import { useCallback, useState } from 'react'
import { useLatest, useUpdateEffect } from 'react-use'
import { Defined } from '../types'
 
export type UseControllableValueOptions<
  TProps,
  TDefaultValuePropName extends keyof TProps,
  TValuePropName extends keyof TProps,
  TCallbackPropName extends keyof TProps,
  TDefaultValue extends TProps[TValuePropName],
> = {
  /**
   * 默认值的属性名。
   */
  defaultValuePropName: TDefaultValuePropName
 
  /**
   * 值的属性名。
   */
  valuePropName: TValuePropName
 
  /**
   * 值改变时的回调函数的属性名。
   */
  callbackPropName: TCallbackPropName
 
  /**
   * 默认值。
   */
  defaultValue?: TDefaultValue
 
  /**
   * 是否总是更新值。
   */
  alwaysUpdateValue?: boolean
}
 
export type UseControllableValueResult<
  TProps,
  TValuePropName extends keyof TProps,
  TCallbackPropName extends keyof TProps,
  TDefaultValue extends TProps[TValuePropName],
> = [
  TDefaultValue extends undefined
    ? TProps[TValuePropName]
    : Defined<TProps[TValuePropName]>,
  Defined<TProps[TCallbackPropName]>,
  () => void,
]
 
/**
 * 受控值。
 *
 * @param props 组件的属性
 * @param options 选项
 */
export function useControllableValue<
  TProps extends {},
  TDefaultValuePropName extends keyof TProps,
  TValuePropName extends keyof TProps,
  TCallbackPropName extends keyof TProps,
  TDefaultValue extends TProps[TValuePropName],
>(
  props: TProps,
  options: UseControllableValueOptions<
    TProps,
    TDefaultValuePropName,
    TValuePropName,
    TCallbackPropName,
    TDefaultValue
  >,
): UseControllableValueResult<
  TProps,
  TValuePropName,
  TCallbackPropName,
  TDefaultValue
> {
  const latestProps = useLatest(props)
  const latestOptions = useLatest(options)
 
  const getInitialValue = useCallback(() => {
    if (latestOptions.current.valuePropName in latestProps.current) {
      return latestProps.current[latestOptions.current.valuePropName]
    }
    if (latestOptions.current.defaultValuePropName in latestProps.current) {
      return latestProps.current[latestOptions.current.defaultValuePropName]
    }
    return latestOptions.current.defaultValue
  }, [])
 
  const [value, setValue] = useState(getInitialValue)
 
  useUpdateEffect(() => {
    if (options.valuePropName in props) {
      setValue(props[options.valuePropName])
    }
  }, [props[options.valuePropName]])
 
  const handleSetValue = useCallback((nextValue: typeof value) => {
    if (
      !(latestOptions.current.valuePropName in latestProps.current) ||
      latestOptions.current.alwaysUpdateValue
    ) {
      setValue(nextValue)
    }
    if (
      typeof latestProps.current[latestOptions.current.callbackPropName] ===
      'function'
    ) {
      ;(latestProps.current[latestOptions.current.callbackPropName] as any)(
        nextValue,
      )
    }
  }, [])
 
  const handleResetValue = useCallback(() => {
    handleSetValue(getInitialValue())
  }, [])
 
  return [value, handleSetValue, handleResetValue] as any
}