Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/typescript/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Typescript 用户定义的类型保护,用于验证泛型对象';s属性都不是空的/未定义的_Typescript - Fatal编程技术网

Typescript 用户定义的类型保护,用于验证泛型对象';s属性都不是空的/未定义的

Typescript 用户定义的类型保护,用于验证泛型对象';s属性都不是空的/未定义的,typescript,Typescript,我正在尝试为此用例编写一个用户定义的类型保护函数: 100+TS函数,每个函数接受一个选项对象 每个函数只使用该对象的几个属性,而忽略其余属性 选项对象的类型允许空值,但对于这些函数中的任何一个,如果其所需的任何属性为空,则该函数必须将缺少的属性名称记录到控制台并立即返回 我的想法(这可能是不可能的)是将输入验证、无效输入的日志记录和类型保护组合到一个函数中,该函数将接受一个对象,如果该对象的任何属性为null或未定义,它将记录到控制台,包括null属性的名称。如果所有属性都有非null/非

我正在尝试为此用例编写一个用户定义的类型保护函数:

  • 100+TS函数,每个函数接受一个
    选项
    对象
  • 每个函数只使用该对象的几个属性,而忽略其余属性
  • 选项
    对象的类型允许空值,但对于这些函数中的任何一个,如果其所需的任何属性为空,则该函数必须将缺少的属性名称记录到控制台并立即返回
我的想法(这可能是不可能的)是将输入验证、无效输入的日志记录和类型保护组合到一个函数中,该函数将接受一个对象,如果该对象的任何属性为null或未定义,它将记录到控制台,包括null属性的名称。如果所有属性都有非null/非未定义的值,则返回true;如果有null或未定义的值,则返回false。当函数返回时,它应该充当类型保护,这样就可以引用对象的属性,而不必强制转换为不可为null的类型

这是我的第一次尝试:

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;
}