代码之家  ›  专栏  ›  技术社区  ›  Justin Grant

用户定义的类型保护,用于验证泛型对象的属性是否全部为空/未定义

  •  1
  • Justin Grant  · 技术社区  · 7 年前

    • options
    • 每个函数只使用该对象的几个属性,而忽略其余属性。
    • 这个

    我的想法(这可能是不可能的)是将输入验证、无效输入的日志记录和类型保护合并到一个函数中,该函数将接受一个对象,如果该对象的任何属性为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;
    }
    

    下面是一个简单的例子,说明我使用这个函数的想法:

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

    但那太可怕了。每个属性名需要输入三次,重构属性名会很痛苦。

    项目1 支柱2 ,可能是因为类型保护仅应用于在调用类型保护函数时创建的匿名对象。

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

    有没有更好的解决方案可以在TypeScript 3.0上使用?在3.1上?

    1 回复  |  直到 7 年前
        1
  •  3
  •   Titian Cernicova-Dragomir    7 年前

    一种方法是将属性作为字符串传递给 allPropertiesHaveValuesLogged . Typescript提供了使用 keyof T

    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;
    }
    
    推荐文章