Was this page helpful?

TypeScript 2.8

Conditional Types

TypeScript 2.8 introducesconditional types which add the ability to express non-uniform type mappings.A conditional type selects one of two possible types based on a condition expressed as a type relationship test:

ts
TextendsU ?X :Y

The type above means whenT is assignable toU the type isX, otherwise the type isY.

A conditional typeT extends U ? X : Y is eitherresolved toX orY, ordeferred because the condition depends on one or more type variables.Whether to resolve or defer is determined as follows:

  • First, given typesT' andU' that are instantiations ofT andU where all occurrences of type parameters are replaced withany, ifT' is not assignable toU', the conditional type is resolved toY. Intuitively, if the most permissive instantiation ofT is not assignable to the most permissive instantiation ofU, we know that no instantiation will be and we can just resolve toY.
  • Next, for each type variable introduced by aninfer (more later) declaration withinU collect a set of candidate types by inferring fromT toU (using the same inference algorithm as type inference for generic functions). For a giveninfer type variableV, if any candidates were inferred from co-variant positions, the type inferred forV is a union of those candidates. Otherwise, if any candidates were inferred from contra-variant positions, the type inferred forV is an intersection of those candidates. Otherwise, the type inferred forV isnever.
  • Then, given a typeT'' that is an instantiation ofT where allinfer type variables are replaced with the types inferred in the previous step, ifT'' isdefinitely assignable toU, the conditional type is resolved toX. The definitely assignable relation is the same as the regular assignable relation, except that type variable constraints are not considered. Intuitively, when a type is definitely assignable to another type, we know that it will be assignable forall instantiations of those types.
  • Otherwise, the condition depends on one or more type variables and the conditional type is deferred.
Example
ts
typeTypeName<T> =Textendsstring
?"string"
:Textendsnumber
?"number"
:Textendsboolean
?"boolean"
:Textendsundefined
?"undefined"
:TextendsFunction
?"function"
:"object";
typeT0 =TypeName<string>;// "string"
typeT1 =TypeName<"a">;// "string"
typeT2 =TypeName<true>;// "boolean"
typeT3 =TypeName<()=>void>;// "function"
typeT4 =TypeName<string[]>;// "object"

Distributive conditional types

Conditional types in which the checked type is a naked type parameter are calleddistributive conditional types.Distributive conditional types are automatically distributed over union types during instantiation.For example, an instantiation ofT extends U ? X : Y with the type argumentA | B | C forT is resolved as(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).

Example
ts
typeT10 =TypeName<string | (()=>void)>;// "string" | "function"
typeT12 =TypeName<string |string[] |undefined>;// "string" | "object" | "undefined"
typeT11 =TypeName<string[] |number[]>;// "object"

In instantiations of a distributive conditional typeT extends U ? X : Y, references toT within the conditional type are resolved to individual constituents of the union type (i.e.T refers to the individual constituentsafter the conditional type is distributed over the union type).Furthermore, references toT withinX have an additional type parameter constraintU (i.e.T is considered assignable toU withinX).

Example
ts
typeBoxedValue<T> = {value:T };
typeBoxedArray<T> = {array:T[] };
typeBoxed<T> =Textendsany[] ?BoxedArray<T[number]> :BoxedValue<T>;
typeT20 =Boxed<string>;// BoxedValue<string>;
typeT21 =Boxed<number[]>;// BoxedArray<number>;
typeT22 =Boxed<string |number[]>;// BoxedValue<string> | BoxedArray<number>;

Notice thatT has the additional constraintany[] within the true branch ofBoxed<T> and it is therefore possible to refer to the element type of the array asT[number]. Also, notice how the conditional type is distributed over the union type in the last example.

The distributive property of conditional types can conveniently be used tofilter union types:

ts
typeDiff<T,U> =TextendsU ?never :T;// Remove types from T that are assignable to U
typeFilter<T,U> =TextendsU ?T :never;// Remove types from T that are not assignable to U
typeT30 =Diff<"a" |"b" |"c" |"d","a" |"c" |"f">;// "b" | "d"
typeT31 =Filter<"a" |"b" |"c" |"d","a" |"c" |"f">;// "a" | "c"
typeT32 =Diff<string |number | (()=>void),Function>;// string | number
typeT33 =Filter<string |number | (()=>void),Function>;// () => void
typeNonNullable<T> =Diff<T,null |undefined>;// Remove null and undefined from T
typeT34 =NonNullable<string |number |undefined>;// string | number
typeT35 =NonNullable<string |string[] |null |undefined>;// string | string[]
functionf1<T>(x:T,y:NonNullable<T>) {
x =y;// Ok
y =x;// Error
}
functionf2<Textendsstring |undefined>(x:T,y:NonNullable<T>) {
x =y;// Ok
y =x;// Error
lets1:string =x;// Error
lets2:string =y;// Ok
}

Conditional types are particularly useful when combined with mapped types:

ts
typeFunctionPropertyNames<T> = {
[KinkeyofT]:T[K]extendsFunction ?K :never;
}[keyofT];
typeFunctionProperties<T> =Pick<T,FunctionPropertyNames<T>>;
typeNonFunctionPropertyNames<T> = {
[KinkeyofT]:T[K]extendsFunction ?never :K;
}[keyofT];
typeNonFunctionProperties<T> =Pick<T,NonFunctionPropertyNames<T>>;
interfacePart {
id:number;
name:string;
subparts:Part[];
updatePart(newName:string):void;
}
typeT40 =FunctionPropertyNames<Part>;// "updatePart"
typeT41 =NonFunctionPropertyNames<Part>;// "id" | "name" | "subparts"
typeT42 =FunctionProperties<Part>;// { updatePart(newName: string): void }
typeT43 =NonFunctionProperties<Part>;// { id: number, name: string, subparts: Part[] }

Similar to union and intersection types, conditional types are not permitted to reference themselves recursively.For example the following is an error.

Example
ts
typeElementType<T> =Textendsany[] ?ElementType<T[number]> :T;// Error

Type inference in conditional types

Within theextends clause of a conditional type, it is now possible to haveinfer 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 multipleinfer locations for the same type variable.

For example, the following extracts the return type of a function type:

ts
typeReturnType<T> =Textends (...args:any[])=>inferR ?R :any;

Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:

ts
typeUnpacked<T> =Textends (inferU)[]
?U
:Textends (...args:any[])=>inferU
?U
:TextendsPromise<inferU>
?U
:T;
typeT0 =Unpacked<string>;// string
typeT1 =Unpacked<string[]>;// string
typeT2 =Unpacked<()=>string>;// string
typeT3 =Unpacked<Promise<string>>;// string
typeT4 =Unpacked<Promise<string>[]>;// Promise<string>
typeT5 =Unpacked<Unpacked<Promise<string>[]>>;// string

The following example demonstrates how multiple candidates for the same type variable in co-variant positions causes a union type to be inferred:

ts
typeFoo<T> =Textends {a:inferU;b:inferU } ?U :never;
typeT10 =Foo<{a:string;b:string }>;// string
typeT11 =Foo<{a:string;b:number }>;// string | number

Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:

ts
typeBar<T> =Textends {a: (x:inferU)=>void;b: (x:inferU)=>void }
?U
:never;
typeT20 =Bar<{a: (x:string)=>void;b: (x:string)=>void }>;// string
typeT21 =Bar<{a: (x:string)=>void;b: (x:number)=>void }>;// string & number

When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from thelast signature (which, presumably, is the most permissive catch-all case).It is not possible to perform overload resolution based on a list of argument types.

ts
declarefunctionfoo(x:string):number;
declarefunctionfoo(x:number):string;
declarefunctionfoo(x:string |number):string |number;
typeT30 =ReturnType<typeoffoo>;// string | number

It is not possible to useinfer declarations in constraint clauses for regular type parameters:

ts
typeReturnType<Textends (...args:any[])=>inferR> =R;// Error, not supported

However, much the same effect can be obtained by erasing the type variables in the constraint and instead specifying a conditional type:

ts
typeAnyFunction = (...args:any[])=>any;
typeReturnType<TextendsAnyFunction> =Textends (...args:any[])=>inferR
?R
:any;

Predefined conditional types

TypeScript 2.8 adds several predefined conditional types tolib.d.ts:

  • Exclude<T, U> — Exclude fromT those types that are assignable toU.
  • Extract<T, U> — Extract fromT those types that are assignable toU.
  • NonNullable<T> — Excludenull andundefined fromT.
  • ReturnType<T> — Obtain the return type of a function type.
  • InstanceType<T> — Obtain the instance type of a constructor function type.
Example
ts
typeT00 =Exclude<"a" |"b" |"c" |"d","a" |"c" |"f">;// "b" | "d"
typeT01 =Extract<"a" |"b" |"c" |"d","a" |"c" |"f">;// "a" | "c"
typeT02 =Exclude<string |number | (()=>void),Function>;// string | number
typeT03 =Extract<string |number | (()=>void),Function>;// () => void
typeT04 =NonNullable<string |number |undefined>;// string | number
typeT05 =NonNullable<(()=>string) |string[] |null |undefined>;// (() => string) | string[]
functionf1(s:string) {
return {a:1,b:s };
}
classC {
x =0;
y =0;
}
typeT10 =ReturnType<()=>string>;// string
typeT11 =ReturnType<(s:string)=>void>;// void
typeT12 =ReturnType<<T>()=>T>;// {}
typeT13 =ReturnType<<TextendsU,Uextendsnumber[]>()=>T>;// number[]
typeT14 =ReturnType<typeoff1>;// { a: number, b: string }
typeT15 =ReturnType<any>;// any
typeT16 =ReturnType<never>;// any
typeT17 =ReturnType<string>;// Error
typeT18 =ReturnType<Function>;// Error
typeT20 =InstanceType<typeofC>;// C
typeT21 =InstanceType<any>;// any
typeT22 =InstanceType<never>;// any
typeT23 =InstanceType<string>;// Error
typeT24 =InstanceType<Function>;// Error

Note: TheExclude type is a proper implementation of theDiff type suggestedhere. We’ve used the nameExclude to avoid breaking existing code that defines aDiff, plus we feel that name better conveys the semantics of the type. We did not include theOmit<T, K> type because it is trivially written asPick<T, Exclude<keyof T, K>>.

Improved control over mapped type modifiers

Mapped types support adding areadonly or? modifier to a mapped property, but they did not provide support for the ability toremove modifiers.This matters inhomomorphic mapped types which by default preserve the modifiers of the underlying type.

TypeScript 2.8 adds the ability for a mapped type to either add or remove a particular modifier.Specifically, areadonly or? property modifier in a mapped type can now be prefixed with either+ or- to indicate that the modifier should be added or removed.

Example

ts
typeMutableRequired<T> = { -readonly [PinkeyofT]-?:T[P] };// Remove readonly and ?
typeReadonlyPartial<T> = { +readonly [PinkeyofT]+?:T[P] };// Add readonly and ?

A modifier with no+ or- prefix is the same as a modifier with a+ prefix. So, theReadonlyPartial<T> type above corresponds to

ts
typeReadonlyPartial<T> = {readonly [PinkeyofT]?:T[P] };// Add readonly and ?

Using this ability,lib.d.ts now has a newRequired<T> type.This type strips? modifiers from all properties ofT, thus making all properties required.

Example
ts
typeRequired<T> = { [PinkeyofT]-?:T[P] };

Note that instrictNullChecks mode, when a homomorphic mapped type removes a? modifier from a property in the underlying type it also removesundefined from the type of that property:

Example
ts
typeFoo = {a?:string };// Same as { a?: string | undefined }
typeBar =Required<Foo>;// Same as { a: string }

Improvedkeyof with intersection types

With TypeScript 2.8keyof applied to an intersection type is transformed to a union ofkeyof applied to each intersection constituent.In other words, types of the formkeyof (A & B) are transformed to bekeyof A | keyof B.This change should address inconsistencies with inference fromkeyof expressions.

Example
ts
typeA = {a:string };
typeB = {b:string };
typeT1 =keyof (A &B);// "a" | "b"
typeT2<T> =keyof (T &B);// keyof T | "b"
typeT3<U> =keyof (A &U);// "a" | keyof U
typeT4<T,U> =keyof (T &U);// keyof T | keyof U
typeT5 =T2<A>;// "a" | "b"
typeT6 =T3<B>;// "a" | "b"
typeT7 =T4<A,B>;// "a" | "b"

Better handling for namespace patterns in.js files

TypeScript 2.8 adds support for understanding more namespace patterns in.js files.Empty object literals declarations on top level, just like functions and classes, are now recognized as namespace declarations in JavaScript.

js
varns = {};// recognized as a declaration for a namespace `ns`
ns.constant =1;// recognized as a declaration for var `constant`

Assignments at the top-level should behave the same way; in other words, avar orconst declaration is not required.

js
app = {};// does NOT need to be `var app = {}`
app.C =class {};
app.f =function() {};
app.prop =1;

IIFEs as namespace declarations

An IIFE returning a function, class or empty object literal, is also recognized as a namespace:

js
varC = (function() {
functionC(n) {
this.p =n;
}
returnC;
})();
C.staticProperty =1;

Defaulted declarations

“Defaulted declarations” allow initializers that reference the declared name in the left side of a logical or:

js
my =window.my || {};
my.app =my.app || {};

Prototype assignment

You can assign an object literal directly to the prototype property. Individual prototype assignments still work too:

ts
varC =function(p) {
this.p =p;
};
C.prototype = {
m() {
console.log(this.p);
}
};
C.prototype.q =function(r) {
returnthis.p ===r;
};

Nested and merged declarations

Nesting works to any level now, and merges correctly across files. Previously neither was the case.

js
varapp =window.app || {};
app.C =class {};

Per-file JSX factories

TypeScript 2.8 adds support for a per-file configurable JSX factory name using@jsx dom pragma.JSX factory can be configured for a compilation usingjsxFactory (default isReact.createElement). With TypeScript 2.8 you can override this on a per-file-basis by adding a comment to the beginning of the file.

Example
ts
/**@jsx dom */
import {dom }from"./renderer";
<h></h>;

Generates:

js
varrenderer_1 =require("./renderer");
renderer_1.dom("h",null);

Locally scoped JSX namespaces

JSX type checking is driven by definitions in a JSX namespace, for instanceJSX.Element for the type of a JSX element, andJSX.IntrinsicElements for built-in elements.Before TypeScript 2.8 theJSX namespace was expected to be in the global namespace, and thus only allowing one to be defined in a project.Starting with TypeScript 2.8 theJSX namespace will be looked under thejsxNamespace (e.g.React) allowing for multiple jsx factories in one compilation.For backward compatibility the globalJSX namespace is used as a fallback if none was defined on the factory function.Combined with the per-file@jsx pragma, each file can have a different JSX factory.

New--emitDeclarationOnly

emitDeclarationOnly allows foronly generating declaration files;.js/.jsx output generation will be skipped with this flag. The flag is useful when the.js output generation is handled by a different transpiler like Babel.

The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request

Contributors to this page:
MHMohamed Hegazy  (55)
OTOrta Therox  (12)
EIEugene Ilyin  (1)
DKDongho Kim  (1)
JBJack Bates  (1)
6+

Last updated: Dec 16, 2025