shou2017.com
JP

TypeScript Basic Grammar

Sun Jun 16, 2019
Sat Aug 10, 2024

Variables

Syntax: Variable Declaration

let name: type[= initial]
--------------------------

- name: Variable name 

- type: Variable type

- initial: Initial value

Example 1:

let age: number = 20;
console.log(age);

Since TypeScript checks types, you cannot store a string in a number type variable.

let age: number = 20;
age = 'Twenty';
// Error

Data Types

// boolean type
let flag: boolean = false;

// number type
let age: number = 30; // Decimal
let binary: number = 0b11; // Binary
let exp: number = 1.23e-5; // Exponential notation

// string type
let name: string = 'John Doe';

// string type (template string)
let meibo = `Name: ${name}`;
console.log(meibo);  => John Doe

// Array type
let os: string[] = ['macOS', 'Windows', 'Linux'];
console.log(os[2]);  => Linux

// Object type (associative array)
let duet: { [index: string]: string } =
    { first: 'John', second: 'Smith'};
console.log(duet['first']);

In TypeScript, the ability to specify the index/value type of an associative array is called an index signature.
{ [index: string]: string }
   index type     value type

Although it's called an index signature, "index" is just a placeholder name, so you could use "key" instead.

The index signature notation can also be extracted as an interface instruction that defines only the signatures of methods (name, arguments, return value type) required to implement a specific functionality, without including the actual processing.

interface MyMap {
    [index: string]: string;
}
let duet: MyMap = { first: 'John', second: 'Smith' };

// Tuple type
A tuple is a data structure that can hold multiple values like a list.

- Immutable (cannot be changed)

- Slightly faster execution speed

let score: [number, string] = [100, 'great'];
console.log(score[0]); => 100
console.log(score[1]); => great

// enum type (enumeration)
The enum type (enumeration) is used when it is convenient to assign a series of integer values to multiple variables.

enum Color { Red, Green, Blue };
let red: Color = Color.Red;
console.log(red);   => 0
console.log(Color[red]);    => Red

Constants

Syntax: Constant Declaration

const name: type = value
--------------------------

- name: Constant name

- type: Constant type

- value: Constant value

Example 1:

const PI: number = 3.14;
PI = 3.141 // Error when trying to reassign

Type Assertion

Syntax: Type Assertion

Type casting. Type conversion.

<type> variable
--------------------------

- type: Type to convert to

- variable: Variable to be type-converted

Example 1:

function greet(name: string) {
    return 'Hello, ' + name;
}
 // Passing a number to the string type parameter name (cast to any type)
 console.log(greet(<any>108));

 // Can also be replaced with as syntax
 // as syntax is also ok instead of <...>
 console.log(greet(108 as any));

Functions

Syntax: Function Declaration

function name(arg : type, ...) : r_type {
    ...body...
}
--------------------------

- name: Function name

- arg: Parameter

- type: Parameter type

- r_type: Return value type

- body: Function body

Example 1:

// No parameters or return value. void type means a type that has no value.
function greet(): void {
    console.log('Hello');
}
greet(); => Hello

// With parameters and return value
function add(x: number, y: number): number {
    return x + y;
}
console.log(add(4, 5));

// To make a parameter optional, append ? to the parameter name.
function showCost(price: number, discount?: number) {
    if (discount) {
        console.log(`Cost: ${price - discount}`);
    } else {
        console.log(`Cost: ${price}`);
    }
}

showCost(1000);
showCost(1000, 200)


// Forgetting to append ? to the parameter will result in an error
function showCost(price: number, discount: number) {
    if (discount) {
        console.log(`Cost: ${price - discount}`);
    } else {
        console.log(`Cost: ${price}`);
    }
}

showCost(1000); => Expected 2 arguments, but got 1.
showCost(1000, 200)

// Parameter default values
When specifying default values, parameters become optional unconditionally, so ? is unnecessary. Specifying it will cause an error.
function showCost(price: number, discount: number = 0) {
    if (discount) {
        console.log(`Cost: ${price - discount}`);
    } else {
        console.log(`Cost: ${price}`);
    }
}

showCost(1000); 
showCost(1000, 200)


// Variable length arguments "..."
function sum(...values: number[]): number {
    let result: number = 0;
    // The variable length argument values can be processed as an array
    for (let i: number = 0; i < values.length; i++) {
        result += values[i];
    }
    return result;
}
console.log(sum(100, 200, 300));

Arrow Functions

Syntax: Arrow Function

(arg : type, ...) : r_type => {
    ...body...
}
--------------------------

- arg: Parameter

- type: Parameter type

- r_type: Return value type

- body: Function body

Example 1:

// Rewriting the following with an arrow function
function add(x: number, y: number): number {
    return x + y;
}

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

If there's only one argument and no type declarations for arguments and return values, you can omit the parentheses around the argument, but there's not much need to do so.

Arrow functions have the property that this is fixed when declared, so it's preferable to use arrow functions over function commands whenever possible.

Function Overloading

Syntax: Function Overloading

function name(arg : type, ...) : r_type;
function name(arg : type, ...) : r_type;
...
function name(arg : type, ...) : r_type; {
    ...body...
}

--------------------------
- name: Function name

- arg: Parameter

- type: Parameter type

- r_type: Return value type

- body: Function body

Function overloading means defining multiple functions with the same name but different parameter types or orders. However, the syntax for TypeScript overloading is to first list functions with only signatures (no implementation), and then write a “catch-all” function with a body that can handle all overloads.

It’s like writing type determination in a case statement.

function search(str: string, start: number): string;
function search(str: string, start: string): string;
function search(str: string, start: any): string {
    if (typeof start === 'number') {
        return str.substring(start);
    } else {
        return str.substring(str.indexOf(start));
    }
}

let msg = 'abcdefghijklmn';
console.log(search(msg, 3));
console.log(search(msg, 'f'));

Union Types

Syntax: Union Type

// Variable variable is either type1 or type2
variable: type1 | type2
--------------------------

- variable: Variable

- type1, 2: Types

A union type is a type that binds multiple types to a variable.

Example 1:

function search(str: string, start: number): string;
function search(str: string, start: string): string;
function search(str: string, start: any): string {
    if (typeof start === 'number') {
        return str.substring(start);
    } else {
        return str.substring(str.indexOf(start));
    }
}

let msg = 'abcdefghijklmn';
console.log(search(msg, 3));
console.log(search(msg, 'f'));

Rewriting the above using union types:

function search(str: string, start: number | string): string {
    if (typeof start === 'number') {
        return str.substring(start);
    } else {
        return str.substring(str.indexOf(start));
    }
}

let msg = 'abcdefghijklmn';
console.log(search(msg, 3));
console.log(search(msg, 'f'));

Rewriting further using arrow function:

let search = (str: string, start: number | string): string => {
    if (typeof start === 'number') {
        return str.substring(start);
    } else {
        return str.substring(str.indexOf(start));
    }
}

let msg = 'abcdefghijklmn';
console.log(search(msg, 3));
console.log(search(msg, 'f'));

Classes

Syntax: Class Declaration

class name {
    modifier variable: p_type...

    modifier constructor(modifier arg: a_type,...) { ...c_body... }

    modifier method(arg: a_type,...): r_type { ...m_body... }
}
--------------------------

- name: Class name

- modifier: Modifier

- variable: Property name

- p_type: Property type

- arg: Argument

- a_type: Argument type

- c_body: Constructor body

- method: Method name

- r_type: Return value type

- m_body: Method body

Example 1:

class Dog {
    // Constructor
    constructor(private category: string, private weight: number) { }

    // toString method
    public toString(): string {
        return this.category + ':' + this.weight + 'kg'
    }
}

let mame = new Dog('Shiba', 8);
console.log(mame.toString());

Access Modifiers

  • public: Accessible from anywhere (default).
  • protected: Accessible from the current class and its derived classes
  • private: Accessible only from the current class

Static Members

Static members are fields or methods that belong to the class rather than to a specific instance.

class FigureUtil {
    // Static property PI declaration
    static PI = 3.14;

    // Static method circle declaration
    static circle(radius: number): number {
        return radius * radius * this.PI;
    }
}

console.log(FigureUtil.PI);
console.log(FigureUtil.circle(2));

get/set Accessors

Syntax: get/set Accessors

get name(): type { ... }
set name(value: type) { ... }
--------------------------

- name: Property name

- type: Property type

- value: Value passed when setting

get/set accessors can be accessed from outside the class as if they were properties. → They are mechanisms for implementing behavior when getting/setting values internally as methods, rather than simple variables.

By using get/set accessors, you can implement “arbitrary processing associated with the input/output of values” that cannot be expressed by properties (member variables) alone, such as validating the value when setting it or converting the value when retrieving it.

class Dog {
    // Private variable to store the weight property value
    private _weight: number;

    // Get weight property
    get weight(): number {
        return this._weight;
    }

    // Set weight property (exception if less than 0)
    set weight(value: number) {
        if (value < 0) {
            throw new Error('The weight property must be specified as a positive number.');
        }
        this._weight = value;
    }
}

let poodle = new Dog();
poodle.weight = -13; // => The weight property must be specified as a positive number.

Inheritance

Syntax: Inheritance

class name extends parent {
    ...body...
}
--------------------------

- name: Class name

- parent: Base class name

- body: Class definition

extends keyword → Inheriting from an existing class

super keyword → Calling base class (superclass) methods from a derived class (subclass)

class Dog {
    constructor(protected category: string, protected weight: number) { }

    toString(): string {
        return this.category + ':' + this.weight + 'Kg'
    }
}

// UltraDog class inheriting from Dog class
class UltraDog extends Dog {
    toString(): string {
        // Call the toString method of the base class
        return 'Super' + super.toString();
    }
}

let dober = new UltraDog('Doberman', 35);
console.log(dober.toString()); => SuperDoberman:35Kg

To call a parent method, use super.methodName(...).

It’s also possible to override the constructor and call the parent constructor from within it. In that case, simply use super(...).

Interfaces

Syntax: Interface Declaration

interface name extends s_name {
    ...body...
}
--------------------------

- name: Interface name

- s_name: Parent interface name

- body: Interface body

An interface is like a class with only signatures and no implementation, used to enforce certain functionalities (implementations) on classes.

An interface is like a contract. Unlike class inheritance, implementing an interface creates an entry point to reference other functionality.

Example:

Electricity Consumers → Power Company ← Thermal Power, Nuclear Power, or Wind Power Generation

Consumers can use electricity (methods) formally without being aware of how the electricity is produced (implementation).

To define an interface itself, use the interface directive. Like classes, you can inherit from other interfaces using the extends keyword.

// Pet interface
interface Pet {
    name: string;   // Property definition
    call(): void;   // Method definition
}

// Declare a class implementing Pet
class Dog implements Pet {
    name: string;
    call(): void {
        console.log(this.name + '-san');
    }
}

let pochi = new Dog();
pochi.name = 'Pochi';
pochi.call(); => Pochi-san


// Power Contract interface
interface PowerContract {
    method: string;
    call(): void;
}

// Declare a Power Company class implementing PowerContract
class PowerCompany implements PowerContract {
    method: string;
    call(): void {
        console.log('Supplying electricity using ' + this.method);
    }
}

let tepco = new PowerCompany();
tepco.method = 'nuclear power';
tepco.call(); => Supplying electricity using nuclear power

To implement an interface, use the implements keyword.

To implement multiple interfaces, separate them with commas:

implements Pet, Movable

Type Annotations

Using interfaces as type annotations.

interface Member {
    name: string;   // Property signature
    greet(): void;  // Method signature
}

// Declare a variable mem of Member type
let mem: Member = {
    name: 'John Doe',
    greet() {
        console.log('Hello, ' + this.name);
    }
};

mem.greet(): => Hello, John Doe

Function Types

// AddFunc type that takes number type arguments a, b and returns a number type value
interface AddFunc {
    (a: number, b: number): number
}

// Define a function of AddFunc type
let add: AddFunc = function (x: number, y: number): number {
    return x + y;
};

Index Signature Declaration

{ [index: string]: string }
   index type     value type

Generics

Syntax: Generics

class name<T> {
    ...body...
}
--------------------------

- name: Class name

- T: Type parameter

- body: Class body

Generics are a feature for associating general-purpose classes and methods with specific types.

class GenericClass<T> {
    data: T;

    getData(): T {
        return this.data;
    }
}

let gen = new GenericClass<number>();
gen.data = 108;
console.log(gen.getData()); => 108

To define a generic type, simply append a type parameter like after the class name.

When instantiating a generic type, append the type to be bound in the form <…>. In the example, the type parameter T is assigned the number type. A GenericClass object bound to the number type is generated.

This is written in a complicated way, but basically, it’s saying that when you specify a type when creating a GenericClass object, T is conveniently bound to that type.

Reference: Angular Application Programming

Although this is an Angular book, it has a good introduction to TypeScript as well.

Angularアプリケーションプログラミング

See Also