keyof
)<const>
castRule(s)
- TypeScript relies on an advanced typing system from the idea of conditional type. The
type
keyword is in particular introduced for managing some “type interoperability”.Example
export abstract class Individual { … export class Kid extends Individual { … type Kid_bis = Kid extends Individual ? Kid : never; // 'Kid_bis' is constructed as a conditional type... const Maternity = function (): Kid_bis { // 'Kid' and 'Kid_bis' are equivalent types... return new Kid(); } type Individual_bis = Individual extends Kid ? Individual : never; const Maternity_ = function (): Individual_bis { // 'Individual_bis' and 'never' are equivalent types... // One doesn't return a 'Individual' object since 'Individual' is abstract: // return new Kid(); // Compilation error since 'Maternity_' *never* returns something... // 'throw' creates compatibility with 'never': throw "TS2534: A function returning 'never' cannot have a reachable end point."; }
Example
type Never_never<Any_type> = Any_type extends Object ? Any_type : never; const b: Never_never<boolean> = true; // No compilation error!
Example Miscellaneous.ts.zip
type Primitive = boolean | number | string | symbol; // Bug: this does not include *ALL* primitive types from the TypeScript perspective... // let wm: WeakMap<Primitive, any>; // Compilation error: by construction, 'WeakMap' does not accept primitive types... // type AdaptiveMap<Key, Value> = Key extends Primitive ? Map<Key, Value> : WeakMap<Key, Value>; // Compilation error: Type 'Key' does not satisfy the constraint 'object'... type AdaptiveMap<Key, Value> = Key extends Object ? WeakMap<Key, Value> : Map<Key, Value>; let am: AdaptiveMap<Event, any>; // 'WeakMap' am.set(new Event(""), null); let am_: AdaptiveMap<number, any>; // 'Map' -> This does not work with 'boolean' while *it works with other primitive types*, why? am_.set(0, null);
infer
keywordRule(s)
infer
applies within theextends
clause of a conditional type. It “extracts” a type from generics, tuples…Example (
infer
) Miscellaneous.ts.ziptype Array_element_type<T> = T extends (infer Element_type)[] ? Element_type : T; let b: Array_element_type<boolean>; // 'boolean' is not an array type... b = true; let n: Array_element_type<Array<number>>; // 'Array<number>' is an array type... n = 0; // Fairly similar: type What_is_inside_type<I> = I extends Array<infer Element_type> ? Element_type : never; let s: What_is_inside_type<string>; // 'string' is not an array type... // s.constructor; // Compilation error: Property 'constructor' does not exist on type 'never'... let s_: What_is_inside_type<Array<HTMLElement>>; // 'Array<HTMLElement>' is an array type... s_.constructor; // 'HTMLElement' constructor function...
Example (tuple type) Miscellaneous.ts.zip
type Second<T> = T extends [infer F, infer S, ...unknown[]] ? S : never; type Forname_Surname_IsFemale = [string, string, boolean]; type Surname = Second<Forname_Surname_IsFemale>; // 'string' type... const Barbier: Surname = "Barbier";
See also
keyof
)Rule(s)
- Lookup types allow the access to variable types from a compilation viewpoint. The
keyof
keyword complements this facility.Example (base) Miscellaneous.ts.zip
// Lookup type extracts type of property: type EventTarget_or_null = Event["currentTarget"]; // Extracted type is 'EventTarget | null'... const et: EventTarget_or_null = new EventTarget(); // No compilation error!
Example (
keyof
) Miscellaneous.ts.ziptype Keys_of_Event = keyof Event; // "data" | "type" | ... as properties of 'Event' DOM type... const me: Event = new Event("Franck's event"); let type_property: Keys_of_Event = "type"; window.alert(me[type_property]); // 'Event' objects effectively have a 'type' property... -> "Franck's event" is displayed... // let property_does_not_exist: Keys_of_Event = "property_does_not_exist"; // Bingo! -> compilation error...
Example Miscellaneous.ts.zip
function get_property<Indexed_type, Property_type extends keyof Indexed_type>(o: Indexed_type, p: Property_type): Indexed_type[Property_type] { return o[p]; // Inferred type (as return) is 'Indexed_type[Property_type]' }
Rule(s)
- Utility types (here…) allow some kind of “type adaptation”.
ReturnType<T>
Miscellaneous.ts.zipRule(s)
ReturnType<T>
computes the returned type of a function (see also here…).Example
class Kid extends Individual { … isResponsible(responsibility: Responsibility): boolean { return …; }; // 'typeof this.isResponsible' does not work: isNotResponsible(responsibility: Responsibility): ReturnType<typeof Kid.prototype.isResponsible> { return !this.isResponsible(responsibility); }; } … let Jules: Individual = new Kid(); let is_Jules_responsible_: ReturnType<typeof Jules.isResponsible> = Jules.isResponsible(some_responsibility);
ThisParameterType<T>
Miscellaneous.ts.zipRule(s)
ThisParameterType<T>
extracts the type ofthis
from a function.Example
type Kid_bis_repetita = ThisParameterType<typeof Jules.isResponsible>; let Julie: Kid_bis_repetita;
Omit<T, Ks>
Rule(s)
Omit<T, Ks>
constructs a type by picking all properties fromT
and then removingKs
.Example
type P = Omit<Prisoner, 'given_name' | 'surname'>; // import * as _ from 'lodash'; const p: P = _.omit(prisoner, ['given_name', 'surname']);
<const>
castRule(s)
<const>
cast is the ability of constraining the access to an object (see also here…).Example Lottery.ts.zip
class Lottery { public static Draw(bonus_number: number, ...numbers: Array<number>) /* Return type is inferred... */ { return { bonus_number: bonus_number, date: Date.now(), // For simplicity draw: Array.from(numbers) } } public static Draw_(bonus_number: number, ...numbers: Array<number>) /* Return type is inferred... */ { return <const>{ // On object literal here… bonus_number: bonus_number, date: Date.now(), // For simplicity draw: Array.from(numbers) } } } let result = Lottery.Draw(6, 43, 12, 3, 32, 34, 22); console.assert(result.draw[0] === 43); result.date = Date.parse('10 Feb 2020 15:50:00 GMT'); result = Lottery.Draw_(6, 43, 12, 3, 32, 34, 22); // 'result' inferred type is: '{ bonus_number: number, date: Date, draw: Array<number> }' // so 'date' *CAN BE* changed because of inferred type: result.date = Date.parse('10 Feb 2020 15:50:00 GMT'); // 'result_' inferred type is: '{ readonly bonus_number: number, readonly date: Date, readonly draw: Array
}' const result_ = Lottery.Draw_(6, 43, 12, 3, 32, 34, 22); // so 'date' *CANNOT BE* changed because of inferred type: // result_.date = Date.parse('10 Feb 2020 15:50:00 GMT'); // Compilation error!
Rule(s)
- Function types (here…) are the way of controlling how functions are rigorously signed, and therefore checked at calling places.
- Using call signature to construct function type cannot be used at interface implementation time.
Example (call signature)
enum Comparable_ {LessThan, Equal, GreaterThan} interface Comparable<T> { // Similar to Java "functional interface" (t: T): Comparable_; // Call signature } // Compiler error: "Type 'Giraffe' provides no match for the signature '(t: Giraffe): Comparable_'" (no solution exists!) class Giraffe extends Mammal implements Comparable<Giraffe> { constructor(readonly _name: string, readonly _height: number) { } }
Example (method signature)
enum Comparable_ {LessThan, Equal, GreaterThan} interface Comparable<T> { // Similar to Java "functional interface" compareTo(t: T): Comparable_; } class Elephant extends Mammal implements Comparable<Elephant> { constructor(readonly _name: string, readonly _weight: number) { super(); } compareTo(e: Elephant): Comparable_ { // 'public' by default if (this._weight < e._weight) return Comparable_.LessThan; if (this._weight === e._weight) return Comparable_.Equal; if (this._weight > e._weight) return Comparable_.GreaterThan; } }
Rule(s)
- Function types may be described through function type literals. Properties may then be simply typed by function types.
Example
interface Comparator<T> { compare: (t1: T, t2, T) => Comparable_; // 'compare' has type '(t1: T, t2, T) => Comparable_' } class Zoo implements Comparator<Giraffe> { compare = function (g1: Giraffe, g2: Giraffe): Comparable_ { if (g1._height < g2._height) return Comparable_.LessThan; if (g1._height === g2._height) return Comparable_.Equal; if (g1._height > g2._height) return Comparable_.GreaterThan; }; }
Example
type Ready = (item: Open_Food_Facts_item) => void; … // Later on: this._item_data = new Promise/*<Open_Food_Facts_item>*/((ready: Ready, problem) => { // 'problem' aims at being called when 'Promise' object has difficulty in achieving its job... … // 'ready' must be called with only one argument whose type is 'Open_Food_Facts_item' let offi: Open_Food_Facts_item = {} as Open_Food_Facts_item; … ready(offi); });
Rule(s)
- Constructors are special functions. Extracting function types from constructor signatures is based on the
new
keyword.Example (type of a constructor function)
// 'new' is the way for TypeScript to define the type signature of a constructor function: type Constructor_function_type = new (bye: string, hello: string) => Greetings;
Generic function type
Example Inheritance_polymorphism.ts.zip
let t_toString = <T>(t: T) => t.toString(); let identity = <T>(t: T): T => { // <=> 'function identity<T>(t: T): T {' return t; } let identity_alias: <U>(u: U) => U = identity; // Type of 'identity' and 'identity_alias' is '<T>(t: T) => T' <=> '<U>(u: U) => U' … // Calls later on: window.alert("Zero: " + t_toString(0)); let result: string = identity("Franck Barbier"); // <=> 'identity<string>("Franck Barbier")' result = identity_alias("Franck Barbier");
Rule(s)
- The notion of assertion function exists from TypeScript ver. 3.7. Beyond the copy of
assert
in Node.js, TypeScript allows assertion checking at compilation time.Example Miscellaneous.ts.zip
class Assertion_function_example { handle(e: MouseEvent | TouchEvent) { // Ideally... if (e instanceof MouseEvent) /* Do something */ return; e.touches; // By construction, 'e' complies with 'TouchEvent' } handle_(e: UIEvent /* MouseEvent | TouchEvent */) { // Legacy signature? if (e instanceof MouseEvent) /* Do something */ return; console.assert(e instanceof TouchEvent); // For test... if (e instanceof TouchEvent === false) throw TypeError("'TouchEvent' expected..."); // For run-time... (e as TouchEvent).touches; // Cast probably succeeds due to immediate prior checking... } _e_is_TouchEvent(e: UIEvent): asserts e is TouchEvent { // The notion of "assertion function" from TypeScript ver. 3.7... if (e instanceof TouchEvent === false) throw TypeError("'TouchEvent' expected..."); } handle__(e: UIEvent /* MouseEvent | TouchEvent */) { // Use of TypeScript ver. 3.7 'asserts' if (e instanceof MouseEvent) /* Do something */ return; console.assert(e instanceof TouchEvent); // For test... this._e_is_TouchEvent(e); // This may occur if and only if immediate prior call succeeds: e.touches; // TypeScript deduces that 'e instanceof TouchEvent' from 'asserts e is TouchEvent' } }
Rule(s)
- In TypeScript, decorators (here…) look like Java annotations.
- The
"experimentalDecorators": true'
clause is required to put decorators into practice. Decorators apply to classes, methods, attributes, method parameters and getters (setters share the samePropertyDescriptor
).Example (method) Miscellaneous.ts.zip
const is_configurable = (value: boolean) => { // Decorator for method... return (target: any, method_name: string, descriptor: PropertyDescriptor) => { console.assert(method_name === "my_method"); // See usage below... descriptor.configurable = value; }; } class My_class { common_public_instance_method() { console.log("Common public instance method..."); } @is_configurable(false) // Remove default configurable nature... my_method() { console.log("'my_method' cannot be suppressed from class' prototype..."); } } const mc = new My_class(); console.assert(delete My_class.prototype.common_public_instance_method); // 'true' since public instance methods are configurable... try { mc.common_public_instance_method(); // No longer exists so error... } catch (error/*: TypeError*/) { // Type annotation not (yet?) allowed in TypeScript... console.log((error as TypeError).message); } console.assert(delete My_class.prototype.my_method === false); // Great, 'my_method' cannot be deleted... mc.my_method(); // It works!
Example (class) Miscellaneous.ts.zip
type Constructor_function_type = new (bye: string, hello: string) => Greetings; // 'new' is the way for TypeScript to define the type signature of a constructor function... function Italian(constructor: Constructor_function_type) { // ECMAScript 2015 class expression (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/class) // const MyClass = class [className] [extends otherClassName] { // // class body // }; return class /* [className] */ extends constructor { constructor(bye = "Ciao", hello = "Pronto") { super(bye, hello); // No change... } } } @Italian class Greetings { constructor(bye = "Bye", hello = "Hello") { console.log(hello + "... " + bye); } } new Greetings(); // Decorator is called here...