我如何描述可用作类型防护以从对象中排除键的类型?
下面,我有一个函数getExcludedKeys
,该函数从传入的对象中筛选出键。
我遇到的是类型防护什么也没做,我获得了所有3个属性,我想要做的就是缩小类型的范围,以便从类型中排除过滤的键。
我以前在filter
表达式中使用过类型保护,但从未在预定义数组中使用过。
interface Foo {
id: number
val1: string
val2: string
}
type KeysOf<T> = (keyof T)[]
const getKeys = <T> (obj: T) => Object.keys(obj) as KeysOf<T>
const getExcludedKeys = <T> (obj: T, excludeKeys: KeysOf<T>) =>
getKeys(obj)
.filter((key): key is Exclude<keyof T, typeof excludeKeys> => { // This line isn't working as expected.
return !excludeKeys.includes(key)
})
const foo: Foo = {
id: 1,
val1: 'val1',
val2: 'val2'
}
const result = getExcludedKeys(foo, ['val1', 'val2'])
.map(key => key) // EXPECTED :: key: "id"
// ACTUAL :: key: "id" | "val1" | "val2"
答案 0 :(得分:2)
您给出了一个自我解答,然后悬赏于您的问题并将此评论添加到您的回答中:
如果在传递给排除对象之前在变量/常量中定义了排除键的数组,这一切都会中断。
所以我假设这是您关注的问题。确实,如果我采用您的代码,然后执行此操作,则会在exclude
的第二个参数上出现错误:
const toExclude = ["foo", "bar"];
const b = exclude(a, toExclude)
.map(key => { // (EXPECTED :: key: 'baz') (ACTUAL :: key: 'foo' | 'bar' | 'baz')
if (key === 'bar') { } // Error
if (key === 'foo') { } // Error
if (key === 'baz') { } // Okay
if (key === 'yay') { } // Okay
});
您的问题是,当您声明这样的变量时:
const toExclude = ["foo", "bar"];
TypeScript将推断“最佳通用类型”(此术语来自documentation)。 “最佳通用类型”是适合最普通使用情况的类型。通常,当有人定义包含字符串文字的数组时,所需的推断类型是string[]
。这类似于您进行const x = "moo";
的推断方式时,x
的类型为string
而不是"moo"
。如果要使用更窄的类型,则需要明确使用const x: "moo" = "moo"
。同样,const x = 1
可以推断number
的{{1}},尽管它的类型可能更窄。
对您来说,这是一个问题,因为您的数组必须包含作为传递给x
的第一个参数上可能键的子集的值。 您必须告诉TS,您的意思是数组具有更窄的类型。您可以执行以下操作:
exclude
如果您有多个数组,则需要这样输入,每次必须添加类型断言可能会变老。您可以使用辅助函数来避免每次都要键入类型断言。例如:
const toExclude = ["foo", "bar"] as ["foo", "bar"];
上面的 function asKeysOfA<T extends (keyof typeof a)[]>(...values: T) { return values; }
const toExclude = asKeysOfA("foo", "bar");
推断为类型toExclude
。这是一个适用于任何对象类型而不只是["foo", "bar"]
的版本:
typeof a
在这种情况下,您需要传递function asKeysOf<O, T extends (keyof O)[]>(o: O, ...values: T) { return values; }
const toExclude = asKeysOf(a, "foo", "bar");
作为第一个参数,以便可以正确推断a
。 T
不被该功能使用。
您可能想知道为什么在执行a
时不需要执行类型声明,因为在推断第二个参数的类型时,TS使用的方法不同于“最佳通用类型”。它确定“上下文类型”。如果结构可以满足为exclude(a, ["foo", "bar"])
的第二个参数声明的类型,则TS会推断该类型,而不是更一般的类型,因此您不必使用类型断言。
这实际上是上述exclude
和asKeysOfA
函数所利用的:将值作为参数传递给这些函数时,TS会为它们推断上下文类型,然后在推断返回值时继续使用辅助功能的值。
我将在此处添加一个从您的代码派生的完整示例,其中包括一些辅助函数,这些函数为变量asKeysOf
提供了一个很好的窄元组类型。我发现toExclude
和KeysList
类型声明没有太大的好处。对我来说,它们不会使代码更易于阅读。所以我删除了它们。 Omit
的输入原理仍然相同。
exclude
TypeScript 3.4即将发布,并提供一些新功能,这些功能会影响您在此处尝试执行的操作。在TypeScript 3.4中,可以要求编译器推断出尽可能窄的类型用于文字值,否则将推断出“最佳通用类型”。您必须使用类型断言function getAllKeys<T>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}
function exclude<T, K extends (keyof T)[]>(obj: T, excludes: K) {
return getAllKeys(obj)
.filter((key: keyof T): key is Exclude<keyof T, K[number]> =>
!excludes.includes(key));
}
const a = {
foo: 'abc',
bar: 'abc',
baz: 'abc',
yay: true
};
// function asKeysOfA<T extends (keyof typeof a)[]>(...values: T) { return values; }
// const toExclude = asKeysOfA("foo", "bar");
function asKeysOf<O, T extends (keyof O)[]>(o: O, ...values: T) { return values; }
const toExclude = asKeysOf(a, "foo", "bar");
const b = exclude(a, toExclude)
.map(key => { // (EXPECTED :: key: 'baz') (ACTUAL :: key: 'foo' | 'bar' | 'baz')
if (key === 'bar') { } // Error
if (key === 'foo') { } // Error
if (key === 'baz') { } // Okay
if (key === 'yay') { } // Okay
});
。因此,as const
数组可以这样声明:
toExclude
请注意,这也将使数组const toExclude = ["foo", "bar"] as const;
。因此,readonly
的声明也应该包含一个exclude
数组,因此readonly
的定义必须更改为K
:
K extends readonly ...
这是一个例子:
function exclude<T, K extends readonly (keyof T)[]>(obj: T, excludes: K) {
此示例在Typescript 3.4.0-rc中的行为符合预期
答案 1 :(得分:1)
经过数小时的纠缠,我终于设法在@nucleartux的帮助下使用Omit类型将其破解。
我所需要的只是将这种古怪的类型Omit<T, K extends KeysList<T>> = Exclude<keyof T, K[number]>
用作类型保护,并结合具有第二种泛型类型exclude
的{{1}}
K extends (keyof T)[]