guard-state.tsx 1.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  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. onChange?: (value: Value) => void;
  8. onFormat?: (...args: any[]) => Value;
  9. onGuard?: (value: Value) => Promise<void>;
  10. onCatch?: (error: Error) => void;
  11. children: ReactNode;
  12. }
  13. function GuardState<T>(props: Props<T>) {
  14. const {
  15. value,
  16. children,
  17. valueProps = "value",
  18. onChangeProps = "onChange",
  19. onGuard = noop,
  20. onCatch = noop,
  21. onChange = noop,
  22. onFormat = (v: T) => v,
  23. } = props;
  24. const lockRef = useRef(false);
  25. if (isValidElement(children)) {
  26. const childProps = { ...children.props };
  27. childProps[valueProps] = value;
  28. childProps[onChangeProps] = async (...args: any[]) => {
  29. // 多次操作无效
  30. if (lockRef.current) return;
  31. lockRef.current = true;
  32. const oldValue = value;
  33. try {
  34. const newValue = (onFormat as any)(...args);
  35. // 先在ui上响应操作
  36. onChange(newValue);
  37. await onGuard(newValue);
  38. } catch (err: any) {
  39. // 状态回退
  40. onChange(oldValue!);
  41. onCatch(err);
  42. }
  43. lockRef.current = false;
  44. };
  45. return cloneElement(children, childProps);
  46. }
  47. return children as any;
  48. }
  49. export default GuardState;