Typescript 用户定义的类型保护,用于验证泛型对象';s属性都不是空的/未定义的
我正在尝试为此用例编写一个用户定义的类型保护函数:Typescript 用户定义的类型保护,用于验证泛型对象';s属性都不是空的/未定义的,typescript,Typescript,我正在尝试为此用例编写一个用户定义的类型保护函数: 100+TS函数,每个函数接受一个选项对象 每个函数只使用该对象的几个属性,而忽略其余属性 选项对象的类型允许空值,但对于这些函数中的任何一个,如果其所需的任何属性为空,则该函数必须将缺少的属性名称记录到控制台并立即返回 我的想法(这可能是不可能的)是将输入验证、无效输入的日志记录和类型保护组合到一个函数中,该函数将接受一个对象,如果该对象的任何属性为null或未定义,它将记录到控制台,包括null属性的名称。如果所有属性都有非null/非
- 100+TS函数,每个函数接受一个
对象李>选项
- 每个函数只使用该对象的几个属性,而忽略其余属性李>
对象的类型允许空值,但对于这些函数中的任何一个,如果其所需的任何属性为空,则该函数必须将缺少的属性名称记录到控制台并立即返回选项
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;
}
但这段代码(至少!)有一个大问题:它检查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;
}
但这太可怕了。每个属性名需要输入三次,重构属性名会很痛苦
最后一个想法是最清晰、重复最少,但遗憾的是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是一个运行时bug#2丑陋、冗长且容易出错#3是一个编译错误,在最好的情况下,可能在以后的TS版本中工作
有更好的解决方案可以在TypeScript 3.0上使用吗?在3.1上,一个选项是将属性作为字符串传递给
所有属性SaveValuesLogged
。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;
}
接口Foo{
prop1:字符串| null;
prop2:数字|空;
prop3:{}空;
}
键入SomeNonNullable={[P in keyof T]:P扩展TKey?NonNullable:T[P]};
const-allpropertieshavevaluesloged=(值:T,…props:TKey[])
:值是不可为空的=>{
用于(道具的常量键){
if(值[键]==null){
console.log(`${key}缺少`);
返回false;
}
}
返回true;
}
常量test1=(foo:foo):布尔=>{
如果(!AllPropertieShareValuesLogged(foo,'prop1','prop2')){
返回false;
}
常量{prop1,prop2}=foo;
console.log(`${prop1.toLowerCase()}然后是${prop2.toFixed(0)}`);
返回true;
}
为什么所有这些函数都共享一个大的“上帝”选项对象?这些选项是否可以不以任何方式分解为公共子集?@Damien_The_unsiever-我的选项示例实际上是对实际用例的一个相当大的简化,即涉及到一大堆类型和函数,但共同点是,所有这些函数都是使用属性子集的传递对象。我不希望为每个函数的子集创建单一使用类型或接口,而是希望能够找到一种清晰、相对枯燥、对重构友好的方法,而不必更改这些函数的调用者。至少在您的解决方案中,我只需键入每个属性名两次而不是三次!但我希望避免“属性名为字符串”方法带来的重构和键入问题。有没有办法在不使用属性名作为引号的字符串的情况下执行此操作?@JustinGrant它是这样的:-)是的。在做了更多的研究之后,你的方法显然是最好的选择。谢谢
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;
}