typescript - 使用类型推断过滤数组
我正在尝试正确推断函数的返回类型。下面是示例:
enum UserType {
ADMIN,
VIEWER,
PUBLISHER,
}
interface User {
name: string;
type: UserType;
}
const userTypes = [UserType.PUBLISHER, UserType.VIEWER];
const users = [
{ name: 'Paul', type: UserType.ADMIN },
{ name: 'Steve', type: UserType.VIEWER },
{ name: 'Marry', type: UserType.PUBLISHER },
];
const getUsersByTypes = (users: User[], types: UserType[]) => {
return users.filter(user => types.includes(user.type));
};
const filteredUsers = getUsersByTypes(users, userTypes);
filteredUsers 变量的推断类型是 User[]。我希望它是一个 Users 数组,其中属性 type 应该是 userTypes 变量的联合类型,所以在这种情况下,role 的类型应该是 UserType.PUBLISHER | UserType.VIEWER。我不知道如何实现这一点。
答案#1
因为你关心 User 的一个特定子类型,其中 type 属性已从 UserType 缩小,所以修改 User 使其在 type 属性上成为泛型,并仍然默认使用原始版本,将是最简单的方法:
interface User<T extends UserType = UserType> {
name: string;
type: T;
}
另外,如果你希望 getUsersByTypes 返回某个更窄的 T 对应的 User<T>[],那么你确实需要确保传递给它的 types 数组的类型比 UserType[] 更窄:
const userTypes = [UserType.PUBLISHER, UserType.VIEWER];
// const userTypes: UserType[] // 👎
这意味着你的 userTypes 变量可能需要更仔细地整理,例如通过使用 const 断言来请求更具体的类型:
const userTypes = [UserType.PUBLISHER, UserType.VIEWER] as const;
// const userTypes: readonly [UserType.PUBLISHER, UserType.VIEWER] 👍
好了,现在我们终于可以继续了。你需要让 getUsersByTypes 在类型 T 上成为通用类型,这个类型对应于 types 参数的元素,并且你需要返回一个 User<T>[],像这样:
declare const getUsersByTypes: <T extends UserType>(
users: readonly User[], types: readonly T[]
) => User<T>[]
请注意,我为两个参数都使用了只读数组类型;这是因为 readonly X[] 比 X[] 更宽泛(你可以将后者赋值给前者,但不能反过来),并且由于 const 断言会生成 readonly 数组,最好将参数类型扩展为 readonly 数组(除非你想修改输入数组,而你并不想)。
现在这将按照调用者的预期工作:
const filteredUsers = getUsersByTypes(users, userTypes);
// const filteredUsers: User<UserType.VIEWER | UserType.PUBLISHER>[]
至于实现,让编译器验证它是类型安全的有点麻烦。
你本质上需要将过滤器回调标记为自定义类型保护函数,因为编译器无法推断这一点。否则,filter() 不会返回比输入更具体的类型。
而且,当在其上调用 includes() 时,你还需要将 types 数组从 readonly T[] 扩展为 readonly UserType[],因为 TS 不允许你向 includes() 传递更宽的输入,尽管这样做完全是安全的。参见 TypeScript 常量断言:如何使用 Array.prototype.includes?。
但一旦你做了这两件事,编译器就会高兴了:
const getUsersByTypes = <T extends UserType>(
users: readonly User[], types: readonly T[]
) => {
return users.filter((user): user is User<T> =>
(types as readonly UserType[]).includes(user.type)
);
};
代码的 Playground 链接
