简体   繁体   中英

How do I make class method user type guard work in Typescript?

I'm trying to make a user type guard defined inside a class instance method work, I'm using latest typescript 4.0.5, and can't find a way to fix it. I'm getting an error (commented below). Here's the typescript playground .

// Example with class method user type guard
class A{
  constructor(public name?: string){}
  public hasName(): this is {name: string}{
    return name !== undefined;
  }
}

const listA = [new A('John'), new A(), new A('Mark')].filter(a => a.hasName())
console.log(listA[0].name.toUpperCase()) // Error: Object is possibly undefined

// Example with function user type guard

class B{
  constructor(public name?: string){}
}
const hasName = (b: B): b is {name: string} => name !== undefined;

const listB = [new B('John'), new B(), new B('Mark')].filter(hasName);
console.log(listB[0].name.toUpperCase()) // No error, type guard makes Typescript know it can not be undefined 

As you are aware, one of the call signatures of the Array<T>.filter() method in the standard TypeScript library will narrow the type of the returned array's element when you pass in a user-defined type guard function as its parameter:

 interface Array<T> {
   filter<S extends T>(
     callbackfn: (value: T, index: number, array: T[]) => value is S, 
     thisArg?: any
   ): S[];
 }

The hasName() function is a user-defined type guard, and that works.

But when you call it with a => a.hasName() instead, the type of that function is inferred to return just boolean . That's because user-defined type guard functions do not propagate and are not inferred for you; see microsoft/TypeScript#10734 . A type like x is Y generally gets widened to boolean by the compiler as soon as you start doing things with it.

You could tell the compiler that your arrow function callback is also a type guard by using an arrow-function return type annotation, like this:

const listA = [new A('John'), new A(), new A('Mark')].filter(
  (a): a is A & { name: string } => a.hasName()
);
console.log(listA[0].name.toUpperCase())

Note that the type predicate is a is A & {name: string} instead of just a is {name: string} . That's because, strictly speaking, a type predicate must narrow the value in question, and {name: string} is not a subtype of A (since {name:string} is missing the hasName() method). You can apparently get away with this is {name: string} in a method because polymorphic this is not checked as strictly in a type predicate (as implemented in microsoft/TypeScript#5906 ). Anyway, the intersection A & {name: string} fixes it.

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM