# 🔶 TypeScript Basic Knowledge

# 🔸 Primitive Types

string, number, boolean, null, undefined, Symbol

// Known type
const str: string = "Hello World";

// Unknown with expectation
let num = <unknown>getNumber();
// Known expectation
num = <number>getNumber();

// Function returning a number
function getNumber(): number {
  return 30;
}

// Void function
function count(): void {
  console.log(1 + 3);
}

# Any Type

A variable of any type cannot change its type during operations.

  1. Any type allows any operation, and the return type is of any.
  2. When a variable is declared without specifying a type and no value is assigned, it defaults to any.
let someText: any = 30; // Will cause an error if set to number
someText = "someText"; // Error

# 🔸 Never

A function that never returns, or a function that always throws an error.

  • bottom level type
  • can be assigned to any type

In TypeScript, the never type is often used in the context of exhaustiveness checking, particularly in situations like switch statements. The never type represents the type of values that never occur. It is used to indicate that a function will not return normally (e.g., it throws an error or has an infinite loop).

Let's discuss how never is commonly used with switch statements and the default case:

type Fruit = "Apple" | "Banana" | "Orange";

function getFruitColor(fruit: Fruit): string {
  switch (fruit) {
    case "Apple":
      return "Red";
    case "Banana":
      return "Yellow";
    case "Orange":
      return "Orange";
    default:
      // The default case is marked as unreachable, and its return type is 'never'.
      // This is because TypeScript can infer that the switch is exhaustive, covering all possible values of 'Fruit'.
      const exhaustiveCheck: never = fruit;
      return exhaustiveCheck; // This line will never be reached.
  }
}

Explanation:

  1. The Fruit type is defined as a union of string literals representing different fruit names.
  2. The getFruitColor function takes a fruit argument of type Fruit and returns a string representing the color.
  3. The switch statement is used to handle different cases based on the value of fruit.
  4. For each known fruit case ('Apple', 'Banana', 'Orange'), the function returns the corresponding color.
  5. The default case is marked with never. This indicates to TypeScript that this case should never be reached because we've covered all possible values of Fruit.
  6. The exhaustiveCheck variable is of type never, which means TypeScript understands that it can never have a value, and this line will never be executed.

This pattern helps TypeScript catch situations where you might forget to handle a specific case in a switch statement, providing better safety and avoiding unintentional bugs.

# 型アノテーション(Type Annotation)

TypeScript では value: Type というフォーマットで宣言時の変数 に型の注釈がつけられる アノテー ションによって静的に型付けされた情報はコンパイル時のチェックに用いられ、書かれたコード中 に型の不整合があるとコンパイルエラーになる

# 型推論(Type Inference)

When the type is not explicitly specified, TypeScript infers the type based on the assigned value.

let typeS = "seven";
typeS = 10; // Inferred as string due to the initial value being a string

let typeA;
typeA = "six";
typeA = 30; // Inferred as any since no initial value is given

# Union Types

Represents a value that can be one of multiple types, using | to separate types.

let unionC: string | number = "Ray";
unionC = 100;

function getLength(something: string | number): number {
  return something.length; // Can only access properties common to both types, error
}

# Object Types - Interface

Commonly used to describe the shape of objects.

  1. Interface names typically start with a capital letter.
  2. Optional properties are denoted with ? to enhance flexibility.
  3. Index signatures allow any property of a certain type.
  4. Use readonly to mark properties as read-only.
interface Person {
  readonly id: number;
  name: string;
  age?: number; // Optional property
  [prop: string]: any; // Any property of type any
  // [prop: string]: string; // Uncommenting restricts any property to string type
}

let tom: Person = {
  id: 3387,
  name: "Tom",
  // age: 30,
};
// Constraints Tom to have the same shape as Person (no more, no less)

// Cannot modify readonly property after initialization, error
tom.id = 4069;

Function interfaces or interfaces with function properties are also possible:

interface MyFunction {
  (a: number): void;
}

interface MyObject {
  jump: (a: number) => void;
}

# Array Types

Several ways to define array types:

  1. Type + square brackets
  2. Array generics (Array)
  3. Interface for arrays
  4. Tuple types (IArguments, NodeList, HTMLCollection, Element...)
// 1.
const myArr: number[] = [1, 2, 3];
myArr.push("4"); // Method calls are type-checked, error

// 2.
const myArr2: Array<number> = [1, 2, 3];
myArr2[2] = "3"; // Error

// 3.
interface NumberArray {
  [index: number]: number;
}
const myArr3: NumberArray = [1, 2, 3];

// 4. Built-in array interfaces
function sum() {
  let args: IArguments = arguments;
}

For more built-in objects, refer to this link (opens new window).

# Function Types

Functions have input and output types, considering both is crucial.

  1. Declaration

  2. Expression

    • Be cautious when using the => syntax; it might be confusing and is recommended to use the declaration syntax.
  3. Interface definition

  4. Optional parameters marked with ?, which cannot follow required parameters.

  5. Default parameter values make parameters optional.

  6. Rest parameters can be defined using an array type (any[]).

  7. Overloads allow a function to accept different numbers or types of parameters.

// 1.
function sumFn(x: number, y: number): number {
  return x + y;
}

sumFn(1); // Error
sumFn(1, 2, 3); // Error

// 2.
const sumFnEx: (x: number, y: number) => number = function (
  x: number,
  y: number
): number {
  return x + y;
};

// 3.
interface Isum {
  (x: number, y: number): number;
}

const mySum: Isum = function (x: number, y: number): number {
  return x + y;
};

// 4. & 5.
function buildName(
  firstName: string,
  lastName?: string,
  age: number = 30
): string {
  return `${firstName} ${lastName}, now age ${age}`;
}
buildName("johnny");

// 6.
function push(array: any[], ...items: any[]) {
  items.forEach((item) => {
    array.push(item);
  });
}

// 7.
// Define precise input and output types for numbers, then implement logic
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
  if (typeof x === "number") {
    return Number(x.toString().split("").reverse().join(""));
  } else if (typeof x === "string") {
    return x.split("").reverse().join("");
  }
  return 0;
}

# Type Assertion

Manually specify a value's type, commonly used in React's TSX (TS for JSX) to distinguish between TypeScript and ES6 arrow functions.

  1. When dealing with union types, sometimes a specific type's property needs to be accessed.

    • Caution is needed to avoid runtime errors when accessing properties immediately after assertion.
  2. Parent class inheritance assertion

  3. "XXX as any" should be a last resort for solving type issues, use sparingly.

  4. Type assertion reinforcement (for old code returning any, explicitly assert its type after calling)

  5. Type assertions are only effective during compilation, having no impact on the compiled code.

  6. Type declarations are stricter; prefer using them whenever possible.

// 1.
interface Cat {
  name: string;
  run(): void;
}

interface Fish {
  name: string;
  swim(): void;
}

function getName(animal: Cat | Fish) {
  // Accessing swim directly after assertion may cause runtime errors
  // if (typeof animal.swim === 'function') {
  if (typeof (animal as Fish).swim === "function") {
    console.log("Is a fish");
  } else {
    // ...
  }
}

// 2.
class ApiError extends Error {
  code: number = 0;
}

class HttpError extends Error {
  statusCode: number = 200;
}

function isApiError(error: Error): boolean {
  if (typeof (error as ApiError).code === "number") {
    return true;
  }
  return false;
}

// 3.
// Accessing any property is legal, but this is a last resort
window.foo = 1; // Error
(window as any).foo = 1;

// 4.
function getCache(key: string): any {
  return (window as any).cache[key];
}

interface Cat {
  name: string;
  run(): void;
}

// Type assertion
const tomCat = getCache("tom") as Cat;
// Type declaration (more strict)
// const tomCat: Cat = getCache('tom');
tomCat.run();

# Declaration Files

When using third-party libraries, include their declaration files to enable type checking.

Usually, declaration statements are placed in separate files, e.g., jQuery.d.ts.

  1. declare var/let/const
  2. declare namespace creates a namespace to avoid global pollution; use the namespace when accessing its interfaces.
  3. For library declaration files, it is recommended to use @types for centralized management. Install with npm install @types/library --save-dev. No further configuration is needed for global declarations.
  4. For NPM declaration files, export and import are required to use types within modules.
// 1. Using jQuery as an example
declare const jQuery: (selector: string) => any;

// 2. Example (illustrative purposes only)
declare namespace Vue {
  function component(name: string, data: any): any;
  function mixin(data: any): void;
}

# Declaration Merging

Taking jQuery as an example, it is both a function and an object with properties. Multiple declaration statements that do not conflict will be merged.

declare function jQuery(selector: string): any;
declare namespace jQuery {
  function ajax(url: string, settings?: any): void;
}