如何在打字稿中编写对联合类型的过滤器

JLa*_*rky 6 typescript

我有以下示例来说明我想要得到的内容。

const v1: { type: "S"; payload: string } = { type: "S", payload: "test" };
const v2: { type: "N"; payload: number } = { type: "N", payload: 123 };

type Actions = typeof v1 | typeof v2;

const findByType = <A extends Actions>(type: A["type"]) => (
    action: Actions
): action is A => action.type === type;

const filterWithBothNameAndType = [v1, v2].filter(findByType<typeof v1>("S"));
console.log(filterWithBothNameAndType[0].payload.trim());

const findByTypeDoesntWork = <A extends Actions, T extends A["type"]>(type: T) => (
    action: Actions
): action is A => action.type === type;

const filterWithJustType = [v1, v2].filter(findByTypeDoesntWork("S"));
console.log(filterWithJustType[0].payload.trim());
Run Code Online (Sandbox Code Playgroud)

打字稿游乐场

我有findByType具有正确类型信息的函数filterWithJustType,我有具有我喜欢的 api 的函数,但它丢失了类型信息。我希望 api 只是filter("S")不传递泛型类型。到目前为止,它看起来只适用于类,instaceof但我想让它适用于普通对象。

Mat*_*vic 12

您可以使用ExcludeExtract正如文档中提到的,例如:

type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "a" | "c"

type T02 = Exclude<string | number | (() => void), Function>;  // string | number
type T03 = Extract<string | number | (() => void), Function>;  // () => void

Run Code Online (Sandbox Code Playgroud)


art*_*tem 1

它不起作用,因为它要求编译器做出假设,如果扩展的某个类型Actions具有type文字类型 的成员S,那么它一定是typeof v1

一般来说,这个假设是不安全的,因为extends Actions约束非常弱,并且可以编译:

const v3: { type: "S"; payload: boolean } = { type: "S", payload: false };

const filterWithJustType3 = [v1, v2, v3].filter(findByTypeDoesntWork("S"));
Run Code Online (Sandbox Code Playgroud)

顺便说一句,它不会在strictFunctionTypes打开的 2.6 中进行编译,但类型推断没有利用此选项引入的额外健全性,并且不清楚它是否应该这样做。

但是有一种方法可以明确地告诉编译器,只能从其成员的类型明确地推断出联合成员type。您只需要自己建立类型映射:

const v1: { type: "S"; payload: string } = { type: "S", payload: "test" };
const v2: { type: "N"; payload: number } = { type: "N", payload: 123 };

interface ActionMap {
    S: typeof v1;
    N: typeof v2;
}

type Actions = ActionMap[keyof ActionMap];


const findByTypeWorks = <T extends keyof ActionMap>(type: T) => (
    action: Actions
): action is ActionMap[T] => action.type === type;

const filterWithJustType = [v1, v2].filter(findByTypeWorks("S"));
console.log(filterWithJustType[0].payload.trim());
Run Code Online (Sandbox Code Playgroud)