Type Guards allow you to narrow down the type of an object within a conditional block.
typeof
TypeScript is aware of the usage of the JavaScript instanceof and typeof operators. If you use these in a conditional block, TypeScript will understand the type of the variable to be different within that conditional block. Here is a quick example where TypeScript realizes that a particular function does not exist on string and points out what was probably a user typo:
functiondoSomething(x:number|string) {if (typeof x ==='string') { // Within the block TypeScript knows that `x` must be a stringconsole.log(x.subtr(1)); // Error, 'subtr' does not exist on `string`console.log(x.substr(1)); // OK }x.substr(1); // Error: There is no guarantee that `x` is a `string`}
instanceof
Here is an example with a class and instanceof:
classFoo { foo =123; common ='123';}classBar { bar =123; common ='123';}functiondoStuff(arg:Foo|Bar) {if (arg instanceofFoo) {console.log(arg.foo); // OKconsole.log(arg.bar); // Error! }if (arg instanceofBar) {console.log(arg.foo); // Error!console.log(arg.bar); // OK }console.log(arg.common); // OKconsole.log(arg.foo); // Error!console.log(arg.bar); // Error!}doStuff(newFoo());doStuff(newBar());
TypeScript even understands else so when an if narrows out one type it knows that within the else it's definitely not that type. Here is an example:
classFoo { foo =123;}classBar { bar =123;}functiondoStuff(arg:Foo|Bar) {if (arg instanceofFoo) {console.log(arg.foo); // OKconsole.log(arg.bar); // Error! }else { // MUST BE Bar!console.log(arg.foo); // Error!console.log(arg.bar); // OK }}doStuff(newFoo());doStuff(newBar());
in
The in operator does a safe check for the existance of a property on an object and can be used as a type guard. E.g.
interfaceA { x:number;}interfaceB { y:string;}functiondoStuff(q:A|B) {if ('x'in q) {// q: A }else {// q: B }}
Literal Type Guard
When you have literal types in a union you can check them to discriminate e.g.
typeFoo= { kind:'foo',// Literal type foo:number}typeBar= { kind:'bar',// Literal type bar:number}functiondoStuff(arg:Foo|Bar) {if (arg.kind ==='foo') {console.log(arg.foo); // OKconsole.log(arg.bar); // Error! }else { // MUST BE Bar!console.log(arg.foo); // Error!console.log(arg.bar); // OK }}
User Defined Type Guards
JavaScript doesn't have very rich runtime introspection support built in. When you are using just plain JavaScript Objects (using structural typing to your advantage), you do not even have access to instanceof or typeof. For these cases you can create User Defined Type Guard functions. These are just functions that return someArgumentName is SomeType. Here is an example:
/** * Just some interfaces */interfaceFoo { foo:number; common:string;}interfaceBar { bar:number; common:string;}/** * User Defined Type Guard! */functionisFoo(arg:any): arg isFoo {returnarg.foo !==undefined;}/** * Sample usage of the User Defined Type Guard */functiondoStuff(arg:Foo|Bar) {if (isFoo(arg)) {console.log(arg.foo); // OKconsole.log(arg.bar); // Error! }else {console.log(arg.foo); // Error!console.log(arg.bar); // OK }}doStuff({ foo:123, common:'123' });doStuff({ bar:123, common:'123' });
Type Guards and callbacks
TypeScript doesn't assume type guards remain active in callbacks as making this assumption is dangerous. e.g.
// Example Setupdeclarevar foo:{bar?: {baz:string}};functionimmediate(callback: ()=>void) {callback();}// Type Guardif (foo.bar) {console.log(foo.bar.baz); // OkayfunctionDoingSomeStuff(() => {console.log(foo.bar.baz); // TS error: Object is possibly 'undefined'" });}
The fix is as easy as storing the inferred safe value in a local variable, automatically ensuring it doesn't get changed externally, and TypeScript can easily understand that: