用户定义的类型防护,用于验证通用对象的属性是否都不都是null / undefined

时间:2018-09-27 07:04:33

标签: typescript

我正在尝试为此用例编写用户定义的类型保护功能:

  • 100多个TS函数,每个函数都接受一个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的所有属性,在这里我只希望它检查此代码正在使用的两个道具。这些其他道具中的一些可能为空。我只关心prop1prop2

我的第二次尝试是这样的详细解决方案:

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并未意识到我的类型保护应应用于prop1prop2,大概是因为类型保护仅适用于调用类型保护功能时创建的匿名对象。

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上?

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