guard-state.tsx 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import { cloneElement, isValidElement, ReactNode, useRef } from "react";
  2. import noop from "@/utils/noop";
  3. interface Props<Value> {
  4. value?: Value;
  5. valueProps?: string;
  6. onChangeProps?: string;
  7. waitTime?: number;
  8. onChange?: (value: Value) => void;
  9. onFormat?: (...args: any[]) => Value;
  10. onGuard?: (value: Value, oldValue: Value) => Promise<void>;
  11. onCatch?: (error: Error) => void;
  12. children: ReactNode;
  13. }
  14. function GuardState<T>(props: Props<T>) {
  15. const {
  16. value,
  17. children,
  18. valueProps = "value",
  19. onChangeProps = "onChange",
  20. waitTime = 0, // debounce wait time default 0
  21. onGuard = noop,
  22. onCatch = noop,
  23. onChange = noop,
  24. onFormat = (v: T) => v,
  25. } = props;
  26. const lockRef = useRef(false);
  27. const saveRef = useRef(value);
  28. const lastRef = useRef(0);
  29. const timeRef = useRef<any>();
  30. if (!isValidElement(children)) {
  31. return children as any;
  32. }
  33. const childProps = { ...children.props };
  34. childProps[valueProps] = value;
  35. childProps[onChangeProps] = async (...args: any[]) => {
  36. // 多次操作无效
  37. if (lockRef.current) return;
  38. lockRef.current = true;
  39. try {
  40. const newValue = (onFormat as any)(...args);
  41. // 先在ui上响应操作
  42. onChange(newValue);
  43. const now = Date.now();
  44. // save the old value
  45. if (waitTime <= 0 || now - lastRef.current >= waitTime) {
  46. saveRef.current = value;
  47. }
  48. lastRef.current = now;
  49. if (waitTime <= 0) {
  50. await onGuard(newValue, value!);
  51. } else {
  52. // debounce guard
  53. clearTimeout(timeRef.current);
  54. timeRef.current = setTimeout(async () => {
  55. try {
  56. await onGuard(newValue, saveRef.current!);
  57. } catch (err: any) {
  58. // 状态回退
  59. onChange(saveRef.current!);
  60. onCatch(err);
  61. }
  62. }, waitTime);
  63. }
  64. } catch (err: any) {
  65. // 状态回退
  66. onChange(saveRef.current!);
  67. onCatch(err);
  68. }
  69. lockRef.current = false;
  70. };
  71. return cloneElement(children, childProps);
  72. }
  73. export default GuardState;