Typescript
Problems
if ("" == 0) {
// It is! But why??
}
if (1 < x < 3) {
// True for *any* value of x!
}JavaScript’s equality operator (==) coerces its arguments, leading to unexpected behavior:
const obj = { width: 10, height: 15 };
// Why is this NaN? Spelling is hard!
const area = obj.width * obj.heigth;JavaScript also allows accessing properties which aren’t present:
TypeScript: A Static Type Checker
const obj = { width: 10, height: 15 };
const area = obj.width * obj.heigth;
// Error
Property 'heigth' does not exist on type '{ width: number; height: number; }'.
Did you mean 'height'?JavaScript Error in runtime
const obj = { width: 10, height: 15 };
const area = obj.width * obj.heigth;TypeScript Error in compiling time
console.log(4 / []);
// Error
The right-hand side of an arithmetic operation
must be of type 'any', 'number', 'bigint' or an enum type.JavaScript Error in runtime
console.log(4 / []); -> InfinityTypeScript Error in compiling time
How to add/try
Base types
Boolean
The most basic datatype is the simple true/false value, which JavaScript and TypeScript call a boolean value.
let isDone: boolean = false;Number
As in JavaScript, all numbers in TypeScript are either floating point values or BigIntegers. These floating point numbers get the type number, while BigIntegers get the type bigint. In addition to hexadecimal and decimal literals, TypeScript also supports binary and octal literals introduced in ECMAScript 2015.
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;String
Another fundamental part of creating programs in JavaScript for webpages and servers alike is working with textual data. As in other languages, we use the type string to refer to these textual datatypes. Just like JavaScript, TypeScript also uses double quotes (") or single quotes (') to surround string data.
let color: string = "blue";
color = 'red';
// You can also use template strings
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}.
sentence = I'll be ${age + 1} years old next month.`;Array
TypeScript, like JavaScript, allows you to work with arrays of values. Array types can be written in one of two ways.
In the first, you use the type of the elements followed by [] to denote an array of that element type:
let list: number[] = [1, 2, 3];The second way uses a generic array type, Array<elemType>:
let list: Array<number> = [1, 2, 3];Tuple
Tuple types allow you to express an array with a fixed number of elements whose types are known, but need not be the same.
For example, you may want to represent a value as a pair of a string and a number:
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error
//ERROR
//Type 'number' is not assignable to type 'string'.
// Type 'string' is not assignable to type 'number'When accessing an element with a known index, the correct type is retrieved:
// OK
console.log(x[0].substring(1));
//Error
console.log(x[1].substring(1));
// ERROR
// Property 'substring' does not exist on type 'number'.Accessing an element outside the set of known indices fails with an error:
x[3] = "world";
// ERROR
// Tuple type '[string, number]' of length '2' has no element at index '3'.
console.log(x[5].toString());
// ERROR
// Object is possibly 'undefined'.
// Tuple type '[string, number]' of length '2' has no element at index '5'.Unknown
We may need to describe the type of variables that we do not know when we are writing an application. These values may come from dynamic content – e.g. from the user – or we may want to intentionally accept all values in our API. In these cases, we want to provide a type that tells the compiler and future readers that this variable could be anything, so we give it the unknown type.
let notSure: unknown = 4;
notSure = "maybe a string instead";
// OK, definitely a boolean
notSure = false;Unknown
declare const maybe: unknown;
// 'maybe' could be a string, object, boolean, undefined, or other types
const aNumber: number = maybe;
// ERROR
// Type 'unknown' is not assignable to type 'number'.
if (maybe === true) {
// TypeScript knows that maybe is a boolean now
const aBoolean: boolean = maybe;
// So, it cannot be a string
const aString: string = maybe;
// ERROR
// Type 'boolean' is not assignable to type 'string'.
}
if (typeof maybe === "string") {
// TypeScript knows that maybe is a string
const aString: string = maybe;
// So, it cannot be a boolean
const aBoolean: boolean = maybe;
// ERROR
// Type 'string' is not assignable to type 'boolean'.
}Any
In some situations, not all type information is available or its declaration would take an inappropriate amount of effort. These may occur for values from code that has been written without TypeScript or a 3rd party library. In these cases, we might want to opt-out of type checking. To do so, we label these values with the any type:
declare function getValue(key: string): any;
// OK, return value of 'getValue' is not checked
const str: string = getValue("myString");Any vs Unknown
Unlike unknown, variables of type any allow you to access arbitrary properties, even ones that don’t exist. These properties include functions and TypeScript will not check their existence or type:
let looselyTyped: any = 4;
// OK, ifItExists might exist at runtime
looselyTyped.ifItExists();
// OK, toFixed exists (but the compiler doesn't check)
looselyTyped.toFixed();
let strictlyTyped: unknown = 4;
strictlyTyped.toFixed();
// ERROR
// Object is of type 'unknown'.Void
You may commonly see this as the return type of functions that do not return a value:
function warnUser(): void {
console.log("This is my warning message");
}Null and Undefined
// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;Never
// Function returning never must not have a reachable end point
function error(message: string): never {
throw new Error(message);
}
// Inferred return type is never
function fail() {
return error("Something failed");
}
// Function returning never must not have a reachable end point
function infiniteLoop(): never {
while (true) {}
}The never type represents the type of values that never occur. For instance, never is the return type for a function expression or an arrow function expression that always throws an exception or one that never returns. Variables also acquire the type never when narrowed by any type guards that can never be true.
Interfaces
function printLabel(labeledObj: { label: string }) {
console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);interface LabeledValue {
label: string;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);interface SquareConfig {
color?: string;
width?: number;
}Optional Properties
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
// ERROR
// Cannot assign to 'x' because it is a read-only property.Readonly properties
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;Extending Interfaces
Functions
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function (x: number, y: number): number {
return x + y;
};function buildName(firstName: string, lastName: string = "Smith") {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result4 = buildName("Bob", "Adams"); // ah, just rightDefault value
function buildName(firstName: string, lastName?: string) {
if (lastName) return firstName + " " + lastName;
else return firstName;
}
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
Expected 1-2 arguments, but got 3.
let result3 = buildName("Bob", "Adams"); // ah, just rightOptional value
Literal Types
String Literal Types
type Easing = "ease-in" | "ease-out" | "ease-in-out";Numeric Literal Types
type Size = 1 | 2 | 3 | 4 | 5 | 6;interface ValidationSuccess {
isValid: true;
reason: null;
};
interface ValidationFailure {
isValid: false;
reason: string;
};
type ValidationResult =
| ValidationSuccess
| ValidationFailure;Advance Types
- Generics
- Keyof Type Operator
- Typeof Type Operator
- Indexed Access Types
- Conditional Types
- Mapped Types
- Template Literal Types
Generic
function identity(arg: number): number {
return arg;
}function identity(arg: any): any {
return arg;
}function identity<Type>(arg: Type): Type {
return arg;
}
let output = identity<string>("myString");Keyof
type Point = { x: number; y: number };
type P = keyof Point;The keyof operator takes an object type and produces a string or numeric literal union of its keys. The following type P is the same type as “x” | “y”:
Typeof
let s = "hello";
let n: typeof s;
// let n: stringTypeScript adds a typeof operator you can use in a type context to refer to the type of a variable or property:
ReturnType
function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;Indexed Access Types
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];We can use an indexed access type to look up a specific property on another type:
Conditional Types
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
type Example1 = Dog extends Animal ? number : string;
// type Example1 = number
type Example2 = RegExp extends Animal ? number : string;
// type Example1 = string
Mapped Types
type OnlyBoolsAndNumbers = {
[key: string]: boolean | number;
};
const conforms: OnlyBoolsAndNumbers = {
del: true,
rodney: false,
first: 100,
};When you don’t want to repeat yourself, sometimes a type needs to be based on another type.
Mapped Types
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type CreateImmutable<Type> = {
readonly [Key in keyof Type]: Type[Key]
}
Template Literal Types
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// type AllLocaleIDs = "welcome_email_id" | "email_heading_id"
// | "footer_title_id" | "footer_sendoff_id"type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]:
() => Type[Property]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
type LazyPerson = {
getName: () => string;
getAge: () => number;
getLocation: () => string;
}Class
implements
interface Pingable {
ping(): void;
}
class Sonar implements Pingable {
ping() {
console.log("ping!");
}
}
class Ball implements Pingable {
// Class 'Ball' incorrectly implements interface 'Pingable'.
// Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.
pong() {
console.log("pong!");
}
}private/protected/public
- The private modifier allows access within the same class.
- The protected modifier allows access within the same class and subclasses.
- The public modifier allows access from any location.
Parameter Properties
class Params {
constructor(
public readonly x: number,
protected y: number,
private z: number
) {
// No body necessary
}
}
const a = new Params(1, 2, 3);
console.log(a.x);Abstract
An abstract method or abstract field is one that hasn’t had an implementation provided. These members must exist inside an abstract class, which cannot be directly instantiated.
The role of abstract classes is to serve as a base class for subclasses which do implement all the abstract members. When a class doesn’t have any abstract members, it is said to be concrete.
abstract class Base {
abstract getName(): string;
printName() {
console.log("Hello, " + this.getName());
}
}
// error
const b = new Base();class Derived extends Base {
getName() {
return "world";
}
}
const d = new Derived();
d.printName();Typescript
By Aleh Lipski
Typescript
- 82