Fish
? A common idiom in JavaScript to differentiate between two possible values is to check for the presence of a member. As we mentioned, you can only access members that are guaranteed to be in all the constituents of a union type.// Try it out on TypeScript Playground interface Bird { fly(): void; layEggs(): void; } interface Fish { swim(): void; layEggs(): void; } declare function getSmallPet(): Fish | Bird; const pet = getSmallPet(); pet.layEggs(); // This condition will always return true since the function is always defined. Did you mean to call it instead?(2774) if( pet.layEggs ) { console.log( 1 ); } pet.layEggs(); // Property 'swim' does not exist on type 'Bird | Fish'. // Property 'swim' does not exist on type 'Bird'.(2339) pet.swim(); if( 'swim' in pet ) { pet.swim(); } // Property 'fly' does not exist on type 'Bird | Fish'. // Property 'fly' does not exist on type 'Fish'.(2339) if( pet.fly ) { // Property 'fly' does not exist on type 'Bird | Fish'. // Property 'fly' does not exist on type 'Fish'.(2339) pet.fly(); } const fishPet = pet as Fish; fishPet.swim(); if( fishPet.swim ) { fishPet.swim(); }
if( pet instanceof Birt ) { pet.fly(); }
instanceof
needs to be a constructor function, and TypeScript will narrow down to:prototype
property if its type is not any
.null
and undefined
assignable to anything. Effectively, null
and undefined
are valid values of every type. That means it’s not possible to stop them from being assigned to any type, even when you would like to prevent it. The inventor of null
, Tony Hoare, calls this his “billion dollar mistake”.--strictNullChecks
flag fixes this: when you declare a variable, it doesn’t automatically include null or undefined. You can include them explicitly using a union type:// Try it out on TypeScript Playground const user: { email?: string } = { email : 'a@b.com' }; let account, domain; // Object is possibly 'undefined'.(2532) [ account, domain ] = user.email.split( '@' ); [ account, domain ] = user.email?.split( '@' ) || []; [ account, domain ] = user.email!.split( '@' ) || [];
interface
are available in type
, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.type Person = { name: string; age: number; } // Type '{}' is missing the following properties from type 'Person': name, age(2739) const a: Person = {} const b: Partial<Person> = {}
type PartialExclude<T, M extends keyof T> = { [ P in keyof T ]?: T[ P ]; } & { [ P in M ]: T[ P ]; } // Type '{ age: number; }' is not assignable to type 'PartialExclude<Person, "name">'. // Property 'name' is missing in type '{ age: number; }' but required in type '{ name: string; }'.(2322) const c: PartialExclude<Person, 'name'> = { age : 123 }
type Partial<T> = { [ P in keyof T ]?: T[ P ]; } type Readonly<T> = { readonly [ P in keyof T ]: T[ P ]; } type Pick<T, K extends keyof T> = { [ P in K ]: T[ P ]; } type Record<K extends keyof any, T> = { [ P in K ]: T; }
Readonly
, Partial
and Pick
are homomorphic whereas Record
is not. One clue that Record
is not homomorphic is that it doesn't take an input type to copy properties from:type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>;
distributive conditional types
. Distributive conditional types are automatically distributed over union types during instantiation. For example, an instantiation of T extends U ? X : Y
with the type argument A | B | C
for T
is resolved a (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
.// Remove types from T that are assignable to U type Diff<T, U> = T extends U ? never : T; // Remove types from T that are not assignable to U type Filter<T, U> = T extends U ? T : never; // Remove null and undefined from T type NotNullable<T> = Diff<T, null | undefined>
type Diff<T, U> = T extends U ? never : T; type PartialWith<T, M extends keyof T> = { [ P in Diff<keyof T, M> ]: T[ P ]; } & { [ P in M ]?: T[ P ]; } const person: Person = { name : '', age : 1 } const a: PartialWith<Person, 'name'> = { age: 123 }
extends
clause of a conditional type, it is not possible to have infer
declarations that introduce a type variable to be inferred. Such inferred type variables may be referenced in the true branch of the conditional type. It is possible to have multiple infer
locations for the same type variable.type ReturnType<T> = T extends ( ...args: any[] ) => infer R ? R : any;
type AllTypes<T> = T extends Records<keyof any, infer U> ? U : never; type A = { name: string; age: number; male: boolean; } // AA = string | number | boolean; type AA = AllTypes<A>;
declare function foo( x: string ): number; declare function foo( x: number ): string; declare function foo( x: string | number ): string | number; // type T1 = string | number; type T1 = ReturnType<typeof foo>;