我正在尝试为此用例编写用户定义的类型保护功能:
options
对象。 options
对象的类型允许为空,但是对于这些函数中的任何一个,如果其任何需要的属性为null,则该函数必须将缺少的属性名称记录到控制台并立即返回。我的想法(可能不可能)是将输入验证,无效输入的日志记录和类型保护结合到一个可以接受对象的函数中,如果该对象的任何属性为null或未定义,它将将登录到控制台,包括null属性的名称。如果所有属性都具有非null / non-undefined值,则应返回true;如果任何属性为null或undefined,则应返回false。当函数返回时,它应充当类型保护,以便可以在不强制转换为非空类型的情况下引用对象的属性。
这是我的第一次尝试:
type AllNonNullable<T> = { [P in keyof T]: NonNullable<T[P]> };
type StringKeyedObject = { [s: string]: any };
const allPropertiesHaveValuesLogged = <T extends StringKeyedObject>(values: T)
: values is AllNonNullable<T> => {
for (const key in Object.keys(values)) {
if (values[key] == null) {
console.log (`${key} is missing`);
return false;
}
}
return true;
}
这是我使用此功能的简单示例:
interface Foo {
prop1: string | null;
prop2: number | null;
prop3: {} | null;
}
const test1 = (foo: Foo): boolean => {
if (!allPropertiesHaveValuesLogged(foo)) {
return false;
}
const { prop1, prop2 } = foo;
console.log (`${prop1.toLowerCase()} and then ${prop2.toFixed(0)}`);
return true;
}
但是此代码(至少!)有一个大问题:它正在检查foo
的所有属性,在这里我只希望它检查此代码正在使用的两个道具。这些其他道具中的一些可能为空。我只关心prop1
和prop2
。
我的第二次尝试是这样的详细解决方案:
const test2 = (foo: Foo): boolean => {
const propsToUse = { prop1: foo.prop1, prop2: foo.prop2 };
if (!allPropertiesHaveValuesLogged(propsToUse)) {
return false;
}
const {prop1, prop2} = propsToUse;
console.log (`${prop1.toLowerCase()} and then ${prop2.toFixed(0)}`);
return true;
}
但这太糟糕了。这将需要键入每个属性名称3次,并且会使重构属性名称变得很麻烦。
最后一个想法是恕我直言,这是最清晰,最少重复的内容,但可悲的是TypeScript并未意识到我的类型保护应应用于prop1
和prop2
,大概是因为类型保护仅适用于调用类型保护功能时创建的匿名对象。
const test3 = (foo: Foo): boolean => {
const {prop1, prop2} = foo;
if (!allPropertiesHaveValuesLogged({prop1, prop2})) {
return false;
}
console.log (`${prop1.toLowerCase()} and then ${prop2.toFixed(0)}`);
return true;
}
所以#1是运行时错误。 #2丑陋,冗长且容易出错。 #3是一个编译错误,在最佳情况下,可能会在更高版本的TS中起作用。
有没有更好的解决方案可以在TypeScript 3.0上使用?在3.1上?
答案 0 :(得分:2)
一种选择是将属性作为字符串传递给allPropertiesHaveValuesLogged
。 Typescript提供了使用keyof T
键入安全密钥的功能。比版本2少一些冗长,并且具有不创建额外对象的额外好处。
interface Foo {
prop1: string | null;
prop2: number | null;
prop3: {} | null;
}
type SomeNonNullable<T, TKey> = { [P in keyof T]: P extends TKey ? NonNullable<T[P]> : T[P] };
const allPropertiesHaveValuesLogged = <T, TKey extends keyof T>(values: T, ... props: TKey[])
: values is SomeNonNullable<T, TKey> => {
for (const key of props) {
if (values[key] == null) {
console.log (`${key} is missing`);
return false;
}
}
return true;
}
const test1 = (foo: Foo): boolean => {
if (!allPropertiesHaveValuesLogged(foo, 'prop1', 'prop2')) {
return false;
}
const { prop1, prop2 } = foo;
console.log (`${prop1.toLowerCase()} and then ${prop2.toFixed(0)}`);
return true;
}