npm init
npm i express
npm i -D typescript ts-node nodemon @types/node @types/express
# npm install -save-dev typescript ts-node nodemon @types/node @types/express
touch .gitignore
mkdir {dist,src}
tsc --init
Update package.json
{
"name": "hello-typescript",
"version": "1.0.0",
"description": "Hello typescript",
"main": "index.js",
"scripts": {
"test": "test",
"start": "node ./dist/app.js",
"dev": "nodemon ./dist/app.js",
"build": "tsc -p ."
},
"author": "Linh Pham",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
}
}
Update tsconfig.json
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"strictNullChecks": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Run a specify .ts
file
npx ts-node src/_class.ts
- The new type of intersect property is
never
type LeftType = {
id: string
}
type RightType = {
id: number
}
type IntersectionType = LeftType & RightType
type PowerUser = Omit<User, 'type'> & Omit<Admin, 'type'> & {
type: 'powerUser'
};
- Optional parameter must be put on the end of function parameters.
- The return type of implementation signature function must be cover all return type of the rest overload signature. The parameter is no mater.
// Notice the return type of the implementation signature.
function hello(number: number): number;
function hello(text: string): string;
function hello(content: string, name: string, user: User): User;
function hello(content: string, name: string, admin: Admin): Admin;
function hello(number: any): string | number | User | Admin {
return ''
}
- Type Guards allow you to check the type of variable or an object with an operator.
- Type Guard
- Such as Smart cast in Kotlin.
- To check primitive type using
typeof
- To check class type using
instanceof
- To check interface, type alias type using
in
operator narrowing- Type predicates
- Add type literal property such as
type: 'admin
orkind: 'admin'
. See Discriminated unions
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square
function isSquare(shape: Shape): shape is Square {
return shape.kind === 'square'
}
// Using type literal
function getArea(shape: Shape): number {
if (shape.kind === 'square') {
return shape.sideLength ** 2
} else {
return shape.radius ** 2 * Math.PI
}
}
// Using in operator
function getArea(shape: Shape): number {
if ('sideLength' in shape) {
return shape.sideLength ** 2
} else {
return shape.radius ** 2 * Math.PI
}
}
// Using type predicate
function getArea(shape: Circle | Square): number {
if (isSquare(shape)) {
return shape.sideLength ** 2
} else {
return shape.radius ** 2 * Math.PI
}
}
- Used to exhaustiveness check. Typically, it is used in switch case.
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
Adding a new member to the Shape union, will cause a TypeScript error:
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
// Type 'Triangle' is not assignable to type 'never'.
return _exhaustiveCheck;
}
}
- Use for update or filter.
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return {...todo, ...fieldsToUpdate};
}
- Ensure every optional properties are non-null
interface Props {
a?: number;
b?: string;
}
const obj: Props = {a: 5};
// Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
const obj2: Required<Props> = {a: 5};
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
// Cannot assign to 'title' because it is a read-only property.
todo.title = "Hello";
- To construct new type and remove some keys
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoPreview = Omit<Todo, "description">;
type TodoInfo = Omit<Todo, "completed" | "createdAt">;
type PowerUser = Omit<User, 'type'> & Omit<Admin, 'type'> & {
type: 'powerUser'
};
function second(option: string) {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("second(): called");
};
}
class ExampleClass {
@second('hello')
method() {
console.log(1)
}
}
- Execute after class declaration not when instantiate
- Notice the last line: actual it first generates a function name
__decorate
. - Then executes it wit after class declaration.
- Run get and result of
second()
- Pass the result to the
__decorate()
- The order is:
second()
β__decorate()
βfunctionReturnFromSecond()
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length
var r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;
var d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
r = Reflect.decorate(decorators, target, key, desc);
} else {
for (var i = decorators.length - 1; i >= 0; i--) {
if (d = decorators[i]) {
// This line will invoke function return from second function.
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
}
}
}
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
function second(target: Object,
propertyKey: string,
descriptor: PropertyDescriptor) {
// This execute before __decorate()
// In nestjs, @Get(), @Post() maybe use descriptor.value to register handler to router...
return function (target, propertyKey, descriptor) {
// We can override origin method by: descriptor.value = function (...args) { }
// This still execute when class declare
};
}
class ExampleClass {
method() {
console.log(1);
}
}
__decorate([ second() ], ExampleClass.prototype, "method", null);
Another example to measure the execution time. See more
function measure(target: Object,
propertyKey: string,
descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
// descriptor.value is point to launch method
// In this case we override origin method
descriptor.value = function (...args) {
// This will invoke after launch is get called
const start = performance.now();
const result = originalMethod.apply(this, args);
const finish = performance.now();
console.log(`Execution time: ${finish - start} milliseconds`);
return result;
};
return descriptor;
}
class Rocket {
@measure
launch() {
console.log("Launching in 3... 2... 1... π");
}
}
- Essential of Typescript
import { City, Person, Product, Employee } from "./dataTypes";
function getValue<T, K extends keyof T>(
item: T, keyname: K) { console.log(`Value: ${item[keyname]}`);
}
type priceType = Product["price"];
type allTypes = Product[keyof Product];
let p = new Product("Running Shoes", 100);
getValue<Product, "name">(p, "name"); getValue(p, "price");
let e = new Employee("Bob Smith", "Sales");
getValue(e, "name");
getValue(e, "role");
The indexed access operator is expressed using square brackets following a type so that Product["price"]
, for example, is number, since that is the type of the price
property defined by the Product
class. The indexed access operator works on literal value types, which means it can be used with index type queries, like this:
type allTypes = Product[keyof Product];
The keyof Product
expression returns a literal value type union with the property names defined by the Product
class, "name" | "price"
. The indexed access operator returns the union of the types of those properties, such that Product[keyof Product]
is string | number
, which is the union of the types of the name and price properties.
The indexed access operator is most commonly used with generic types, which allows property types to be handled safely even though the specific types that will be used are unknown,
import { City, Person, Product, Employee } from "./dataTypes";
function getValue<T, K extends keyof T>(item: T, keyname: K): T[K] {
return item[keyname];
}
let p = new Product("Running Shoes", 100);
console.log(getValue<Product, "name">(p, "name"));
console.log(getValue(p, "price"));
let e = new Employee("Bob Smith", "Sales");
console.log(getValue(e, "name"));
console.log(getValue(e, "role"));
T[K]
tells the compiler that the result of the getValue function will have the type of the property whose name is specified by the keyof type argument, leaving the compiler to determine the result types based on the generic type arguments used to invoke the function. For the Product
object, that means a name
argument will produce a string
result, and a price
argument will produce a number
result.
class Collection<T, K extends keyof T> implements Iterable<T> {
private items: Map<T[K], T>;
constructor(initialItems: T[] = [], private propertyName: K) {
this.items = new Map<T[K], T>();
this.add(...initialItems);
}
get(key: T[K]): T {
return this.items.get(key);
}
}