考虑 Props 中的两种不同的函数变化

Account for two different function variations in Props

我不确定如何正确键入作为 props 传递的函数。

基本上可以有两种形状:

// Hoc1.tsx
import ChildComponent from "./ChildComponent";

const Hoc1 = () => {
  const cancelResponse = async (agreement: boolean) => {
    return mockCall(agreement);
  };

  return <ChildComponent cancelResponse={cancelResponse} />;
};

export default Hoc1;

// Hoc2.tsx
import ChildComponent from "./ChildComponent";

const Hoc2 = () => {
  const cancelResponse = async (data: {
    agreement: boolean;
    shiftId: string;
    userId: string;
  }) => {
    return mockCall(data);
  };

  return (
    <ChildComponent
      cancelResponse={cancelResponse}
      cancelData={{ shiftId: "1", userId: "2" }}
    />
  );
};

export default Hoc2;

现在我不确定我应该如何正确输入它。我尝试使用条件

这两个将不同的功能传递给所述 ChildComponent

type CancelResponseVariant<ArgumentVariant> = ArgumentVariant extends boolean
  ? boolean
  : { agreement: boolean; shiftId: string; userId: string };

type Props = {
  cancelResponse: <T>(arg: CancelResponseVariant<T>) => Promise<void>;
  cancelData?: { shiftId: string; userId: string };
};

const ChildComponent = (props: Props) => {
  const { cancelData, cancelResponse } = props;
  const onCancelResponse = async (agreement: boolean) => {
    if (cancelData) {
      await cancelResponse({ agreement, ...cancelData });
    } else {
      await cancelResponse(agreement);
    }
  };

  return <button onClick={async () => onCancelResponse(true)}>Example</button>;
};

export default ChildComponent;

这似乎接近解决方案,但并非完全如此。最后一次调用产生以下错误:

Argument of type 'boolean' is not assignable to parameter of type '{ agreement: boolean; shiftId: string; userId: string; }'.ts(2345)

并且在传递函数的 HoC 中产生以下错误:

Type '(agreement: boolean) => Promise' is not assignable to type '(arg: CancelResponseVariant) => Promise'. Types of parameters 'agreement' and 'arg' are incompatible. Type 'CancelResponseVariant' is not assignable to type 'boolean'. Type 'boolean | { agreement: boolean; shiftId: string; userId: string; }' is not assignable to type 'boolean'. Type '{ agreement: boolean; shiftId: string; userId: string; }' is not assignable to type 'boolean'.

知道我应该如何正确输入吗?

Note: Obviously I tried using a simple conditional union type as well

type Data = { agreement: boolean, userId: string, shiftId: string }
type Props = {
  (agreement: boolean | Data) => Promise<void>
}

but that still throws an error inside the HoC

您可以在此处看到重现的错误:

TSPlayground

PS:我知道这不是传递具有不同 arg 变体的函数的理想方式,但它是一个 5 年前的代码库,我需要迁移到打字稿,所以现在,我只是尽我所能。

因为在 cancelResponse 旁边的道具中有 cancelData,所以你有一个受歧视的联合(即使 HOC 没有提供 cancelData 属性),所以你可以这样定义 Props

type Props =
    // First variant
    {
        cancelResponse: (arg: boolean) => Promise<unknown>;
        cancelData?: never;
    }
    |
    // Second variant
    {
        cancelResponse: (arg: { agreement: boolean; shiftId: string; userId: string }) => Promise<unknown>;
        cancelData: { shiftId: string; userId: string };
    };

(稍后我将解释 Promise<unknown> 而不是 Promise<void>,您可能不需要在实际代码中进行更改,但我做了例如。)

我们在第一个变体中需要 cancelData?: never;,因为没有它,我们就没有完全可区分的联合。但我们将其设为可选,因此不需要它的 HOC 不会提供它(因此使用简单的 (agreement: boolean) => Promise<unknown> 签名)。

ChildComponent 根据 cancelData:

找出要进行的调用
const ChildComponent = (props: Props) => {
    const onCancelResponse = async (agreement: boolean) => {
        if (props.cancelData) {
            await props.cancelResponse({ agreement, ...props.cancelData });
        } else {
            await props.cancelResponse(agreement);
        }
    };

    return <button onClick={async () => onCancelResponse(true)}>Example</button>;
};

这使得 Hoc1Hoc2 都有效,并且不允许这种不正确的 Hoc3:

// With an intentional error
const Hoc3 = () => {
    const cancelResponse = async (agreement: boolean) => {
        return mockCall(agreement);
    };

    return <ChildComponent cancelResponse={cancelResponse} cancelData={{shiftId: "1", userId: "2"}} />;
    //      ^−−−−− Error as desired, the provided `cancelResponse` has the wrong signature
};

Playground link

关于示例中的 Promise<unknown> 而不是 Promise<void>:那是因为在 Hoc1Hoc2 中,您的代码正在执行 return mockCall(agreement);mockCall 没有 return 类型注释,所以以 Promise<unknown> 结尾。如果 HOC 进行的实际调用具有 return 类型 Promise<void>,则您可能不需要该更改。如果他们这样做了,那么您可以使用原来的 Promise<void>like this.