如何在TypeScript中表达Record的类型减去它的一些键在一个通用的方式?



我有一个从Record中过滤出一些键的函数,我想要类型安全以防止我访问过滤出的键。

我要表达的是:

// Filters out the result of OnlySelected to remove never from filtered out keys
type OmitNever<T> = { [K in keyof T as T[K] extends never ? never : K]: T[K] }
// Only keeps the keys that are in the type V
type OnlySelected<T extends object, V> = {
[K in keyof T]-?: K extends V ? T[K] : never
}

对于简单的用例非常有用:

const a: A = {
a: 1,
b: "2",
c: "3",
d: "4",
e: "5",
}
type MyType = OmitNever<OnlySelected<A, "a" | "b">>;
// type MyType = {
//    a: number;
//    b: string;
//}

但是现在,如果我试图在泛型函数中使用这些类型,我必须设法将我想要保留的键的类型转换为联合类型。所以我必须手动提供类型而且很遗憾必须重复两次键来保持类型安全:

const filterRecord = <T extends Record<any, any>, TO_KEEP>(record: T, keys: Array<keyof T>) => {
return Object.keys(record)
.reduce((acc, it) => {
if (keys.includes(it)) {
acc[it as keyof OmitNever<OnlySelected<T, TO_KEEP>>] = record[it];
}
return acc;
}, {} as OmitNever<OnlySelected<T, TO_KEEP>>)
}
const res = filterRecord<A, "d" | "e">(a, ["d", "e"]);
console.log(res.d)
console.log(res.e)
console.log(res.a)

有人知道解决方案或更好的设计吗?

看这里的操场。

可以将filterRecordTO_KEEP定义为extends keyof T,然后使用TO_KEEP[]作为参数类型。TypeScript会正确推断(但请继续阅读):

const filterRecord = <T extends Record<any, any>, TO_KEEP extends keyof T>(
record: T,
keys: TO_KEEP[]
) => {
return Object.keys(record).reduce((acc, it) => {
if ((keys as readonly string[]).includes(it)) {
acc[it as keyof OmitNever<OnlySelected<T, TO_KEEP>>] = record[it];
}
return acc;
}, {} as OmitNever<OnlySelected<T, TO_KEEP>>);
};

操场例子

注意,为了使用includes,我必须在这里的实现中添加一个扩展类型断言(keys as readonly string[]),但这是无害的。


旁注:除非您将其用于其他目的,否则您可以通过稍微更改OnlySelected的定义来避免需要OmitNever(但请继续阅读):

type OnlySelected<T extends object, V> = {
[K in keyof T as K extends V ? K : never]-?: T[K];
// −−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^−−−−−^^^^
};

操场例子


,OnlySelected似乎与内置的RequiredPick做同样的事情,所以您可以使用它们(或根据RequiredPick定义OnlySelected)。(Playground的例子)(感谢caTS指出Required部分,我错过了flag mod,之前只提到了Pick)

最新更新