useMergedState.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. import { useState, useCallback } from "react";
  2. /**
  3. * useMergedState - 统一管理“受控(Controlled)”与“非受控(Uncontrolled)”状态的 Hook。
  4. * * [用途]
  5. * 当一个组件需要同时支持:
  6. * 1. 内部自管状态(不传 value 时,组件可以自己开关/变化)。
  7. * 2. 外部完全掌控状态(传入 value 时,状态由父组件决定,内部调用 set 仅触发 onChange)。
  8. * 这种模式常用于 Modal, Input, Select 等通用 UI 组件。
  9. * * [参数]
  10. * @param defaultStateValue - 默认初始值(非受控模式下使用)。
  11. * @param option - 配置项,包含:
  12. * - value: 外部传入的受控值。
  13. * - onChange: 状态变更时的回调函数。
  14. * * [返回值]
  15. * @returns [mergedValue, setMergedValue]
  16. * - mergedValue: 最终确定的状态值。
  17. * - setMergedValue: 更新状态的函数(内部会自动判断是更新本地状态还是仅触发回调)。
  18. * * [使用示例]
  19. * const [open, setOpen] = useMergedState<boolean>(false, {
  20. * value: props.open,
  21. * onChange: props.onOpenChange
  22. * });
  23. */
  24. function useMergedState<T>(
  25. defaultStateValue: T | (() => T),
  26. option?: {
  27. value?: T;
  28. onChange?: (value: T) => void;
  29. }
  30. ): [T, (value: T) => void] {
  31. const { value, onChange } = option || {};
  32. // 1. 内部状态:用于非受控模式
  33. const [innerValue, setInnerValue] = useState<T>(() => {
  34. if (value !== undefined) {
  35. return value;
  36. }
  37. return typeof defaultStateValue === "function"
  38. ? (defaultStateValue as () => T)()
  39. : defaultStateValue;
  40. });
  41. // 2. 决定最终输出的值:如果 value 存在,则为受控模式
  42. const mergedValue = value !== undefined ? value : innerValue;
  43. // 3. 定义更新函数
  44. const triggerChange = useCallback(
  45. (newValue: T) => {
  46. // 如果值没变,不触发更新
  47. if (newValue === mergedValue) return;
  48. // 如果是非受控模式,更新内部状态
  49. if (value === undefined) {
  50. setInnerValue(newValue);
  51. }
  52. // 无论受控还是非受控,都触发回调通知父组件
  53. if (onChange) {
  54. onChange(newValue);
  55. }
  56. },
  57. [value, mergedValue, onChange]
  58. );
  59. return [mergedValue, triggerChange];
  60. }
  61. export default useMergedState;