相关文章推荐

2019-10-8T22:00:32.169Z

Read [tdd vs. statically typed][tdd-v-static] to get a refresher on why statically typed languages are an advantage for large projects.

Installation

You can check whether you have the typescript compiler installed type tsc in the command line.

To install typescript you can use npm . Use the command:

npm i -g typescript

This will install the tsc compiler globally.

Compiling a .ts file into javascript

tsc myscript.ts

This will output a .js file. This is the file that can run on the browser and that you would import in an html file through the:

<script src="./myscript.js"></script>

You can also call the compiler passing [options][tsc-compiler-options]:

tsc myscript.ts --noImplicitAny --target "ES5" --module

Config strict checking

To enforce very strict checks there are 3 directives:

  • noImplicitAny : sometimes typescript isn't able to figure out the type of a variable, in those cases it will use type any implicitly in its place. So this means you will not get the type safety there, and tooling support. If you want to get an error in these occurrences, then use this option.
  • strictNullChecks : (dependencies may need to be updated) by default typescript assumes that null and undefined are in the domain of every type. When strictNullChecks is enabled, null and undefined get their own types called null and undefined respectively. Whenever anything is possibly null, you can use the | type union operator to tell typescript that null is also allowed there. For example, number | null tells typescript that the variable can be a number or null. Note : if you ever have a value that typescript thinks is null but you know better, you can tell typescript to shut up with:
  •    declare const foo: string[] | null;
       foo.length; // error - foo is possibly null (so no .length method on it)
       foo!.length; // okay - 'foo!' is always going to be type string[] (even though its declaration allows null)
    
  • noImplicitThis : No implicit any for this. When you use the this keyword outside of classes, the this can be any type. Even when you attach it to the prototype, of some other object, typescript assumes this to be any type. And you can easily misspell a property of this within the function, and never notice. So noImplicitThis complains whenever you are not enforcing a this type:
  •    // ... Point class somewhere else with getDistance method
       // reopen the interface
       interface Point {
         distanceFromOrigin(point: Point): number;
       Point.prototype.distanceFromOrigin = function(this: Point, point: Point) {
         return this.getDistance(point); // getDistance belongs to class Point defined somewhere else
    

    Mosh hamedani's type script introduction

    Classes, using typescript. Private members, short hand constructors, auto assignment getters and setters

    class Point {
      constructor(private _x?: number, public y?: number) {
        // no need for this.x = x etc. typescript generates that for us.
      draw() {
        console.log(this._x, this.y);
      // give read access
      get x() {
        return this._x;
      // give write access but with conditions
      set x(value) {
        if (value < 0)
          throw new Error('Negative values are not allowed');
        this._x = value;
    let point = new Point(1, 2); // no need to type "let point: Point = new Point(1, 2);
    point.draw() // we still benefit from hinting of the class Point
    

    A convention to be able to define getter and/or setter methods on private members, is to prefix the private member with an underscore, and then use the name without underscore for the setter and getter method names. These methods are called respectively on obj.x = 'something' and let x = obj.x and there exists no naming collision.

    Typescript has the enum type which lets you define a set of constants related to each other.

    Traversy

    What does typescript offer:

  • Static type checking
  • Class based objects
  • Modularity
  • ES6 features
  • Syntax closer to java and other high level languages
  • Typescript types

  • String
  • Number
  • Boolean
  • Array
  • Undefined
  • Unknown
  • Tuple
  • Generics
  • let numArra: number[];
    let numberArray: Array<number>;
    let strArray: string[];
    let stringArray: Array<string>;
    let boolArray: boolean[];
    let boolArray: Array<boolean>;
    let tuple: [string, number];
    tuple = ['asdf', 1] // good
    let myVoid: void = null; // works fine
    let myVoid: void = undefined; // works fine too,
    let myVoid: void = 1; // compilation error
    let myUndef: undefined = null; // works fine too
    let myNull: null = undefined; // works fine too
    
    let x: number = null; // [ts] Error Type 'null' is not assignable to type 'number'
    let y: number = undefined; // [ts] Error: Type 'undefined' is not assignable to type 'number'
    let z: number;
    console.log(z); // [ts] Error: Variable 'z' is used before being assigned
    

    Functions

    --noImplicitThis will warn you when this is not explicitly bound to some object and therefore tell you that this is of type any.

    The this keyword has a special meaning in JS, and more so in TypeScript.

    class Hello {
      hi() {
        return function() {
          return this; // Error: 'this' implicitly has type 'any' because it does not have a type annotation.
      ho() {
        return () => {
          return this; // Works.
    const h = new Hello(); // [ts] h: Hello     ;; [js] instance of Hello
    const a = h.hi();    // [ts] a: () => any
    const aCalled = a(); // [ts] aCalled: any   ;; [js] global object
    const b = h.ho();    // [ts] b: () => Hello
    const bCalled = b(); // [ts] bCalled: Hello ;; [js] instance of Hello, same as in h
    

    A quick explanation of what is happening above: in the hi() method, which is returning a function which in turn returns this. Remember, in javascript, the this keyword will refer to the pre-dot object to which the function is attached to at call time. And in case of a standalone call, to the global object. So in the hi() case, the this within the inner returned function has no ts type. On the other hand, the arrow function retains the value of this from the parent scope where it was defined. Called the lexical scope or lexical this. Which means that this does retain the Hello object, and ts knows it too.

    this parameter

    Contrary to javascript, typescript allows you to set this as the first parameter to any function. This will tell typescript what this is supposed to refer to.

    Forbidding standalone usage of a function this: void

    You can prevent usage of this in a function by setting the function's first parameter to be this: void

    let h = function(this: void) {
      return this.hello; // Error : Property 'hello' does not exist on type 'void'.
    

    Telling the compiler what this refers to

    class Hello {
      hi(this: Hello) {
        return function() {
          this.ho(); // [ts] Error 'this' implicitly has type 'any' because it does not have a type annotation
          this.notHere(); // [ts] Error 'this' implicitly has type 'any' because it does not have a type annotation
          return this; // [ts] Error 'this' implicitly has type 'any' because it does not have a type annotation
      ho(this: Hello) {
        return () => {
          this.ho(); // [ts] Works
          this.notHere(); // [ts] Error: Property 'bee' does not exist on type 'Hello2'
          return this; // [ts] Works: 'this' has type Hello.
    

    Above, the problem that ts is complaining about in function hi(this: Hello) is the same as when no type was specified because the returned function will loose the this scope anyway. On the other hand, ho(this: Hello) using the arrow function is enough to let ts know the return type, but note that without the type annotation, ts knew the return type aswell, so what is the benefit?.

    this parameter in callback

    You can also run into errors with this in callbacks, when you pass functions to a library that will later call them. Because the library that calls your callback will call it like a normal function, this will be undefined. With some work you can use this parameter to prevent errors with callbacks too.

  • First the library author needs to annotate the callback type with this:
  •    interface UIElement {
         addClickListener(onClick: (this: void, e: Event) => void): void;
    
  • The this: void means that addClickListener expects onClick to be a function that does not require this type.
  • Second, annotate your calling code with this:
  •    class Handler {
         info: string;
         onClickBad(this: Handler, event: Event): void {
           // Wrong! Although it would compile, it will still crash at runtime
           this.info = event.message;
       let h = new Handler();
       // ... somewhere else in the library
       // uiElement implements UIElement interface above
       uiElement.addClickListener(h.onClickBad); // Error!
    
  • With the this: Handler we make it explicit that onClickBad is expecting this to be of type Handler, so that when we pass onClickBad to addClickListener TypeScript will detect the incompatibility with the interface UIElement which requires this: void.
  • To Fix the error we need to change the code a bit:

    class Handler {
      info: string;
      onClickGood(this: void, event: Event): void {
        // Works, but we cannot use 'this' anymore, because it is expected to be void
        console.log('Good, but no "this" usage allowed anymore in here, but runs ok' + event.message);
    let h = new Handler();
    // ... somewhere else in the library
    // uiElement implements UIElement interface above
    uiElement.addClickListener(h.onClickGood); // Works!
    

    The problem now is that with the solution above, if we want to use the this keyword then we'll need to use an arrow function as a property (which does not allow overriding from subclasses, and which is copied over every object, whereas methods are stored in the prototype). Like so:

    class Handler {
      info: string
    
    
    
    
        
    ;
      onClickGood: (event: Event) => {
        this.info = event.message;
    

    The above works because arrow functions capture the other this, so you can always pass them to something that expects this: void.

    Function types

    Functions belong to the function type which typescript figures out whenever you are declaring a function. Typescript gives you the ability to specify how a specific function is typed:

  • its argument types
  • its return type.
  • Therefore the function type is a superset of all the function types with specific argument counts, types and return types.

    function sum(a: number, b?: number): number {
      if (b == undefined) {
        return a;
      return a + b;
    function myvoid(name: string): void {
      console.log('hello ' + name);
    

    Named function vs. Anonymous Function

    function namedFunc(a, b) { a + b; };
    let anonymousFunc = function(x, y) { return x + y; };
    

    Typing Functions

    We can explicitely specify the function return type, but in many cases TypeScript is able to figure out the return type by itself from the type of the parameters.

    function add(x: number, y: number): number {
      return x + y;
    // typescript knows that the return type is a number
    function addWithImplicitReturnType(x: nubmer, y: number) {
      return x + y;
    

    One thing is to type a function, as above: giving the function definition a few constraints on the argument counts, types and return types. And another, is to tell typescript the type of a certain variable. Two function definitions may share a same type.

    function a(hello: string, name?: string) {
      return hello + name;
    function b(hello: string, name = "Bob") {
      return hello + name;
    // Both functions above share the type
    let ab: (p: string, q?: string) => string;
    

    The default value of name is not held in the type.

    Note: When using default-initialized parameters, they need not be at the end of the parameter list. The only requirement is that undefined is passed in their location when the user expects to use the default. Note: The type of the default parameter is inferred from the default value.

    function buildName(name = "Bernard", lastName: string) {
      return name + " " + lastName;
    let bn: string = buildName(undefined, "Montiel"); // ok "Bernard Montiel"
    let bn: string = buildName("Montiel"); // Error: too few parameters ( "Montiel" is assigned to 'name' and 'lastName' is empty when it should be )
    

    Below what we are doing is storing the function on the right side ( which has types for parameters and for the return value, and is of a certain type itself ) into the variable myAdd.

    let myAdd : (x: number, y: number) => number = function(x: number, y: number): number { return x + y; };
    

    Below is the right hand side of the expression above. This is the part where we actually define the function and typescript infers its type.

    function(x: number, y: number): number { return x + y; };
    

    and what Typescript remembers of the above's function type is:

    (x: number, y: number) => number;
    

    Now if we want to store this function into some variable, like above in myAdd, we need to tell typescript what type is going to be store in the variable. We could do it in two ways:

  • Let typescript figure out the type of myAdd directly from the assignment.
  •    let myAdd = function(x: number, y: number): number { return x + y; };
    
  • Tell typescript which type is the variable myAdd going to hold, and then store the function matching that type in it. This second way is the same as the initial example, except it is performed in two steps.
  •    // tell Ts what type is going to be held
       let myAdd: (x: number, y: number) => number;
       // assign a value matching that type
       myAdd = function(x: number, y: number): number { return x + y; }
    

    Note: the part after the fat arrow => indicates the return type of a function type. And it is mandatory. In case a function returns undefined or never, you would use void or never respectively:

    let returnsUndefined: (message: string, value: number) => void;
    let neverReturns: (foreverMessage: string) => never;
    

    Rest parameters

    Required and default parameters, have one thing in common: they all talk about a single parameter at time.

    Sometimes you may want to:

  • talk about many parameters at a time
  • avoid specifying the exact count of parameters in advance
  • Note: javascript allows you to use the arguments variable within a function to access the parameter list.

    In TypeScript you can use ...rest paramters like so:

    function buildName(first: string, ...restOfParams: string[]): string {
      return first + restOfParams.join(" ");
    let employeeName: (a: string, ...b: string[]) => string = buildName("John", "Mc", "Kenzy", "The", "Great");
    

    Colon : vs. Fat Arrow =>

    You may wonder why sometimes the return type of a function is specified using a colon : and other times with using a fat arrow =>. The reason is that using the : may be ambiguous depending on the context where it is used. For example, given a function that accepts a callback, using : to represent the return type is ambiguous and results in a syntax error, we can overcome it with the fat arrow notation or by wrapping the the callback parameter type in {} :

    // ambiguous parse, syntax error
    function sendString(callback: (value: string): void);
    // valid, using fat arrow
    function sendString(callback: (value: string) => void);
    // same thing, using curly brackets
    // (harder to write, harder to parse visually)
    function sendString(callback: { (value: string): void; });
    

    In classes it is possible to use the fat arrow notation for properties of a class that hold a function, but not for methods.

    class Foo {
      // This actually a property in typescript's eyes, not a method
      someMethod: (v: string) => boolean;
    

    In this case TypeScript makes the distinction between methods of a class and properties of a class. Functions defined as properties like above, don't need a body, but cannot be overridden or defined as methods in subclasses. In other words you couldn't do this even though the signatures are compatible:

    // Error cannot do this
    class Bar extends Foo {
      someMethod(v: string): boolean {
         return v.length > 0;
    

    In conclusion, due to how syntax works in TypeScript, it is always possible to use the : syntax for a function type (use {} if needed), but it is not always possible to use the => syntax.

    Function Overloads

    Sometimes a function will return different types depending on way it is called. From a mathematic perspective they are two separate functions with separate input sets and output mappings. So they could be separated in two. In this sense, TypeScript allows you to use overloads, which define the different function types that the function can have and thus enable type checking.

    let suits = ["hearts", "clubs", "diamonds", "spades"];
    function pickCard(x: any): any {
      if (typeof x === "object") {
        let pickedCard = Math.floor(Math.rand() * x.length);
        return x[pickedCard];
      } else if (typeof x === "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13, };
    let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
    let pickedCard1 = myDeck[pickCard(myDeck)];
    alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
    let pickedCard2 = pickCard(15);
    alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
    

    The above function's type can be narrowed down to two. Instead of the very generic any, we can use function overloads like so:

    // overload for when it is supplied with a deck of cards
    function pickCard(x: { suit: string; card: number; }[]): number;
    // overload for when it is supplied with a deck of cards
    function pickCard(x: number): { suit: string; card: number; };
    // compatible type function definition (the type should be a superset of the overloads) BUT is not an overload in itself!
    function pickCard(x: any): any {
      if (typeof x === "object") {
        let pickedCard = Math.floor(Math.rand() * x.length);
        return x[pickedCard];
      } else if (typeof x === "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13
    
    
    
    
        
    , };
    

    With this change, the overloads now give us type checked calls to the pickCard function.

    IMPORTANT: It looks at the overload list and, proceeding with the first overload, attempts to call the function with the provided parameters. If it finds a match, it picks this overload as the correct overload. For this reason, it’s customary to order overloads from most specific to least specific.

    Note: Note that the function pickCard(x): any piece is not part of the overload list, so it only has two overloads: one that takes an object and one that takes a number. Calling pickCard with any other parameter types would cause an error.

    Interfaces

    One of TypeScript's core principles is that type-checking focuses on the shape that values have also known as "structural subtyping". In typescript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts outside of your project.

    IMPORTANT: contrary to languages like PHP, C# or Java, you don't need to explicitly implement the interface in order for the compiler to consider their signature compatible. Only the shape matters in typescript.

    function showTodo(todo: {title: string, text: string, }): void {
      console.log('This is my todo: Title ' + toto.title + ', text ' + todo.text);
    let myTodo = {title: 'asdf', text: ' hello' };
    showToto(myTodo);
    //A cleaner way to do this would be to create an interface
    inteface Todo {
      title: string,
      text: string,
    function showTodo(todo: Todo): void {
      console.log('This is my todo: Title ' + toto.title + ', text ' + todo.text);
    // an even better way would be to create a class, because of the unity principle. If it has lots of connection with some other part, then it should be together
    

    Classes

    When comparing types that have private and protected members, we treat these types differently. For two types to be considered compatible, if one of them has a private member, then the other must have a private member that originated in the same declaration. The same applies to protected members.

    interface UserInterface {
      name: string;
      email: string;
      age: number;
      payInvoice(): void;
    class User implements {
      name: string;
      protected email: string; // only inheritants can use this
      private age: number; // adding an access modifier
      constructor(name: string, email: string, age: number) {
        this.name = name;
        this.email = email;
        this.age = number;
      payInvoice(): void {
        console.log('Pay invoice');
    let user = new User('John', 'my@mail.com', 21)
    

    Types

    You have to think of types as sets. Sets of possible values.

    Basic Types

    Boolean

    The most basic data type is the simple true/false boolean value:

    let isDone: boolean = false;
    

    Number

    As in javascript all numbers in TypeScript are floating point values. These floating point numbers get the type number.

    let decimal: number = 6;
    let hex: number = 0xf00d;
    let binary: number = 0b1010;
    let octal: number = 0o744;
    

    String

    let n: number = 21;
    let multilineString: string = `Hello.
    Some template string with number ${n + 1}
    

    Array

    TypeScript like javascript allows you to work with arrays of values. Array types can be defined in two ways.

  • number[] : type of the elements followed by []
  •    let list: number[] = [1, 2, 3];
       let otherList: string[] = ["", 1]; // error
    
  • Array<number> : Array followed by the type name between <>
  •    let list: Array<number> = [1, 2, 3];
       let otherList: Array<string> = ["", 1]; // error
    

    Tuple

    Tuples allow you to express an array with a fixed amount of elements of some specific types:

    let tup: [string, number, boolean];
    tup = ["Hello", 1, true];
    tup = ["Hello", 1, 0]; // Error
    

    When accessing an element with a known index, the correct type is inferred:

    console.log(tup[0].substring(2)) // works
    console.log(tup[1].substring(2)) // Error 'number' does not have 'substring'
    

    When setting an element with an invalid index, an error is thrown :

    tup[3] = ""; // Error, property 3 does not exist on type [string, number, boolean]
    

    A helpful addition to the standard set of data types in JavaScript is the enum. As in C#, enum are a way of giving more frienly names to a set of numeric values.

    enum Color = {Red, Green, Blue};
    let c: Color = Color.Green;
    

    By default enums beggin numbering their members at 0. But that can be changed by either manually setting the first value, or setting them all manually. Then you can even get the name of the index in the enum as a string

    enum Color = {Red=3, Green, Blue};
    let c: Color = Color.Green; // 4
    enum Color = {Red=10, Green=15, Blue=20};
    let c: Color = Color.Green; // 4
    let colorName: string = Color[15];
    console.log(colorName); // 'Green'
    

    We may need to describe the type of variables whose value we do not know at the time of writing the code. These values may come from dynamic content (e.g. from the user or 3rd party libraries). In this case, we want to opt out of type checking, and let the values pass through compile time checks. To do so, we label the type of those variables as any.

    let notSure: any = 4;
    notSure = "Why not a string now!"; // ok
    notSure = false; // ok
    

    The any type can allow you to work with existing javascript. Allowing you to gradually opt in and opt out of type checking during compilation.

    Difference between Any and Object

    You might expect Object to be as obscure as any, but the difference is that whereas TypeScript will not complain on arbirary property/method calls to an any variable, it will definitely not allow you to call arbitrary methods on variables of type Object. That's mainly because variables of type Object can host any type of object as long as typeof v === 'object', but once one is placed into the variable, only methods of that specific object will be allowed to be called by TypeScript.

    Check difference between Object and object in TypeScript.

    let notSure: any = 4;
    notSure.ifItExists(); // Ok - ifItExists might be available at runtime
    let prettySure: Object = 4;
    notSure.ifItExists(); // Error: Property 'ifItExists' doesn't exist on type 'object'
    

    You can also have type any arrays, which allow the array to contain anything

    let a: any[] = [1, "b", new Hey()];
    a.push("Something Else");
    

    void is somewhat like the opposite of any the absence of having any type at all. You may commonly see this as the return type of functions that do not return a value. Or which return undefined in JavaScript. So basically void is like the type undefined but for function returns. Actually you can also use it for variables, and it will let you store null (only if --strictNullChecks is not specified) or undefined.

    function f(): void {
      console.log("No return value");
    let unusable: void = undefined; // what's the point? None
    unusable = null; // only if --stricNullChecks is not enabled, but still useless
    

    Null & Undefined

    null and undefined are actually their own types. Much like void they are not extremely usefull on their own.

    let u: undefined = undefined; // only acceptable value for u
    let n: null = null; // only acceptable value for n
    

    By default null and undefined are included in all other types. That means you can assign null and undefined to something that is of type number for example.

    Note: if --strictNullChecks is enabled, then null and undefined are only assignable to variables of type any (well undefined can also be assigned to void). In that case you need to use type unions in order to allow having variables that can hold string and null and undefined with: string | null | undefined.

    Object

    object (with lowercase o) is a type that represents the non primitive types i.e. anything that is not in:

  • number
  • string
  • boolean
  • symbol
  • undefined
  • With object type, API's like Object.create can be better represented. For example:

    declare function create(o: object | null): void;
    create({prop: 0}); // Ok
    create(null); // Ok because we added the union
    create("hello"); // Error
    create(54); // Error
    create(undefined); // Error
    create(false); // Error
    

    Object vs object

    object with lowercase o is anything that is non primitive type. Whereas Object are the functionalities of every object in javascript, like .hasOwnProperty(s: string): boolean or .toString(): string etc. Here is the definition of type Object:

    interface Object {
      // ...
      /** Returns a string representation of an object. */
      toString(): string;
      /** Returns a date converted to a string using the current locale. */
      toLocaleString(): string;
      /** Returns the primitive value of the specified object. */
      valueOf(): Object;
       * Determines whether an object has a property with the specified name.
       * @param v A property name.
      hasOwnProperty(v: string): boolean;
       * Determines whether an object exists in another object's prototype chain.
       * @param v Another object whose prototype chain is to be checked.
      isPrototypeOf(v: Object): boolean;
       * Determines whether a specified property is enumerable.
       * @param v A property name.
      propertyIsEnumerable(v: string): boolean;
    

    Type assertions

    Sometimes you will end up in a situation where you will know better about a value than TypeScript does. This usually will happen when you know the type of some entity could be narrower than its current type.

    Type assertions are a way to tell the complier "trust me, I know what I'm doing!". A type assertion is like a type cast in other languages, but performs no special checking or restructuring of the data. It has no runtime impact and is used purely by the compiler. TypeScript assumes that you, the programmer, have performed any special checks that you need.

    Type assertions have two forms:

  • <> angle bracket (not allowed with JSX)
  • as syntax (works with JSX)
  • let someValue: any = "this is a string";
    let strLength: number = (<string>someValue).length;
    strLength = (someValue as string).length;
    

    Both notations are equivalent. But when using TypeScript with JSX, only the as notation is allowed.

    Tuple destructuring

    let tuple: [number, string, number] = [1, "one", 2];
    let [a, b, c] = tuple; // a: number, b: string, c: number
    

    cannot attempt to destructure more than what the tuple contains

    let tuple: [number, string, number] = [1, "one", 2];
    let [a, b, c, d] = tuple; // Error: no element at index 3, whereas in Javascript this would just make e === undefined
    

    except if you destructure it into a rest, which produces an empty tuple

    let tuple: [number, string, number] = [1, "one", 2];
    let [a, b, c, ...d] = tuple; // a: number, b: string, c: number, d: [] empty tuple
    

    You can also destructure a tuple into a smaller tuple with the rest operator

    let tuple: [number, string, number] = [1, "one", 2];
    let [a, ...b] = tuple; // a: number, b: [string, number]
    let [ , , c] = tuple; // c: number
    

    Object destructuring

    As with array destructuring. An interesting feature is destructuring without declaration (note the user of parenthesis, Javascript parses { as the start of a block):

    let a: string, b: number;
    ({a, b} = {a: "hi", b: 12});
    

    Object property renaming

    You can destructure object properties into variables with different names than the properties themselves. But note how using the colon does not mean the type but rather the name of the variable

    let o = {a: "hi", b: 12};
    let {a: renamedToThis, b: renamedToThat} = o;
    console.log(renameToThis); // "hi"
    

    You can still specify the type with

    let o = {a: "hi", b: 12};
    let {a, b}: { a: string, b: number } = o;
    

    Default values

    Default values let you specify the value in case a property is undefined:

    function hello(wholeObject: { a: string, b?: number }): voic {
      let { a, b = 100 } = wholeObject;
    

    Function Declarations

    Destructuring also works in function declarations.

    type C = { a: string, b?: number };
    function hello({ a, b }: C): void {
      let { a, b = 100 } = wholeObject;
    

    But specifying defaults is more common for parameters, and getting defaults right can be tricky. First of all you need to remember to put the pattern before the default value. (This is an example of type inference)

    function f({ a = "", b = 0 }: {}): void {
      //...
    

    Then you need to remember to give a default for optional properties on the destructured property instead of the main initializer. Remember that C was defined with b optional.

    function f({ a, b = 0 } = { a = "" }): void {
      console.log(a, b);
    f({}); // Error - 'a' needs to be present in the supplied argument object
    f({b: 1}); // Error - 'a' needs to be present in the supplied argument object
    f(); // Good - the default parameter { a = "" } will be used and default destructured 'b' value as well, logs: "", 0
    f({a: "asdf"}); // Good - the default parameter { a = "" } will be overridden and default destructured 'b' value will be used, logs: "asdf", 0
    f({a: "asdf", b: 1 }); // Good - the default parameter { a = "" } will be overridden and default destructured 'b' as well, logs: "asdf", 1
    

    The above is using destructuring (on the left of the middle =) for the function's single parameter, which destructured into { a, b = 0 } where b is optional. Then the right side of the middle = is giving a default value to that single parameter with { a = "" }. Since b has a default value inside the destructuring, the resulting single parameter object will have a b property = 0 if the function f is not called with an object containing a property b (in which case, that b will override the default).

    Spread

    Arrays

    let first = [1, 2]
    let second = [4, 5]
    let combined = [0, ...first, 3, ...second, 6]
    

    Objects

    let defaults = { food: "spicy", price: "$", ambiance: "noisy", };
    let search = { ...defaults, food: "rich", value: "Hey", }; // food overrides defaults: good
    // { food: "rich", price: "$", ambiance: "noisy", value: "Hey"}
    let search2 = { food: "rich", ...defaults, }; // defaults override food, which is not what we want
    // { food: "spicy", price: "$", ambiance: "noisy", value: "Hey"}
    

    Object spreads have limitations:

  • It only spreads own enumerable properties: you loose methods
  • TypeScript compiler does not allow spread of type parameters from generic functions. That feature is expected in future versions of the language
  • Interfaces bis

    TypeScript is a "Duck Typing" meaning that if it "flies and quacks like a dock, then it is a duck". It's not the name of the interface that matters, it's the actual shape of the object that is being passed in the call that matters. It has to at least conform, but can also have more properties than the required ones.

    function printLabel(labeledObject: { label: string }) {
      console.log(labeledObject.label);
    printLabel({ a: "asdf", label: "Some Label" }); // Ok : "Some Label"
    printLabel({ a: "asdf" }); // Error
    

    Now the same example using loger notation, with explicit interface declaration

    interface Labeled {
      label: string;
    function printLabel(labeledObject: Labeled) {
      console.log(labeledObject.label);
    printLabel({ a: "asdf", label: "Some Label" }); // Ok : "Some Label"
    printLabel({ a: "asdf" }); // Error
    

    Optional properties

    interface SquareConfig {
      color?: string;
      width?: number;
    function createSquare(config: SquareConfig): { color: string, area: number } {
      let square = { color: "white", area: 10 };
      if (config.color) {
        square.color = config.color;
      if (config.width) {
        square.area = config.width * config.width;
      return square;
    let s = createSquare({ width: 100 }); // { color: "white", area: 10000 }
    

    Readonly properties

    Some properties should only be modifiable on creation, and then only readable. You can use interfaces for that with the keyword readonly.

    interface Point {
      readonly x: number;
      readonly y: number;
    let p: Point = {x: 1, y: 1};
    p.x = 5; // Error readonly !
    

    By assigning an object literal to a variable with type Point, we make it's properties unchangeable (readonly).

    Readonly Array

    Typescript comes with the ReadonlyArray<T> which is the same as Array<T> with all mutating methods removed, making sure you do not change the Array after creation.

    let a: number[] = [1, 2, 3]
    let ro: ReadonlyArray<number> = a;
    ro.push(4); // Error !
    ro[0] = 4; // Error !
    ro.length = 100; // Error !
    a = ro; // Error ! cannot assign ReadonlyArray<number> to Array<number>
    a = ro as number[]; // Ok type assertion (type casting) is allowed
    

    Readonly vs. const

    readonly is to properties what const is to variables.

    Excess Property Checks

    Optional properties in interfaces give you the possibility to not pass them. And what can happen is that if you try to pass a misspelled property as an object literal, you might inadvertently insert a bug.

    interface SquareConfig {
      color?: string;
      width?: number;
    function createSqaure(config: SquareConfig): { color: string, area: number } {
      // ...
    let s = createSquare({ colour: "Orange" }); // error: Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?
    

    TypeScript does excessive property checking when passing an object literal as the parameter. This is to avoid misspelled properties in the object literal. You can easily get around these checks with type assertions

    let s = createSquare({ colour: "Orange" } as SquareConfig); // Works, but with a bug inserted !
    

    Another approach is to allow additional properties within the interface itself, without mentioning their explicit names. The following allows having both color and width with their respective types, but additionally any other property of any type, this is using index signatures.

    interface SquareConfig {
      color?: string;
      width?: number;
      [propName: string]: any;
    

    Another option is to assign the object literal to some variable. Since the variable is no longer an object literal, typescript will no longer perform excessive type checks so the compiler will not give you any error.

    let config = { colour: "Orange" };
    let s = createSquare(config); // works fine but with a bug inserted !
    

    In the majority of cases, excess property errors are actually caused by bugs, so you should not try to bypass those checks. And rather revise your type definitions and add properties to your types if needed. However, for more complex object literals that have methods and hold state, you might need to keep these techniques in mind.

    Function Types

    interface SearchFunc {
      (source: string, subString: string): boolean;
    let mySearch: SearchFunc = function(src: string, subStr: string): boolean {
      let result = src.search(subStr);
      return
    
    
    
    
        
     result > -1;
    

    Note: type checks pass even if the parameter names do not match. Only types in parameter positions need to match.

    Function parameters are checked one at a time, with the type in each corresponding position checked against each other. If you do not want to specify types at all, TypeScript's contextual type checking can infer types, since the function value is assigned directly to a variable of type SearchFunc. Here also the return type of our function expression is implied by the values it returns (here false and true). Had the function expression returned or numbers or strings, the type checker would have warned us that the type did not match the return type described in SearchFunc.

    let mySearch: SearchFunc;
    mySearch = function(src, substr) {
      let result = src.search(substr);
      return result > -1;
    

    Indexable Types

    Similarly to how we can use interfaces to describe function types, we can also describe types that we can "index into" like a[10], or ageMap["Daniel"]. Indexable types have an index signature that describes the types that we can use to index into an object, along with the corresponding return types when indexing. Let's take an example;

    interface StringArray {
      [index: number]: string;
    let myArray: StringArray;
    myArray = ["Bob", "Fred"];
    let myStr: string = myArray[0];
    

    Above we have a StringArray interface that has an index signature. This index signature states that when StringArray is indexed with a number it will return a string.

    There are two types of supported index signatures: string and number. It is possible to support both types of indexers, but the type returned from a numeric indexer must be a subtype of the type returned from the string indexer. This is because when indexing with a number, Javascript will actually convert that to a string before indexing into an object. That means that indexing with 100 (a number) is the same as indexing with "100" (a string), so the two need to be consistent.

    class Animal {
      name: string;
    class Dog extends Animal {
      breed: string;
    interface NotOkay {
      [x: number]: Animal;
      [x: string]: Dog; // Error: getting an animal with numeric index might return you a totally different type of Animal (numeric index should return Animal, but since it is converted to string first, and strings return Dogs, the numeric may return Dog as well.)
    

    While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that all properties match their return types. This is because a string index declares that obj.property is also available as obj["property"]. In the following example, name's does not match the string's index type, and the type checker gives an error.

    interface NumberDictionary {
      [index: string]: number;
      length: number; // Ok length is a number, matches above
      name: string; // Error: name is not a number, does not match the string index type `number`
    interface NumberDictionary {
      [index: string]: number | string;
      length: number; // Ok length is a number, matches above
      name: string; // Ok`both are tolerated now
    

    We can also make index signatures readonly in order to prevent assignments to their indices.

    interface ReadonlyStringArray {
      readonly [index: number]: string;
    let sa: string[] = ["Hey", "These", "Elements", "Will", "Never", "Change"]
    sa[0] = "Change Now!"; // Error - index signature is readonly
    

    Class Types

    Implementing in interfaces

    One of the most common uses of interfaces in languages like C# and Java, that of explicitly enforcing that a class meets a particular contract is also possible in Typescript.

    interface ClockInterface {
      currentTime: Date;
    class Clock implements ClockInterface {
      currentTime: Date = new Date();
      constructor(h: number, m: number) {}
    

    You can also describe methods in an interface that are implemented in the class, as we do setTime in the below example:

    interface ClockInterface {
      currentTime: Date;
      setTime(d: Date): void;
    class Clock implements ClockInterface {
      currentTime: Date = new Date();
      setTime(d: Date): void {
        this.currentTime = d;
      constructor(h: number, m: number) {}
    

    interfaces describe the public side of the class, rather than both the public and private side of the class. This prohibits you from using the to make sure that a class uses specific types for the private side of the class.

    Difference between Static and Instance side of classes

    When working with classes and interfaces it is helpful to keep in mind that the class has two types: the type of the static side and the type of the instance side. You may notice that if you try to create an interface with a construct signature and try to create a class that implements this signature, you get an error.

    interface ClockConstructor {
      new (hour: number, minute: number);
    // Error!
    class Clock implements ClockConstructor {
      currentTime: Date;
      constructor(h: number, m: number) {}
    

    This is because when a class implements an interface only the instance part of the class is checked against the interface. Since the constructor sits in the static side of the class it is not included in the check. Therefore the interface instance requirement is not met by the class in the example above.

    Instead, you would need to work with the static side of the class directly. In the following example, we define two interfaces: ClockConstructor for the constructor and ClockInterface for the instance side of the class.

    interface ClockConstructor {
      new (hour: number, minute: number);
    interface ClockInterface {
      tick(): void;
    function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
      return new ctor(hour, minute);
    class DigitalClock implements ClockInterface {
      currentTime: Date;
      constructor(h: number, m: number) {}
      tick() {
        console.log('beep beep');
    class AnalogClock implements ClockInterface {
      currentTime: Date;
      constructor(h: number, m: number) {}
      tick() {
        console.log('tick tock');
    let d = createClock(DigitalClock, 12, 0);
    let a = createClock(AnalogClock, 7, 32);
    

    Because createClock first parameter is of type ClockConstructor the argument passed to it, must implement it. We know from ES5 to ES6 that class notation is syntactic sugar for simple the former function name notation, this is useful to understand that the name of the class, is actually a reference to the function containing the constructor in it's prototype (which is invoked with the new keyword).

    Another simple way is to use class expressions:

    interface ClockConstructor {
      new (hour: number, minute: number);
    interface ClockInterface {
      tick(): void;
    const Clock: ClockConstructor = class Clock implements ClockInterface {
      constructor(h: number, m: number) {}
      tick() {
        console.log('beep beep');
    

    Here we do the same thing, we first create the class Clock on the right hand side of the = enforcing the implementation of the ClockInterface, then we store the whole expression into a constant Clock on which we enforce the ClockConstructor. By the same reason as above, what we store in const Clock is actually the constructor function, which has a prototype with the tick and constructor etc.

    Extending interfaces

    Like classes, interfaces can extend each other:

    interface Shape {
      color: string;
    interface Square extends Shape {
      sideLength: number;
    

    You can compose interfaces from multiple others:

    interface Shape {
      color: string;
    interface Bounceable {
      bounce(x: number, y: number, force: number): void;
    interface BouncableSquare extends Shape, Bounceable {
      sideLength: number;
    

    Hybrid types

    As mentioned earlier, interfaces can describe rich types present in real world javascript. Because of JavaScript's dynamic and flexible nature, you may occasionally encounter an object that works as a combination of some of the types described above.

    One such example is an object that acts both as a function and an object, with additional properties.

    interface Counter {
      (start: number): string;
      interval: number;
      reset(): void;
    function getCounter(): Counter {
      let counter = (function(start: number) {}) as Counter;
      counter.interval = 100;
      counter.reset = function() {};
      return counter;
    let counter = getCounter();
    counter(10);
    counter.reset();
    counter.inverval = 10;
    

    When interacting with 3rd party Javascript libraries you may need to use patterns like these to fully describe the shape of the type.

    Interfaces Extending Classes

    When an interface type extends a class type, it inherits its members but not their implementation. Interfaces even inherit the private and protected members of the base class. This means that when you create an interface that extends a class with private or protected members, that interface can only be implemented by that class or subclass of it.

    This is useful when you have a large inheritance hierarchy, but want to specify that your code works with only subclasses that have certain properties. The subclasses don't need to be related, other than inheriting from the base class.

    class Control {
      private state: any;
    // SelectableControl inherits the private state from Control
    interface SelectableControl extends Control {
      select(): void;
    // Ok because SelectableControl is checking if Button has a private state
    // originating in Control, which it does because Button extends Control
    // but Button has no direct access to Control's private state, yet
    // what SelectableControl checks is whether Button has private state
    // in its prototypical chain, and if it originates in Control, which
    // it does.
    class Button extends Control implements SelectableControl {
      select() {}
    // TextBox inherits the private state only Control has access to it though
    class TextBox extends Control {
      select() {}
    // Error - property state is missing in type 'Image'
    // The problem here is that SelectableControl does not find
    // a private state originating in Control, therefore it crashes
    // even though Image has a private member state, just not originating
    // in the same declaration as SelectableControl takes it from (Control).
    
    
    
    
        
    
    class Image implements SelectableControl {
      private state: any;
      select() {}
    class Location {
    

    Only descendants of Control will have a state private member that originates in the same declaration, which is a requirement for private members to be compatible. Within the Control class it is possible to access the state private member through an instance of SelectableControl.

    Intersection types

    So if you have an intersection between two types, basically you have a narrower type, but with broader properties:

    type Duck = Quackable & Swimable; // Duck has the union of Quackable and Swimable properties
    

    A duck is only a duck if it can both quack and swim. If it' can only do one of the two, it is not a duck. What you get is the union of all properties, which is counterintuitive, but since there are more properties, less objects conform to it which makes it narrower…

    Union types

    On the other hand, if you have a union between two types, basically you have a broader type, but narrower set of property requirements:

    type Flyable = Eagle | Butterfly; // Flyable has the intersection of Eagle and Butterfly
    

    An Eagle can fly and pray, and the butterfly can fly and float. The intersection of the properties is the fly property. The union of these is therefore a smaller property set, thus a broader set of matching objects.

    Custom type guards

    Let's say you do not want to cast a type, but there is some custom logic that you require to know whether some object belongs to a type. So you can do something like this:

    const canFly = (animal: Animal): animal is Flyable => typeof (animal as any).fly === 'function'
    if (canFly(myAnimal)) {
      animal.fly();
    

    with the above, you can tell typescript that it can assume that this is actually a flyable. It is kind of a safer cast, but it depends on how safe your implementation of this function is. Another example.

    funciton isFish(pet: Fish | Bird): pet is Fish {
      return (pet as Fish).swim !== undefined;
    

    pet is Fish is our type predicate in this example. A predicate takes the form of parameterName is Type where parameterName must be a parameter name from the current function's signature. Any time isFish is called with some variable as in:

    if (isFish(pet)) {
      pet.swim()
    } else {
      pet.fly()
    

    typescript will narrow that variable down to that specific type, if the variable's original type is compatible. Note that typescript not only knows that pet is a Fish in the if branch; it also knows that in the else branch, if you don't have a Fish then it must be a Bird (Because it would have thrown a compilation error if the parameter passed to isFish() did not belong to the union type: Fish | Bird).

    Using the in operator

    The in operator acts as a narrowing expression for types. In the n in x expression, n is a string literal and x is a union type.

    function move(pet: Fish | Bird) {
      if ("swim" in pet) {
        return pet.swim();
      return pet.fly();
    

    typeof typeguards

    function isNumber(x: any): x is number {
      return typeof x === "number";
    function isString(x: any): x is string {
      return typeof x === "string";
    function padLeft(value: string, padding: string | number) {
      if (isNumber(padding)) {
        return Array(padding + 1).join(" ") + value;
      if (isString(padding)) {
        return padding + value;
      throw new Error(`Expected string of number got ${padding}`);
    

    You actually don't need to write a function to let Typescript know that you are typeguarding. You can write them inline. The only requirement is that the typenames you compare to are in: "number", "string", "boolean", or "symbol".

    instanceof type guards

    instanceof is a way to narrow down a type by the constructor function. For example:

    interface Padder {
      getPaddingString(): string;
    class SpaceCountPadder implements Padder {
      constructor(private numSpaces: number) { }
      getPaddingString(): string {
        return Array(this.numSpaces + 1).join(" ");
    class StringPadder implements Padder {
      constructor(private value: string) { }
      getPaddingString(): string {
        return this.value;
    function getRandomPadder() {
      return Math.random() > 0.5 ? new SpaceCountPadder(5) : new StringPadder("---");
    let padder: Padder = getRandomPadder();
    if (padder instanceof SpaceCountPadder) {
      padder; //constructed with SpaceCountPadder
    if (padder instanceof StringPadder) {
      padder; //constructed with StringPadder
    

    The right side of instanceof must be a constructor function and typescript will narrow down to:

  • the type of the function's prototype property if its type is not any
  • the union of types returned by that type's construct signature in that order.
  • Nullable types

    Typescript has two special types null and undefined that have the values null and undefined respectively. By default, the type checker considers these two types assignable to anything. Effectively, null and undefined are valid values of every type. That means that it si not possible to stop them from being assigned to variables of any type. The inventor of null Tony Hoare calls this his billion dollar mistake. That is why typescript has the --strictNullChecks, when you declare a variable, it's type does not include null or undefined, which means you cannot assign them unless you explicitly include them via a union.

    let s = "hello";
    s = null; // error : 'null' is not assignable to 'string'
    let sn: string | null = "world";
    sn = null; // works
    sn = undefined; // error : 'undefined' is not assignable to 'string | null'
    

    Note: TypeScript treats null and undefined as different in order to match JavaScript semantics. So string | null is different than string | undefined and string | null | undefined.

    With --strictNullChecks an optional parameter automatically adds | undefined:

    function f(x: number, y?: number): number {
      if (y === undefined) {
        return x;
      return x + y;
    f(1, 2); // works
    f(1); // works
    f(1, undefined); // works
    f(1, null); // error: 'null' is not assignable to 'number | undefined'
    

    The same is true for optional properties.

    class C {
      a: number;
      b?: number;
    let c = new C;
    c.a = null; // error : 'null' is not assignable to 'number'
    c.a = unefined; // error : 'undefined' is not assignable to 'number'
    c.b = null; // error : 'null' is not assignable to 'number'
    c.b = undefined; // works
    

    Type guards and type assertions

    Since nullable types are implemented with a union, you need to use typeguard to get rid of the null.

    function f(sn: string | null): string {
      if (sn == null) {
        return "default"
      return sn;
    

    The null elimination is pretty obvious here, but you can use terser operations:

    function f(sn: string | null): string {
      return sn || "default";
    

    ! for known non-null

    In cases where the compiler can't eliminate null or undefined, you can use type assertion operator to manually remove them. The syntax is postfix !: identifier! removes null and undefined from the type of identifier. In the example below, children[0] could potentially be undefined if children is empty for example. So what we do with the ! is tell typescript that children[0]'s type does not contain undefined or null. And so it will contain the rest of types with which it was defined.

    person.children[0]!.name; // here the developer tells ts that there will always be an element within the children array.
    

    Another example using nested functions. We use nested functions because the compiler does not know what the value of the outer function's param will be before it is being called. So the compiler cannot eliminate nulls inside a nested function except if it is an IIFE which it isn't here :

    function broken(sn: string | null): string {
      function postFix(epithet: string) {
        return sn.charAt(0) + '. the' + epithet; // error: 'sn' is possibly 'null'
      sn = sn || "Bob";
      return postFix("Great");
    function fixed(sn: string | null): string {
      function postFix(epithet: string) {
        return sn!.charAt(0) + '. the' + epithet; // works
      sn = sn || "Bob";
      return postFix("Great");
    

    Literal Types

    You can actually use values as types. So if types are sets, a type defined with 3 values, is just the set of 3 values that conform to the type.

    type Move = 'ROCK' | 'PAPER' | 'SCISSORS';
    

    Type string is the set of all strings, and type Move above is just the set of these 3 strings. So:

    let a: Move = 'HEY'; // Error: cannot assign 'HEY' to type Move
    let b: Move = 'PAPER'; // Works!
    

    Never

    The never type represents the type of values that never occur. For instance, never is the return type for a function expression that will always throw an exception or one that never returns (like an infinite loop).

    function myError(message: string): never {
      throw new Error(message);
    function fail() {
      return myError('Something failed'); // inferred return type is never
    function loop(): never {
      while (true) {
        console.log("this will never stop");
    

    never is the return type of a function that does not return. For example if you throw an error or you loop, that is a never

    const throws = (): never => {
      throw new Error('this never returns');
    const loops = (): never => {
      while (true) {
        console.log('a piece of eternity, this function never returns');
    

    Unknown

    unknown is a special type that forces you to do castings before being able to be assigned to a variable of another type. Use unknown for example when a user provides a value which you do not know the type in advance.

    let a: string;
    let x: any;
    a = x; // any assigned to a string compiles
    x = a; // string assigned to any compiles
    let y: unknown;
    y = a; // unknown accepts string, compiles
    a = y; // Error: string does not accept unknown, NOT COMPILES
    

    Index Types

    keyof MyType gives the set of all key types. From there you can retrieve the type of a specific key. And with all the keys you can get the whole union of types.

    type Duck = {
      color: string;
      feathers: number;
    type DuckProps = keyof Duck; // = 'colors' | 'feathers'
    type ColorType = Duck['color']; // = string
    type DuckValues = Duck[DuckProps]; // = string | number
    

    Conditional Types

    Depending on T, it will resolve to a type or another one. In the example below, if we pass a a boolean type to the variable type in StringOrNumber then we get a string type.

    type StringOrNumber<T> = T extends boolean ? string : number;
    type T1 = StringOrNumber<true>; // string
    type T2 = StringOrNumber<false>; // string
    type T3 = StringOrNumber<object>; // number
    

    This is a small example to get the type name of a specific type:

    type TypeName<T> =
      T extends string ? "string" :
      T extends number ? "number" :
      T extends boolean ? "boolean" :
      T extends undefined ? "undefined" :
      T extends Function ? "function" :
      "object";
    type T0 = TypeName<string>; // "string"
    

    The TypeName of a string is the string "string"

    Infer

    infer is a special keyword that you can use within a conditional type.

    type ElementType<T> = T extends (infer U)[] ? U : never;
    type T0 = ElementType<[]>; // = never
    type T1 = ElementType<string[]>; // = string
    

    Here we are using infer which is used to the right side of extends. What it means is : if T is an array of something, infer the type of that something (U) and resolve to that something (U); otherwise resolve to never. T0 resolves to never because T is an array of nothing since it has no arguments. T1 on the other hand.

    Misc Patterns

    Functional Programming

    There is a difference between data and an object. The data is just pure data, whereas the object usually has methods. But the reality is we don't actually need objects, we only need functions that perform operations on those data structures. The cool thing is that you don't need to define any classes or constructors for data structures, you just need to define what kinds of values are there.

    In the example below, typescript will tell you if you do any mistake (meaning that you don't need any constructors or any class etc), only the function that transforms one structure into the next.

    interface RawPerson {
      identifier: number;
      first_name: string;
      last_name: string;
    interface Person {
      id: string
      fullName: string;
    const transformPerson = (raw: RawPerson): Person => {
      return {
        id: `${raw.identifier}`,
        fullName: `${raw.first_name} ${raw.last_name}`,
    

    Discriminate Unions

    So we have talked about unions, this is how unions look like. What the union does, is for common properties, it will unite the property values.

    type Eagle = {
      kind: 'eagle';
      fly: () => 'fly';
    type Duck = {
      kind: 'duck';
      quack: () => 'quack';
    type Bird = {
      kind: 'bird';
    type Animal = Eagle | Duck | Bird; // If the object has any of the above props
    let a: Animal = {kind: 'a', }; // Error : Type '"a"' is not assignable to type '"eagle" | "duck" | "bird"'
    let b: Animal = {kind: 'bird', }; // b is of type Bird
    b.quack(); // Error: property quack() does not exist on type 'Bird'
    

    How can you discriminate unions?

    const doSomething = (animal: Animal): string => {
      switch (animal.kind) {
        case "eagle":
          return animal.fly();
          break;
        case "duck":
          return animal.quack();
          break;
        default:
          return assertNever(animal);
    const assertNever = (animal: Animal): never => {
      throw new Error(`unknown Animal, ${animal.kind}`);
    

    In the above example, if we had not added a default then the doSomething return type should have been string | void because the "bird" case would not be handled, and so anytime a Bird was passed, doSomething would return undefined. To solve this, we can add the default case which makes sure that when an Animal not in the switch case list is passed (such as Bird) an error will be thrown. This is achieved returning a call to our assertNever which has return type never meaning it throws every time. Since never is no return, we can keep the string return type in the doSomething.

    Derive Types from Constants

    const MOVES = {
      ROCK: { beats: 'SCISSORS', },
      PAPER: { beats: 'ROCK', },
      SCISOR: { beats: 'PAPER', },
    // Lets say we want to define the type Move which is the set of strings: "ROCK" | "PAPER" | "SCISSORS"
    type MoveBad = keyof MOVES; // any 'MOVES' refers to a value, but is being used as a type here.
    // the above does not work that is why we need to add a `typeof` keyword which gets the type of the moves constant
    // and then retrieve the keys of the type (typeof MOVES), we do that with the help of `keyof`
    type Move = keyof typeof MOVES;
    const move: Move = 'ROCK';
    

    Untrusted user input

    If something is unknown you want perform some kind of validation, and make the unknown type comply to some known type and return that. In the following example we are using the typeof keyword which works like in javascript.

    const validateInput = (s: unknown): number => {
      let n;
      switch (typeof s) {
        case 'number':
          // handle and return a number
          break;
        case 'string':
          // handle and return a number
          break;
        default:
          throw new Error('This kind of input is not supported');
    

    Mapped types

    Readonly

    type Readonly<T> = { readonly [P in keyof T]: T[P] };
    type ReadonlyDuck = Readonly<Duck>; // { readonly color: string; readonly feathers: number; }
    

    Partial

    type Partial<T> = { [P in keyof T]?: T[P] };
    type PartialDuck = Partial<Duck>; // { color?: string; feathers?: number; }
    

    Required

    type PartialDuck = {
      color?: string;
      feathers?: number;
    type Required<T> = { [P in keyof T]-?: T[P] };
    type RequiredDuck = Required<PartialDuck>; // { color: string; feathers: number; }
    
     
    推荐文章