在接口属性上键入Narrowing(除去null选项)

时间:2019-02-05 22:43:34

标签: typescript

我有一个接口,该接口的属性可以为null。 我要检查它是否不为null,然后将该对象传递给类型安全对象

在将属性分配给变量时,似乎可以缩小范围 但是当尝试整体使用该对象时,无法确定它不能为null。

我不想使用强制转换,因为这样会破坏设计时间类型检查的目的。

interface Person
{
  midddle:string | null
}

interface MiddleNamePerson
{
  midddle:string
}

function DoWork(person:Person) {
  if(person.midddle)
  {
    const middleName:string = person.midddle; // works
    const middle : MiddleNamePerson = person // Error: Type of 'Person' not Assignable to 'MiddleNamePerson' 
    DoStuff(person) // Error: the argument of 'Person' is not Assignable to parameter
  }

}

function DoStuff(value:{midddle:string}) {}

3 个答案:

答案 0 :(得分:2)

解决方案

替换此简单检查:

if(person.midddle)

具有更好的类型防护:

if(hasDefined(person, ['midddle']) {

这种类型保护可以定义为:

const hasDefined = <T, K extends keyof T>(argument: T | Defined<T, K>, keys: K[]): argument is Defined<T, K> =>
  keys.every(key => argument[key] != null)

type Defined<T, K extends keyof T = keyof T> = {
  [P in K]-?: Exclude<T[P], undefined | null>
}

说明

控制流在TypeScript中不起作用。通过检查if(person.midddle),我们知道中间名称是真实的,但是Person的定义不受影响。仍然是一个对象,其中名为middle的属性可以为null

通过更改类型保护程序,使其不验证单个字段,而是验证整个对象,我们可以确保person是整个代码块中定义明确的Person。 / p>

答案 1 :(得分:1)

TypeScript目前不基于控制流分析进行这种扩展(据我所知)。 这可能是一个很好的功能。

现在,虽然有点涉及,您可以使用typeguard。

function hasMidddle(person: Person): person is { midddle: string } {
  return !!person.midddle // btw your check will pass with empty string too.
}

function DoWork(person: Person) {
  if (hasMidddle(person)) {
    const middleName: string = person.midddle;
    const middle: MiddleNamePerson = person
    DoStuff(person)
  }
}

如果您想使Typeguard更好一点,可以使用ExcludePropType中的type-plus

function hasMidddle(person: Person): person is ExcludePropType<Person, null> {
  return !!person.midddle
}

答案 2 :(得分:0)

快速而肮脏的解决方案是只说我知道这是正确的类型,然后将其强制转换为目标接口。

const middle : MiddleNamePerson = person as MiddleNamePerson;

推荐的解决方案是使用Type Guard。使用后,该类型将自动识别为缩小的类型。这是使用类型防护的代码:

export interface Person {
  midddle: string | null;
}

export interface MiddleNamePerson {
  midddle: string;
}

// type guard function
function isMiddleNamePerson(person: Person): person is MiddleNamePerson {
  return person != null && person.midddle != null;
}

export function DoWork(person: Person) {
  if (isMiddleNamePerson(person)) {
    DoStuff(person); // person is automaticaly recognized as MiddleNamePerson becuse of the type guard
  }

}

function DoStuff(value: {midddle: string}) { }