TypeScript Fundamentals
Cheatsheet Content
### Introduction to TypeScript - **What is TypeScript?** - A superset of JavaScript that adds static typing. - Compiles down to plain JavaScript. - Developed and maintained by Microsoft. - **Why use TypeScript?** - **Catch errors early:** Type checking at compile time reduces runtime errors. - **Improved tooling:** Better autocompletion, refactoring, and navigation in IDEs. - **Readability & Maintainability:** Explicit types make code easier to understand and manage, especially in large projects. - **Scalability:** Helps manage complexity in large applications. - **Modern JavaScript features:** Supports ES6+ features, even for older JS environments. - **Installation:** ```bash npm install -g typescript ``` - **Compiling TypeScript:** - Create a `hello.ts` file: ```typescript function greet(person: string) { return "Hello, " + person; } let user = "Jane User"; console.log(greet(user)); ``` - Compile using `tsc`: ```bash tsc hello.ts ``` - This generates `hello.js`: ```javascript function greet(person) { return "Hello, " + person; } var user = "Jane User"; console.log(greet(user)); ``` - Run the JavaScript: ```bash node hello.js ``` ### Basic Types - **`boolean`**: `let isDone: boolean = false;` - **`number`**: `let decimal: number = 6;` (supports decimal, hex, binary, octal) - **`string`**: `let color: string = "blue";` (supports template strings) ```typescript let fullName: string = `Bob Bobbington`; let age: number = 37; let sentence: string = `Hello, my name is ${fullName}. I'll be ${age + 1} years old next month.`; ``` - **`Array`**: - `let list: number[] = [1, 2, 3];` - `let list: Array = [1, 2, 3];` (Generic Array type) - **`Tuple`**: Express an array with a fixed number of elements whose types are known, but need not be the same. ```typescript let x: [string, number]; x = ["hello", 10]; // OK // x = [10, "hello"]; // Error ``` - Accessing elements: `console.log(x[0].substring(1));` - Out of bounds access (TypeScript 3.0+): `x[2] = "world";` (error if strict) - **`Enum`**: A way of giving more friendly names to sets of numeric values. ```typescript enum Color {Red, Green, Blue} let c: Color = Color.Green; // c = 1 enum Color2 {Red = 1, Green = 2, Blue = 4} let c2: Color2 = Color2.Green; // c2 = 2 enum Color3 {Red = 1, Green, Blue} // Green = 2, Blue = 3 ``` - Enums can also be reverse mapped from value to name: `let colorName: string = Color[2]; // Blue` - **`Any`**: A powerful way to work with existing JavaScript, allowing you to opt-out of type checking. ```typescript let notSure: any = 4; notSure = "maybe a string instead"; notSure = false; // okay, definitely a boolean let list: any[] = [1, true, "free"]; // Array of any type ``` - **`Unknown`**: A type-safe counterpart of `any`. Variables of type `unknown` must be narrowed before most operations. ```typescript let value: unknown; value = true; value = 1; value = "hello"; // let s1: string = value; // Error: Type 'unknown' is not assignable to type 'string'. // let s2: string = value as string; // OK: Type assertion // if (typeof value === "string") { // let s3: string = value; // OK: Narrowed to string // } ``` - **`Void`**: The absence of any type. Used for functions that don't return a value. ```typescript function warnUser(): void { console.log("This is my warning message"); } let unusable: void = undefined; // unusable = null; // OK with strictNullChecks: false, Error with strictNullChecks: true ``` - **`Null` and `Undefined`**: - `let u: undefined = undefined;` - `let n: null = null;` - By default, `null` and `undefined` are subtypes of all other types. With `strictNullChecks` flag, they can only be assigned to `void` or themselves. - **`Never`**: Represents the type of values that never occur. For functions that throw errors or never return. ```typescript function error(message: string): never { throw new Error(message); } function infiniteLoop(): never { while (true) {} } ``` ### Interfaces - **Definition**: Define contracts within your code and contracts with code outside of your project. - **Purpose**: Primarily used for type-checking, especially for objects. - **Example**: ```typescript interface LabelledValue { label: string; } function printLabel(labelledObj: LabelledValue) { console.log(labelledObj.label); } let myObj = {size: 10, label: "Size 10 Object"}; printLabel(myObj); // OK, myObj implicitly satisfies LabelledValue ``` - **Optional Properties**: Mark properties as optional with `?`. ```typescript interface SquareConfig { color?: string; width?: number; } function createSquare(config: SquareConfig): {color: string; area: number} { let newSquare = {color: "white", area: 100}; if (config.color) { newSquare.color = config.color; } if (config.width) { newSquare.area = config.width * config.width; } return newSquare; } let mySquare = createSquare({color: "black"}); ``` - **Readonly Properties**: Use `readonly` keyword for properties that can only be modified when an object is first created. ```typescript interface Point { readonly x: number; readonly y: number; } let p1: Point = { x: 10, y: 20 }; // p1.x = 5; // Error! ``` - `ReadonlyArray `: Similar to `readonly` for array types. ```typescript let a: number[] = [1, 2, 3, 4]; let ro: ReadonlyArray = a; // ro[0] = 12; // Error! // ro.push(5); // Error! // ro.length = 100; // Error! // a = ro; // Error! (can't assign ReadonlyArray to mutable Array) a = ro as number[]; // Type assertion to override ``` - **Function Types**: Describe the shape of functions. ```typescript interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(source: string, subString: string): boolean { let result = source.search(subString); return result > -1; } ``` - Parameter names don't need to match, only types. - **Indexable Types**: Describe types that you can "index into" like `a[1]` or `a["foo"]`. ```typescript interface StringArray { [index: number]: string; } let myArray: StringArray; myArray = ["Bob", "Fred"]; let myStr: string = myArray[0]; // "Bob" interface Dictionary { [key: string]: string; length: number; // Error: Property 'length' of type 'number' is not assignable to string index type 'string'. // If a string index signature is present, all other properties must be assignable to that string index signature. } ``` - You can have both `number` and `string` index signatures, but the number indexer must return a type that is a subtype of the string indexer's return type. - **Class Types**: Implementing an interface. ```typescript interface Clock { currentTime: Date; setTime(d: Date): void; } class DigitalClock implements Clock { constructor(h: number, m: number) { } currentTime: Date = new Date(); setTime(d: Date) { this.currentTime = d; } } ``` - Interfaces describe the public side of the class instance, not the static side. - **Extending Interfaces**: Interfaces can extend one or more other interfaces. ```typescript 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; ``` - **Hybrid Types**: Objects that act as a combination of types (e.g., a function that also has properties). ```typescript interface Counter { (start: number): string; interval: number; reset(): void; } function getCounter(): Counter { let counter = function (start: number) { }; counter.interval = 123; counter.reset = function () { }; return counter; } let c = getCounter(); c(10); c.reset(); c.interval = 5.0; ``` ### Classes - **Definition**: Blueprints for creating objects. Support inheritance, abstract classes, access modifiers. - **Basic Class**: ```typescript class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } let greeter = new Greeter("world"); ``` - **Inheritance**: Extend existing classes. ```typescript class Animal { name: string; constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Dog extends Animal { constructor(name: string) { super(name); } // Call parent constructor bark() { console.log('Woof! Woof!'); } } let dog = new Dog("Buddy"); dog.bark(); dog.move(10); dog.bark(); ``` - **Public, Private, and Protected Modifiers**: - **`public`**: Accessible from anywhere. (Default) - **`private`**: Accessible only within the class where it's declared. ```typescript class Animal { private name: string; constructor(theName: string) { this.name = theName; } } // let rhino = new Animal("Rhino"); // rhino.name; // Error: 'name' is private. ``` - **`protected`**: Accessible within the class and by subclasses. ```typescript class Person { protected name: string; constructor(name: string) { this.name = name; } } class Employee extends Person { private department: string; constructor(name: string, department: string) { super(name); this.department = department; } public getElevatorPitch() { return `Hello, my name is ${this.name} and I work in ${this.department}.`; } } let howard = new Employee("Howard", "Sales"); console.log(howard.getElevatorPitch()); // console.log(howard.name); // Error: 'name' is protected. ``` - **`readonly` Modifier**: Properties can be marked `readonly` to prevent assignment after the constructor. ```typescript class Octopus { readonly name: string; readonly numberOfLegs: number = 8; constructor(theName: string) { this.name = theName; } } let dad = new Octopus("Man with the 8 strong legs"); // dad.name = "Man with the 3-piece suit"; // Error! name is readonly. ``` - **Parameter Properties**: A shorthand for declaring and initializing properties in the constructor. ```typescript class Octopus2 { readonly numberOfLegs: number = 8; constructor(readonly name: string) { // 'name' becomes a readonly public property } } let dad2 = new Octopus2("Fred"); console.log(dad2.name); ``` - **Accessors (Getters/Setters)**: Control how class members are accessed. ```typescript class Employee { private _fullName: string = ""; get fullName(): string { return this._fullName; } set fullName(newName: string) { if (newName && newName.length > 0) { this._fullName = newName; } else { console.error("Name cannot be empty."); } } } let employee = new Employee(); employee.fullName = "Bob Smith"; console.log(employee.fullName); // Bob Smith employee.fullName = ""; // "Name cannot be empty." ``` - **Static Properties**: Members that belong to the class itself, rather than to instances of the class. ```typescript class Grid { static origin = {x: 0, y: 0}; calculateDistanceFromOrigin(point: {x: number; y: number;}) { let xDist = (point.x - Grid.origin.x); let yDist = (point.y - Grid.origin.y); return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale; } constructor (public scale: number) {} } let grid1 = new Grid(1.0); // 1x scale let grid2 = new Grid(5.0); // 5x scale console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10})); console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10})); ``` - **Abstract Classes**: Base classes from which other classes may be derived. Cannot be instantiated directly. ```typescript abstract class Department { constructor(public name: string) { } printName(): void { console.log("Department name: " + this.name); } abstract printMeeting(): void; // Must be implemented in derived classes } class AccountingDepartment extends Department { constructor() { super("Accounting and Auditing"); // Call base class constructor } printMeeting(): void { console.log("The Accounting Department meets each Monday at 10am."); } generateReports(): void { console.log("Generating accounting reports..."); } } // let department = new Department(); // Error: Cannot create an instance of an abstract class. let accounting = new AccountingDepartment(); accounting.printName(); accounting.printMeeting(); accounting.generateReports(); ``` ### Functions - **Definition**: Blocks of code designed to perform a particular task. TypeScript adds type annotations to parameters and return values. - **Function Types**: ```typescript function add(x: number, y: number): number { return x + y; } let myAdd = function(x: number, y: number): number { return x + y; }; // Writing the full function type: let myAdd2: (x: number, y: number) => number = function(x: number, y: number): number { return x + y; }; ``` - Parameter names in the function type don't need to match. - **Optional Parameters**: Add `?` to make a parameter optional. Must come after required parameters. ```typescript function buildName(firstName: string, lastName?: string): string { if (lastName) return firstName + " " + lastName; else return firstName; } let result1 = buildName("Bob"); // OK let result2 = buildName("Bob", "Adams"); // OK // let result3 = buildName("Bob", "Adams", "Sr."); // Error, too many parameters ``` - **Default Parameters**: Provide a default value if a parameter is not provided. Can come before required parameters. ```typescript function buildName2(firstName: string, lastName = "Smith"): string { return firstName + " " + lastName; } let resultA = buildName2("Bob"); // Bob Smith let resultB = buildName2("Bob", undefined); // Bob Smith let resultC = buildName2("Bob", "Adams"); // Bob Adams ``` - **Rest Parameters**: Handle an indefinite number of arguments as an array. ```typescript function buildName3(firstName: string, ...restOfName: string[]): string { return firstName + " " + restOfName.join(" "); } let employeeName = buildName3("Joseph", "Samuel", "Lucas", "MacKinzie"); console.log(employeeName); // Joseph Samuel Lucas MacKinzie ``` - **`this` and Arrow Functions**: TypeScript can infer `this` type, but sometimes explicit annotation is needed, especially for callbacks. ```typescript interface Card { suit: string; card: number; } interface Deck { suits: string[]; cards: number[]; createCardPicker(this: Deck): () => Card; // 'this' parameter } let deck: Deck = { suits: ["hearts", "spades", "clubs", "diamonds"], cards: Array(52), createCardPicker: function(this: Deck) { return () => { // Arrow function correctly captures 'this' from 'createCardPicker' let pickedCard = Math.floor(Math.random() * 52); let pickedSuit = Math.floor(pickedCard / 13); return {suit: this.suits[pickedSuit], card: pickedCard % 13}; } } } let cardPicker = deck.createCardPicker(); let pickedCard = cardPicker(); console.log("card: " + pickedCard.card + " of " + pickedCard.suit); ``` - **Overloads**: Provide multiple function signatures for a single function implementation. - The implementation signature (`function pickCard(...)`) must be compatible with all overload signatures. ```typescript function pickCard(x: {suit: string; card: number;}[]): number; // Overload 1: Takes array, returns index function pickCard(x: number): {suit: string; card: number;}; // Overload 2: Takes number, returns card function pickCard(x: any): any { // Check to see if we're working with an object/array // if so, they gave us a deck of cards if (typeof x == "object") { let pickedCard = Math.floor(Math.random() * x.length); return pickedCard; } // Otherwise, just pick the card else if (typeof x == "number") { let suits = ["hearts", "spades", "clubs", "diamonds"]; 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 = pickCard(myDeck); console.log("card: " + myDeck[pickedCard1].card + " of " + myDeck[pickedCard1].suit); // card: 10 of spades let pickedCard2 = pickCard(15); console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit); // card: 2 of spades ``` ### Generics - **Definition**: Allow you to write code that works with different data types while still providing type safety. - **"Hello World" of Generics**: Identity function that returns whatever is passed in. ```typescript function identity (arg: T): T { return arg; } let output = identity ("myString"); // Type argument explicitly assigned let output2 = identity("myString"); // Type argument inferred (common) ``` - **Working with Generic Type Variables**: ```typescript function loggingIdentity (arg: T[]): T[] { console.log(arg.length); // OK, T[] has a .length property return arg; } // Or: function loggingIdentity2 (arg: Array ): Array { console.log(arg.length); return arg; } ``` - **Generic Types**: Define generic interfaces and type aliases. ```typescript interface GenericIdentityFn { (arg: T): T; } function identity2 (arg: T): T { return arg; } let myIdentity: GenericIdentityFn = identity2; console.log(myIdentity(10)); // 10 ``` - **Generic Classes**: ```typescript class GenericNumber { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber (); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; }; console.log(myGenericNumber.add(5, 10)); // 15 let stringNumeric = new GenericNumber (); stringNumeric.zeroValue = ""; stringNumeric.add = function(x, y) { return x + y; }; console.log(stringNumeric.add(stringNumeric.zeroValue, "test")); // test ``` - **Generic Constraints**: Limit the types that can be used with generics using `extends`. ```typescript interface Lengthwise { length: number; } function loggingIdentity3 (arg: T): T { console.log(arg.length); // Now we know it has a .length property return arg; } // loggingIdentity3(3); // Error, number doesn't have a .length property loggingIdentity3({length: 10, value: 3}); // OK ``` - **Using Type Parameters in Generic Constraints**: ```typescript function getProperty (obj: T, key: K) { return obj[key]; } let x = { a: 1, b: 2, c: 3, d: 4 }; getProperty(x, "a"); // OK // getProperty(x, "m"); // Error: Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'. ``` - **Generic with `new`**: Factory functions using constructor types. ```typescript class BeeKeeper { hasMask: boolean = true; } class ZooKeeper { nametag: string = "Mikle"; } class Animal2 { numLegs: number = 4; } class Lion extends Animal2 { keeper: ZooKeeper = new ZooKeeper(); } class Gopher extends Animal2 { keeper: BeeKeeper = new BeeKeeper(); } function createInstance (c: new () => T): T { return new c(); } console.log(createInstance(Lion).keeper.nametag); // Mikle console.log(createInstance(Gopher).keeper.hasMask); // true ``` ### Enums (Advanced) - **Computed and Constant Members**: - **Constant enum members**: Values known at compile time. - **Computed enum members**: Values calculated at runtime. ```typescript enum FileAccess { // constant members None, Read = 1 ### Type Inference - **Definition**: TypeScript's ability to automatically determine types. - **Basic Inference**: ```typescript let x = 3; // x is inferred as number // x = "hello"; // Error: Type '"hello"' is not assignable to type 'number'. let y = [1, 2, null]; // y is inferred as (number | null)[] ``` - **Best Common Type**: When inferring the type of an array, TypeScript looks at all elements and determines the type that is compatible with all of them. ```typescript class Animal3 { } class Rhino extends Animal3 { } class Elephant extends Animal3 { } class Snake extends Animal3 { } let zoo = [new Rhino(), new Elephant(), new Snake()]; // zoo is inferred as Animal3[] ``` - If no best common type is found, it defaults to a union array type (e.g., `(Rhino | Elephant | Snake)[]`). - **Contextual Typing**: When a type is determined by the location of the variable. ```typescript window.onmousedown = function(mouseEvent) { console.log(mouseEvent.button); // OK, mouseEvent is inferred as MouseEvent // console.log(mouseEvent.kangaroo); // Error: Property 'kangaroo' does not exist on type 'MouseEvent'. }; ``` - Contextual typing also applies to: - Function arguments - Return types - Literal object types - Array literals - Casts - Default parameters ### Type Compatibility - **Definition**: How TypeScript determines if one type is assignable to another. - **Structural Typing**: TypeScript uses a structural type system. If two types have the same members, they are considered compatible. - "If it walks like a duck and quacks like a duck, then it must be a duck." ```typescript interface Named { name: string; } class Person { name: string; } let p: Named; // OK, because of structural typing p = new Person(); // Person has a name: string property, so it's compatible with Named. ``` - **Comparing two objects**: ```typescript interface Named { name: string; } let x: Named; // y's inferred type is { name: string; location: string; } let y = { name: "Alice", location: "Seattle" }; x = y; // OK, y has a 'name' property that matches 'Named'. ``` - **Comparing Functions**: - **Parameter types**: Source function's parameters must be assignable to target function's parameters, or fewer parameters. ```typescript let x = (a: number) => 0; let y = (b: number, s: string) => 0; y = x; // OK, x's parameters are a subset of y's // x = y; // Error, y has a required second parameter that x doesn't have. ``` - **Return types**: Source function's return type must be assignable to target function's return type. ```typescript let x = () => ({name: "Alice"}); let y = () => ({name: "Alice", location: "Seattle"}); x = y; // OK, y's return type is compatible with x's // y = x; // Error, x's return type is missing 'location'. ``` - **Enums**: Not compatible with numbers, and members from different enum types are not compatible. ```typescript enum Status { Ready, Waiting }; enum Color { Red, Blue, Green }; let s = Status.Ready; // s = Color.Green; // Error: Type 'Color.Green' is not assignable to type 'Status'. ``` - **Classes**: Only instance members are compared. Static members and constructors don't affect compatibility. - Private and protected members affect compatibility: A class with private/protected members is only compatible with itself or its subclasses. ```typescript class Animal4 { private name: string; constructor(theName: string) { this.name = theName; } } class Rhino2 extends Animal4 { constructor() { super("Rhino"); } } class Employee2 { private name: string; constructor(theName: string) { this.name = theName; } } let animal = new Animal4("Goat"); let rhino = new Rhino2(); let employee = new Employee2("Bob"); animal = rhino; // OK // animal = employee; // Error: Types have separate declarations of a private property 'name'. ``` - **Generics**: Type parameters only affect compatibility if they are used in the type definition. ```typescript interface Empty { } let x: Empty ; let y: Empty ; x = y; // OK, Empty is structurally compatible regardless of T interface NotEmpty { data: T; } let x2: NotEmpty ; let y2: NotEmpty ; // x2 = y2; // Error: Type 'string' is not assignable to type 'number'. ``` ### Union and Intersection Types - **Union Types (`|`)**: A value can be one of several types. ```typescript function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); } console.log(padLeft("Hello", 4)); // " Hello" console.log(padLeft("Hello", "---")); // "---Hello" // console.log(padLeft("Hello", true)); // Error ``` - **Type Narrowing**: Using type guards (`typeof`, `instanceof`, `in` operator, custom type guards) to narrow down a union type. - **Intersection Types (`&`)**: Combines multiple types into one. The new type has all members of the combined types. ```typescript interface Taggable { tag: string; } interface Identifiable { id: number; } type TaggedAndIdentifiable = Taggable & Identifiable; function identifyAndTag(obj: TaggedAndIdentifiable) { console.log(`ID: ${obj.id}, Tag: ${obj.tag}`); } identifyAndTag({ id: 123, tag: "important" }); interface Person { name: string; age: number; } interface Developer { skills: string[]; experience: number; } type DevPerson = Person & Developer; let dev: DevPerson = { name: "Alice", age: 30, skills: ["TypeScript", "React"], experience: 5 }; ``` ### Type Guards and Assertion - **Type Guards**: Special functions or constructs that narrow down the type of a variable within a certain scope. - **`typeof` type guard**: For primitive types (`string`, `number`, `boolean`, `symbol`, `bigint`, `undefined`, `object`, `function`). ```typescript function printId(id: number | string) { if (typeof id === "string") { // In this branch, id is of type 'string' console.log(id.toUpperCase()); } else { // In this branch, id is of type 'number' console.log(id); } } ``` - **`instanceof` type guard**: For classes. ```typescript class Fish { swim() { console.log('swimming'); } } class Bird { fly() { console.log('flying'); } } function isFish(pet: Fish | Bird): pet is Fish { // Type predicate return (pet as Fish).swim !== undefined; } function getSmallPet(): Fish | Bird { return Math.random() > 0.5 ? new Fish() : new Bird(); } let pet = getSmallPet(); if (pet instanceof Fish) { pet.swim(); } else { pet.fly(); } ``` - **`in` operator type guard**: Check for the existence of a property. ```typescript interface A { a: number; } interface B { b: string; } function checkType(obj: A | B) { if ("a" in obj) { console.log(obj.a); // obj is A } else { console.log(obj.b); // obj is B } } ``` - **Custom Type Guards (Type Predicates)**: Functions that return `parameterName is Type`. ```typescript interface Car { drive(): void; } interface Bike { pedal(): void; } function isCar(vehicle: Car | Bike): vehicle is Car { return (vehicle as Car).drive !== undefined; } function startVehicle(vehicle: Car | Bike) { if (isCar(vehicle)) { vehicle.drive(); } else { vehicle.pedal(); } } ``` - **Type Assertions (`as` keyword / ` `)**: Tell the compiler "trust me, I know what I'm doing". No runtime impact. - Use when you have more specific information about a type than TypeScript can infer. ```typescript let someValue: any = "this is a string"; let strLength: number = ( someValue).length; // Angle-bracket syntax let strLength2: number = (someValue as string).length; // 'as' syntax (preferred in TSX) ``` - **Non-null Assertion Operator (`!`)**: Asserts that an expression is not `null` or `undefined`. ```typescript function liveDangerously(x?: number | null) { // No error console.log(x!.toFixed()); } liveDangerously(10); // liveDangerously(null); // Runtime error, but no compile-time error ``` ### Literal Types - **Definition**: Allow you to define types that are exact values (strings, numbers, booleans). - **String Literal Types**: ```typescript type Direction = "up" | "down" | "left" | "right"; function move(dir: Direction) { console.log(`Moving ${dir}`); } move("up"); // move("north"); // Error: Argument of type '"north"' is not assignable to type 'Direction'. ``` - **Numeric Literal Types**: ```typescript type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6; function rollDice(): DiceRoll { return Math.floor(Math.random() * 6) + 1 as DiceRoll; } let result: DiceRoll = rollDice(); ``` - **Boolean Literal Types**: ```typescript type TrueOrFalse = true | false; // Equivalent to boolean ``` - **Combining with Union Types**: Very powerful for creating state machines or API definitions. ```typescript interface SuccessResponse { status: "success"; data: any; } interface ErrorResponse { status: "error"; message: string; code: number; } type APIResponse = SuccessResponse | ErrorResponse; function handleResponse(response: APIResponse) { if (response.status === "success") { console.log("Data:", response.data); } else { console.error("Error:", response.message, response.code); } } handleResponse({ status: "success", data: { user: "Alice" } }); handleResponse({ status: "error", message: "Not found", code: 404 }); ``` ### Null, Undefined, and `strictNullChecks` - **`null` and `undefined`**: - `null`: Intentional absence of any object value. - `undefined`: A variable that has been declared but not yet assigned a value. - **`strictNullChecks` Compiler Option**: - When `false` (default in older versions): `null` and `undefined` are assignable to any type. This is how JavaScript works. ```typescript let s: string = "foo"; s = null; // OK s = undefined; // OK ``` - When `true` (recommended for new projects): `null` and `undefined` are only assignable to `void`, `any`, or their respective types (`null`, `undefined`). ```typescript // With strictNullChecks: true let s: string = "foo"; // s = null; // Error: Type 'null' is not assignable to type 'string'. // s = undefined; // Error: Type 'undefined' is not assignable to type 'string'. let sOrNull: string | null = "foo"; sOrNull = null; // OK ``` - **Working with `strictNullChecks`**: - **Union Types**: Use `| null` or `| undefined` explicitly. - **Type Guards**: Use `if (value != null)` or `if (value !== undefined)` to narrow types. ```typescript function foo(x: number | null) { if (x === null) { // x is null } else { // x is number } } ``` - **Non-null Assertion Operator (`!`)**: Tells the compiler that you're certain a value is not `null` or `undefined`. Use sparingly. ```typescript function broken(name: string | null): string { function postFix(epithet: string) { return name!.charAt(0) + '. the ' + epithet; // name is asserted non-null } name = name || "Bob"; return postFix("great"); } ``` - **Optional Chaining (`?.`)**: Safely access properties or call methods on potentially `null` or `undefined` objects. (ES2020 feature, supported by TS) ```typescript interface Customer { name: string; address?: { street: string; city: string; }; } let customer: Customer = { name: "Alice" }; let city = customer.address?.city; // city is undefined let street = customer.address?.street; // street is undefined let length = customer.address?.street?.length; // length is undefined ``` - **Nullish Coalescing Operator (`??`)**: Provide a default value for `null` or `undefined` expressions. (ES2020 feature, supported by TS) ```typescript function greet(name: string | null | undefined) { const displayName = name ?? "Guest"; console.log(`Hello, ${displayName}`); } greet("Alice"); // Hello, Alice greet(null); // Hello, Guest greet(undefined); // Hello, Guest greet(""); // Hello, (empty string is not nullish) ``` ### Type Aliases - **Definition**: Create a new name for a type. - **Syntax**: `type NewTypeName = ExistingType;` - **Example**: ```typescript type Point = { x: number; y: number; }; function printCoord(pt: Point) { console.log("The coordinate's x value is " + pt.x); console.log("The coordinate's y value is " + pt.y); } printCoord({ x: 100, y: 100 }); ``` - **Differences from Interfaces**: - **Declaration merging**: Interfaces can be merged (`interface Foo { a: string } interface Foo { b: number }` results in `Foo` having both `a` and `b`). Type aliases cannot. - **Primitives, Unions, Tuples**: Type aliases can name primitive types, union types, tuple types, and other types that interfaces cannot. ```typescript type ID = number | string; type UserTuple = [number, string, boolean]; type GreetingFunction = (name: string) => string; ``` - **`extends` / `implements`**: Interfaces can extend and implement other interfaces/classes. Type aliases cannot directly. - **Recursion**: Type aliases can be recursive. ```typescript type Node = { value: T; next?: Node ; }; let node1: Node = { value: 1 }; let node2: Node = { value: 2, next: node1 }; ``` - **When to use which**: - **Interface**: Prefer for defining the shape of objects (especially for public APIs, libraries). Can be extended/implemented. - **Type Alias**: Prefer for naming primitive types, union types, tuple types, intersection types, or when you need recursive types. ### Declaration Merging - **Definition**: When the TypeScript compiler merges two or more declarations with the same name into a single definition. - **Interfaces**: Most common use case. ```typescript interface Box { height: number; width: number; } interface Box { scale: number; } let box: Box = { height: 5, width: 6, scale: 10 }; // Resulting Box interface: // interface Box { // height: number; // width: number; // scale: number; // } ``` - Non-function members must be unique. - Function members with the same name are treated as overloads. ```typescript interface Document { createElement(tagName: "div"): HTMLDivElement; createElement(tagName: "span"): HTMLSpanElement; } interface Document { createElement(tagName: string): HTMLElement; // This will be the last overload, but earlier specific ones are preferred. } // Result: The more specific signatures are ordered first. // createElement(tagName: "div"): HTMLDivElement; // createElement(tagName: "span"): HTMLSpanElement; // createElement(tagName: string): HTMLElement; ``` - **Namespaces with Classes, Functions, Enums**: - A namespace can wrap a class, function, or enum to add static members. ```typescript class Album { label: Album.AlbumLabel; } namespace Album { export class AlbumLabel { } } // The class Album now has a static member AlbumLabel. ``` ```typescript function buildLabel(name: string): string { return buildLabel.prefix + name + buildLabel.suffix; } namespace buildLabel { export let prefix = ""; export let suffix = ""; } buildLabel.prefix = "Hello, "; buildLabel.suffix = "!"; console.log(buildLabel("Sam")); // Hello, Sam! ``` - **Limitations**: - Type aliases cannot be merged. - Classes cannot be merged with other classes or interfaces (except for static side merging with namespaces). ### Modules - **Definition**: Way to organize code into separate files. Each file is a module in TypeScript. - **ES Modules (Preferred)**: Uses `import` and `export` keywords. - **Exporting**: ```typescript // MyModule.ts export interface MyInterface { name: string; } export const PI = 3.14; export function myFunction() { /* ... */ } export class MyClass { /* ... */ } export default class MyDefaultClass { /* ... */ } // Default export ``` - **Importing**: ```typescript // MyConsumer.ts import { MyInterface, PI, myFunction, MyClass } from "./MyModule"; import MyDefaultClass from "./MyModule"; // Import default export import * as MyModule from "./MyModule"; // Import all as namespace let obj: MyInterface = { name: "test" }; console.log(MyModule.PI); ``` - **Module Resolution**: How TypeScript finds a module from an import statement. - **Relative paths**: `./`, `../`. - **Non-relative paths**: Resolved via `baseUrl` and `paths` in `tsconfig.json`, or node_modules lookup. - **CommonJS / AMD / SystemJS / UMD**: Older module systems, primarily used for compatibility with older environments. `target` and `module` options in `tsconfig.json` control output. - **Ambient Modules (Declaration Files)**: - Used to describe JavaScript libraries that don't have TypeScript definitions. - Declared using `declare module "module-name" { ... }`. ```typescript // jquery.d.ts (example ambient module declaration) declare module "jquery" { interface JQuery { (selector: string): JQuery; ajax(settings: any): JQueryPromise ; } function $(selector: string): JQuery; export = $; // CommonJS style export } ``` - Consumed by `import $ = require("jquery");` or `import * as $ from "jquery";`. - **`declare global`**: Extend global types. ```typescript // global.d.ts declare global { interface String { reverse(): string; } } // Now all strings have a .reverse() method (though you'd still need to implement it in JS) ``` ### Namespaces - **Definition**: A way to organize code, especially in global scope, to avoid naming collisions. (Older alternative to ES Modules). - **Declaration**: Use the `namespace` keyword. ```typescript namespace Validation { export interface StringValidator { isAcceptable(s: string): boolean; } const lettersRegexp = /^[A-Za-z]+$/; const numberRegexp = /^[0-9]+$/; export class LettersOnlyValidator implements StringValidator { isAcceptable(s: string) { return lettersRegexp.test(s); } } export class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } } // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validators: {[s: string]: Validation.StringValidator;} = {}; validators["ZIP code"] = new Validation.ZipCodeValidator(); validators["Letters only"] = new Validation.LettersOnlyValidator(); // Show results for (let s of strings) { for (let name in validators) { let isMatch = validators[name].isAcceptable(s); console.log(`'${s}' ${isMatch ? "matches" : "does not match"} '${name}'.`); } } ``` - **Aliasing**: Use `import alias = SomeNamespace.SomeType;` for convenience. ```typescript import ZipCodeValidator = Validation.ZipCodeValidator; let myValidator = new ZipCodeValidator(); ``` - **Multi-file Namespaces**: Can span multiple files, contributing to the same namespace. Requires ` ` directives (now largely replaced by ES modules). - **When to use**: - Primarily for organizing code in global scope (e.g., legacy JS projects). - For bundling multiple type definitions under a single global name. - For declaration merging with classes, functions, enums. - For new projects, ES Modules are generally preferred. ### Decorators - **Definition**: A special kind of declaration that can be attached to classes, methods, accessors, properties, or parameters. - **Syntax**: `@expression` (where `expression` evaluates to a function). - **Enabling Decorators**: Set `experimentalDecorators: true` in `tsconfig.json`. - **Class Decorators**: Applied to the constructor of the class. Can modify, replace, or observe class definition. ```typescript function sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } @sealed class Greeter2 { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } ``` - **Decorator Factories**: Functions that return a decorator. ```typescript function configurable(value: boolean) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.configurable = value; }; } class Greeter3 { @configurable(false) greet() { return "Hello, TypeScript!"; } } ``` - **Method Decorators**: Applied to a method declaration. Can modify, replace, or observe a method definition. - Parameters: `target` (prototype of the class), `propertyKey` (method name), `descriptor` (Property Descriptor). ```typescript function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) { let originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${propertyKey} with arguments: ${JSON.stringify(args)}`); let result = originalMethod.apply(this, args); console.log(`Method ${propertyKey} returned: ${JSON.stringify(result)}`); return result; }; return descriptor; } class Calculator { @log add(a: number, b: number): number { return a + b; } } const calc = new Calculator(); calc.add(2, 3); // Output: // Calling add with arguments: [2,3] // Method add returned: 5 ``` - **Accessor Decorators**: Applied to a property accessor (getter/setter). - Parameters are same as method decorators. - **Property Decorators**: Applied to a property declaration. - Parameters: `target` (prototype), `propertyKey` (property name). - Return value is ignored. ```typescript function format(formatString: string) { return function (target: any, propertyKey: string) { let value: string; const getter = function() { return formatString.replace("{value}", value); }; const setter = function(newVal: string) { value = newVal; }; Object.defineProperty(target, propertyKey, { get: getter, set: setter, enumerable: true, configurable: true }); }; } class Message { @format("Hello, {value}!") text: string = "World"; } const msg = new Message(); console.log(msg.text); // Hello, World! msg.text = "TypeScript"; console.log(msg.text); // Hello, TypeScript! ``` - **Parameter Decorators**: Applied to a parameter in a method or constructor. - Parameters: `target` (prototype), `propertyKey` (method name), `parameterIndex`. - Return value is ignored. ```typescript function required(target: Object, propertyKey: string | symbol, parameterIndex: number) { console.log(`Marking parameter ${parameterIndex} of ${String(propertyKey)} as required.`); // In a real scenario, you might store metadata for validation. } class UserService { saveUser( @required name: string, email: string) { // ... } } ``` - **Order of Evaluation**: 1. Parameter Decorators 2. Method, Accessor, or Property Decorators 3. Class Decorators ### Mixins - **Definition**: A way to compose classes by combining simpler partial classes. - **Problem**: JavaScript's single inheritance model can be limiting. Mixins provide a way to reuse code across different class hierarchies. - **Implementation Pattern**: ```typescript // Disposable Mixin class Disposable { isDisposed: boolean = false; dispose() { this.isDisposed = true; } } // Activatable Mixin class Activatable { isActive: boolean = false; activate() { this.isActive = true; } deactivate() { this.isActive = false; } } // Helper function to apply mixins function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null)); }); }); } class SmartObject implements Disposable, Activatable { constructor() { setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500); } // Disposable isDisposed: boolean = false; dispose: () => void; // Activatable isActive: boolean = false; activate: () => void; deactivate: () => void; } applyMixins(SmartObject, [Disposable, Activatable]); let smartObj = new SmartObject(); setTimeout(() => smartObj.activate(), 1000); setTimeout(() => smartObj.dispose(), 2000); // Output will show state changes over time. ``` - **Key aspects**: - Declare interfaces for the mixin capabilities. - Implement a helper function (`applyMixins`) to copy properties from mixin prototypes to the target class's prototype. - The target class `implements` the mixin interfaces and declares the properties/methods (even if they're empty, for type safety). ### Utility Types - **Definition**: Global types provided by TypeScript to facilitate common type transformations. - **`Partial `**: Makes all properties in `T` optional. ```typescript interface Todo { title: string; description: string; completed: boolean; } function updateTodo(todo: Todo, fieldsToUpdate: Partial ) { return { ...todo, ...fieldsToUpdate }; } const todo1: Todo = { title: "Organize desk", description: "clear clutter", completed: false, }; const todo2 = updateTodo(todo1, { description: "throw out trash", }); console.log(todo2); // { title: "Organize desk", description: "throw out trash", completed: false } ``` - **`Required `**: Makes all properties in `T` required. ```typescript interface Props { a?: number; b?: string; } const obj: Props = { a: 5 }; // OK // const obj2: Required = { a: 5 }; // Error: Property 'b' is missing ``` - **`Readonly `**: Makes all properties in `T` `readonly`. ```typescript interface User { id: number; name: string; } let user: Readonly = { id: 1, name: "Alice" }; // user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property. ``` - **`Record `**: Constructs an object type with properties `K` of type `T`. ```typescript type Page = "home" | "about" | "contact"; interface PageInfo { title: string; } const pages: Record = { home: { title: "Home Page" }, about: { title: "About Us" }, contact: { title: "Contact Us" }, }; // const pages2: Record = { home: { title: "Home" } }; // Error: Missing 'about', 'contact' ``` - **`Pick `**: Constructs a type by picking the set of properties `K` from `T`. ```typescript interface Product { id: number; name: string; price: number; description: string; } type ProductSummary = Pick ; const summary: ProductSummary = { name: "Laptop", price: 1200, }; ``` - **`Omit `**: Constructs a type by omitting the set of properties `K` from `T`. ```typescript type ProductWithoutDescription = Omit ; const product: ProductWithoutDescription = { id: 1, name: "Keyboard", price: 75, }; ``` - **`Exclude `**: Excludes from `T` all union members that are assignable to `U`. ```typescript type T0 = Exclude ; // "b" | "c" type T1 = Exclude ; // "b" | "c" type T2 = Exclude void), Function>; // string | number ``` - **`Extract `**: Extracts from `T` all union members that are assignable to `U`. ```typescript type T0 = Extract ; // "a" type T1 = Extract void), Function>; // () => void ``` - **`NonNullable `**: Excludes `null` and `undefined` from `T`. ```typescript type T0 = NonNullable ; // string | number type T1 = NonNullable ; // string[] ``` - **`Parameters `**: Extracts the parameter types of a function type `T` as a tuple. ```typescript function f1(arg: { a: number; b: string }) { /* ... */ } type T0 = Parameters ; // [{ a: number; b: string; }] type T1 = Parameters string>; // [string] ``` - **`ReturnType `**: Extracts the return type of a function type `T`. ```typescript type T0 = ReturnType string>; // string type T1 = ReturnType void>; // void type T2 = ReturnType () => T>; // {} ``` - **`InstanceType `**: Extracts the instance type of a constructor function type `T`. ```typescript class C { x = 0; y = 0; } type T0 = InstanceType ; // C type T1 = InstanceType ; // any ``` - **`Awaited `**: Represents the type of a value that has been awaited. ```typescript type A = Awaited >; // string type B = Awaited >>; // number type C = Awaited >; // number | boolean ``` ### Conditional Types - **Definition**: Allow types to be chosen based on a condition, similar to conditional expressions (`condition ? trueType : falseType`). - **Syntax**: `T extends U ? X : Y` - If type `T` is assignable to type `U`, then the type is `X`. - Otherwise, the type is `Y`. - **Basic Example**: ```typescript type IsString = T extends string ? "Yes" : "No"; type A = IsString ; // "Yes" type B = IsString ; // "No" type C = IsString ; // "Yes" | "No" (distributive over union) ``` - **`infer` keyword**: Used to "infer" types within the `extends` clause. - Common for extracting parts of types (e.g., return types of functions, element types of arrays). ```typescript // Extract the return type of a function type GetReturnType = T extends (...args: any[]) => infer R ? R : any; type Func = () => string; type FuncReturnType = GetReturnType ; // string type AsyncFunc = () => Promise ; type AsyncFuncReturnType = GetReturnType ; // Promise // Extract the element type of an array type GetArrayElementType = T extends (infer E)[] ? E : T; type NumArray = GetArrayElementType ; // number type StringArray = GetArrayElementType ; // string type NotArray = GetArrayElementType ; // number (falls to false branch) ``` - **Distributive Conditional Types**: When `T` is a union type, a conditional type applies to each member of the union. ```typescript type ToArray = T extends any ? T[] : never; type StrAndNumArray = ToArray ; // string[] | number[] // Evaluates as: (string extends any ? string[] : never) | (number extends any ? number[] : never) // Results in: string[] | number[] ``` - To prevent distribution, wrap `T` in square brackets: `[T] extends [U] ? X : Y`. ```typescript type ToArrayNonDist = [T] extends [any] ? T[] : never; type StrAndNumArrayNonDist = ToArrayNonDist ; // (string | number)[] ``` - **Real-world use cases**: - Implementing `ReturnType`, `Parameters`, `Awaited`, `NonNullable` (as seen in Utility Types). - Advanced type transformations for libraries. ### Mapped Types - **Definition**: Create new types by transforming the properties of an existing type. - **Syntax**: Uses a `[K in KeyType]` syntax, similar to a `for...in` loop. - **Basic Example**: Making all properties `readonly`. ```typescript type Readonly = { readonly [P in keyof T]: T[P]; }; interface Person { name: string; age: number; } type ReadonlyPerson = Readonly ; // { readonly name: string; readonly age: number; } ``` - **Modifiers**: - `readonly` prefix: `+readonly` (add), `-readonly` (remove). - `?` prefix: `+?` (add optional), `-?` (remove optional). - (If no `+` or `-` is specified, `+` is assumed). ```typescript type Partial = { [P in keyof T]?: T[P]; // Adds '?' }; type Mutable = { -readonly [P in keyof T]: T[P]; // Removes 'readonly' }; type Concrete = { [P in keyof T]-?: T[P]; // Removes '?' }; ``` - **Key Remapping (`as` clause)**: Change the names of properties. ```typescript type Getters = { [K in keyof T as `get${Capitalize }`]: T[K]; }; interface User { name: string; age: number; } type UserGetters = Getters ; // { getName: string; getAge: number; } ``` - `Capitalize `: A built-in utility type that capitalizes the first letter of a string literal type. - Other built-in string manipulation types: `Uppercase`, `Lowercase`, `Uncapitalize`. - **Filtering properties**: Use `never` in the `as` clause to remove properties. ```typescript type RemoveKindField = { [K in keyof T as Exclude ]: T[K] }; interface Circle { kind: "circle"; radius: number; } type JustCircleProps = RemoveKindField ; // { radius: number; } ``` - **Combining with Conditional Types**: ```typescript type PickByType = { [P in keyof T as T[P] extends U ? P : never]: T[P] }; interface Person { name: string; age: number; isStudent: boolean; } type StringProps = PickByType ; // { name: string; } type BooleanProps = PickByType ; // { isStudent: boolean; } ``` ### `tsconfig.json` - **Definition**: The presence of a `tsconfig.json` file in a directory indicates that the directory is the root of a TypeScript project. - **Purpose**: Specifies root files and compiler options required to compile the project. - **Key Options**: - **`compilerOptions`**: - `target`: ECMAScript target version (e.g., `es5`, `es2016`, `esnext`). - `module`: Module system to use (e.g., `commonjs`, `es2015`, `esnext`, `umd`). - `lib`: Specify library files to be included in the compilation (e.g., `["dom", "es2017"]`). - `strict`: Enables all strict type-checking options (`noImplicitAny`, `strictNullChecks`, etc.). **Highly recommended.** - `strictNullChecks`: When `true`, `null` and `undefined` are not assignable to non-nullable types. - `noImplicitAny`: Raise error on expressions and declarations with an implied `any` type. - `esModuleInterop`: Enables `allowSyntheticDefaultImports` for CommonJS/AMD/UMD modules. - `allowSyntheticDefaultImports`: Allow default imports from modules with no default export. - `forceConsistentCasingInFileNames`: Disallow inconsistently-cased references to the same file. - `jsx`: Support JSX in `.tsx` files (e.g., `react`, `preserve`). - `outDir`: Redirect output structure to the directory. - `rootDir`: Specify the root directory of input files. - `baseUrl`: Base directory to resolve non-relative module names. - `paths`: Configure module name to location mappings. - `declaration`: Generates corresponding `.d.ts` file. - `sourceMap`: Generates corresponding `.map` file. - `removeComments`: Remove all comments from output. - `noEmitOnError`: Do not emit outputs if any errors were reported. - `downlevelIteration`: Emit more accurate JavaScript for iterating on arrays and iterables when targeting ES5 or ES3. - `experimentalDecorators`: Enables experimental support for decorators. - `emitDecoratorMetadata`: Emit design-type metadata for decorated declarations. - **`include`**: An array of glob patterns that specify which files to include in the program. ```json "include": [ "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx" ] ``` - **`exclude`**: An array of glob patterns that specify which files to exclude from the program. ```json "exclude": [ "node_modules", "**/*.spec.ts" ] ``` - **`files`**: An array of relative or absolute file paths to include in the program. (Rarely used, `include` is preferred). - **`extends`**: A string that specifies a path to another configuration file to inherit from. ```json // tsconfig.base.json { "compilerOptions": { "target": "es2016", "strict": true } } // tsconfig.json { "extends": "./tsconfig.base.json", "compilerOptions": { "outDir": "./dist" }, "include": ["src"] } ``` - **Example `tsconfig.json`**: ```json { "compilerOptions": { "target": "es2018", /* Specify ECMAScript target version. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'esnext'. */ "lib": ["es2018", "dom"], /* Specify library files to be included in the compilation. */ "outDir": "./dist", /* Redirect output structure to the directory. */ "rootDir": "./src", /* Specify the root directory of input files. */ "strict": true, /* Enable all strict type-checking options. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "skipLibCheck": true, /* Skip type checking of all declaration files (*.d.ts). */ "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ "declaration": true, /* Generates corresponding '.d.ts' file. */ "sourceMap": true /* Generates corresponding '.map' file. */ }, "include": [ "src/**/*.ts" ], "exclude": [ "node_modules", "**/*.spec.ts" ] } ``` ### Declaration Files (`.d.ts`) - **Definition**: TypeScript declaration files (`.d.ts`) provide type information for JavaScript code. - **Purpose**: - Allow TypeScript projects to use existing JavaScript libraries. - Describe the shape of modules, functions, classes, etc., without providing implementations. - Enable type checking, autocompletion, and refactoring for JS code. - **Structure**: - Uses `declare` keyword for global variables, functions, classes, enums, and namespaces. - For modules, uses `declare module "module-name" { ... }` or `export` for ES modules. - **Global Declarations**: ```typescript // globals.d.ts declare var MY_GLOBAL_VARIABLE: string; declare function myFunction(arg: number): string; declare class MyClass { constructor(name: string); method(): void; } declare enum MyEnum { One, Two } declare namespace MyNamespace { export interface MyInterface { prop: number; } } ``` - **Module Declarations**: - **External Modules (ES Modules)**: ```typescript // my-library.d.ts export declare function greet(name: string): string; export declare const version: string; export declare class MyService { constructor(config: { url: string }); fetchData(): Promise ; } export type MyCustomType = "foo" | "bar"; ``` - **UMD Modules**: For libraries that can be used as a global or a module. ```typescript // moment.d.ts declare namespace moment { function (date?: any): moment.Moment; // ... more declarations } declare module "moment" { export = moment; } ``` - **`/// `**: Directive to include type declarations from `node_modules/@types`. ```typescript /// ``` - **`/// `**: Directive to include type declarations from other local `.d.ts` files. (Less common with modern module systems). - **`@types` packages**: The most common way to get type definitions for popular JS libraries. ```bash npm install --save-dev @types/react @types/node ``` - **Authoring Declaration Files**: - Start with `declare` for global scope. - Use `export` for module scope. - Use `interface` and `type` to define shapes. - Use `declare module "module-name"` for non-ES modules. - **`dts-gen`**: A tool to generate initial `.d.ts` files from JavaScript. ### JSX and TSX - **Definition**: JSX is a syntax extension for JavaScript, commonly used with React. TSX is TypeScript's version of JSX. - **Enabling JSX**: Set `jsx` option in `tsconfig.json` (e.g., `react`, `preserve`, `react-native`). - `preserve`: Keep JSX as is, for consumption by another transpiler (e.g., Babel). - `react`: Transpile JSX into `React.createElement` calls. - `react-native`: Transpile JSX into `React.createElement` calls, but preserves all non-component tags. - **Basic TSX Component**: ```tsx // MyComponent.tsx import * as React from "react"; interface Props { name: string; age?: number; } class MyComponent extends React.Component { render() { return ( Hello, {this.props.name}! {this.props.age && You are {this.props.age} years old. } ); } } export default MyComponent; ``` - **Functional Components**: ```tsx import React from "react"; interface GreetProps { message: string; } const Greet: React.FC = ({ message }) => { return {message} ; }; export default Greet; ``` - **Type Checking JSX Attributes**: - TypeScript checks attributes against the component's prop types. - **Intrinsic Elements**: HTML elements (e.g., `div`, `span`). Types are looked up in the special interface `JSX.IntrinsicElements`. ```typescript declare namespace JSX { interface IntrinsicElements { div: React.HTMLAttributes ; span: React.HTMLAttributes ; // ... } } ``` - **Value-based Elements**: Custom components. TypeScript determines the type of the element's constructor or functional component. - For class components, the instance type is checked against `JSX.ElementClass`. - For functional components, the function's call signature is used. - **Children Prop**: - Often typed as `React.ReactNode` for flexibility (strings, numbers, elements, arrays, fragments). - Or more specifically, `React.ReactElement`, `React.ReactChild`, etc. - **Fragments**: `<>... ` or ` ... ` are fully supported. - **Type Assertions in TSX**: - ` value` syntax conflicts with JSX tags. - Use `value as Type` instead. ```tsx let foo = as any; // Error: JSX element 'Foo' has no corresponding closing tag. let bar = as any; // OK ``` - **Contextual Typing for JSX**: ```tsx interface MyButtonProps { onClick: (event: React.MouseEvent ) => void; label: string; } const MyButton = (props: MyButtonProps) => ( {props.label} ); const App = () => ( { console.log(e.currentTarget.tagName); // e is correctly typed as React.MouseEvent }} /> ); ``` ### Advanced Types - **`this` Types**: - **`this` parameters**: Explicitly declare the type of `this` in a function. ```typescript interface HTMLElement { addClickListener(onclick: (this: void, e: Event) => void): void; } class MyHandler { info: string = "some info"; onClick(this: MyHandler, e: Event) { // 'this' must be MyHandler console.log(this.info); } } let h = new MyHandler(); // document.getElementById("my-button")?.addClickListener(h.onClick); // Error if 'this' is not void // Correct way to bind 'this' document.getElementById("my-button")?.addEventListener("click", h.onClick.bind(h)); ``` - **Polymorphic `this` types**: Allows `this` to refer to the specific subclass type. ```typescript class BasicCalculator { public constructor(protected value: number = 0) { } public currentValue(): number { return this.value; } public add(operand: number): this { // Returns 'this' type this.value += operand; return this; } public multiply(operand: number): this { this.value *= operand; return this; } } class ScientificCalculator extends BasicCalculator { public constructor(value = 0) { super(value); } public sin(): this { this.value = Math.sin(this.value); return this; } } let v = new ScientificCalculator(2) .multiply(5) .sin() .add(1) .currentValue(); // v is correctly inferred as number ``` - **Index Signatures**: (Covered in Interfaces, but important for advanced use) - Allow arbitrary properties to be added to an object. - Useful for dictionaries or objects with dynamic keys. ```typescript interface StringDictionary { [key: string]: string; // name: number; // Error: Property 'name' of type 'number' is not assignable to string index type 'string'. } interface NumberDictionary { [index: number]: string; length: number; // OK, 'length' is compatible with the string index signature. } ``` - **`keyof` type operator**: Creates a union type of the literal string or number types of an object's keys. ```typescript type Point = { x: number; y: number }; type P = keyof Point; // "x" | "y" type Arrayish = { [n: number]: unknown }; type A = keyof Arrayish; // number (because array keys are numbers) type Mapish = { [k: string]: boolean }; type M = keyof Mapish; // string | number (because JS objects can have number keys which are coerced to strings) ``` - **`typeof` type operator**: Used in a type context to refer to the type of a variable or property. ```typescript let s = "hello"; let n: typeof s; // n is of type string function f() { return { x: 10, y: 3 }; } type P = ReturnType ; // { x: number, y: number } ``` - **Indexed Access Types (`T[K]`)**: Look up a property on another type. ```typescript type Person = { age: number; name: string; alive: boolean }; type Age = Person["age"]; // number type NameOrAge = Person["age" | "name"]; // number | string type Keys = keyof Person; // "age" | "name" | "alive" type PersonProperties = Person[keyof Person]; // string | number | boolean ``` - **Template Literal Types**: Construct new string literal types by concatenating string literals. ```typescript type World = "world"; type Greeting = `hello ${World}`; // "hello world" type EmailLocaleIDs = "welcome_email" | "email_heading"; type FooterLocaleIDs = "footer_title" | "footer_sendoff"; type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`; // "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id" ``` - Can be combined with `Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize`. ```typescript type PropEventSource = { on (eventName: `${K}Changed`, callback: (newValue: T[K]) => void ): void; }; declare function makeWatchedObject (obj: T): T & PropEventSource ; const person = makeWatchedObject({ firstName: "Saoirse", lastName: "Ronan", age: 26 }); person.on("firstNameChanged", newName => { console.log(`new name is ${newName.toUpperCase()}`); }); // person.on("ageChanged", newAge => { /* ... */ }); // newAge is number // person.on("wrongChanged", () => {}); // Error ``` ### Type Challenges & Best Practices - **Never type for Exhaustive Checks**: ```typescript type Shape = | { kind: "circle"; radius: number } | { kind: "square"; sideLength: number } | { kind: "triangle"; base: number; height: number }; function getArea(shape: Shape) { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "square": return shape.sideLength ** 2; // case "triangle": // If removed, next line will error. // return 0.5 * shape.base * shape.height; default: const _exhaustiveCheck: never = shape; // Ensures all cases are handled. return _exhaustiveCheck; } } ``` - **`const` assertions**: - Tell TypeScript to infer the narrowest possible type for an expression. - Applied to string, number, boolean, array, and object literals. ```typescript let x = "hello" as const; // type is "hello" (literal), not string let y = [10, 20] as const; // type is readonly [10, 20] (tuple), not number[] let z = { text: "foo" } as const; // type is { readonly text: "foo" } (readonly literal), not { text: string } // function handle(name: "foo" | "bar") { /* ... */ } // let literal = "foo"; // type is string // handle(literal); // Error // handle(literal as "foo"); // OK (type assertion) // let constLiteral = "foo" as const; // type is "foo" // handle(constLiteral); // OK (const assertion) ``` - **Immutability with `readonly` and `Readonly `**: - Use `readonly` for individual properties. - Use `Readonly ` utility type for object types. - Use `ReadonlyArray ` for arrays. - `as const` for deep immutability of literals. - **Strict Mode (`"strict": true`)**: - Always start new projects with `strict: true` in `tsconfig.json`. - It enables `noImplicitAny`, `strictNullChecks`, `noImplicitThis`, `alwaysStrict`, `strictFunctionTypes`, `strictPropertyInitialization`, `strictBindCallApply`. - Catches a significant number of common errors. - **`unknown` vs `any`**: - Prefer `unknown` over `any` when you don't know the type of a value. - `unknown` is type-safe: you must explicitly narrow its type before performing operations. - `any` bypasses all type checking. - **Type Guards for Runtime Checks**: - Remember that TypeScript types are compile-time only. - Use `typeof`, `instanceof`, `in`, and custom type guards to perform runtime checks and narrow types. - **Avoid Over-engineering Types**: - While TypeScript is powerful, don't create overly complex types that are hard to read and maintain. - Sometimes, a simpler union or interface is more appropriate than an intricate conditional or mapped type. - **When to use `interface` vs `type`**: - **Interface**: Favored for defining object shapes, especially public APIs and when declaration merging is desired. - **Type Alias**: Favored for unions, intersections, primitives, tuples, and when recursive types are needed. - **ES Modules vs Namespaces**: - For modern applications, always prefer ES Modules (`import`/`export`). - Namespaces are useful for global code organization or legacy codebases. - **Linter (ESLint with `@typescript-eslint/parser`)**: - Use a linter to enforce coding standards and catch potential issues beyond what the TypeScript compiler does. - Integrates well with TypeScript to provide additional type-aware linting rules. - **Automated Type Generation (`dts-bundle-generator`, `api-extractor`)**: - For libraries, automate the generation of a single `.d.ts` bundle file for distribution. - **Type-only Imports/Exports (`import type`, `export type`)**: - If you only need to import/export types (not runtime values), use `import type` or `export type`. - This ensures that no JavaScript code is generated for these imports/exports, which can be useful for bundlers and avoiding circular dependencies. ```typescript import type { SomeType } from "./some-module"; export type { AnotherType } from "./another-module"; ```