"TypeScript, the guardian of order in the chaotic world of JavaScript, where clarity and safety reign supreme.”
- Basic Types:
TypeScript includes several basic data types like
number
,string
,boolean
, andany
. Use them to define variables and function parameters.
let num: number = 5; let str: string = "Hello, TypeScript!"; let flag: boolean = true; let anyValue: any = "I can be anything!";
- Type Annotations:
You can explicitly annotate variable types using a colon (
:
).
let myVar: number; myVar = 42;
- Type Inference: TypeScript can often infer the type based on the assigned value.
let x = 5; // TypeScript infers x as number
- Interfaces: Use interfaces to define the shape of objects or classes.
interface Person { name: string; age: number; } let person: Person = { name: "Alice", age: 30 };
- Classes: TypeScript supports class-based object-oriented programming.
class Animal { constructor(public name: string) {} speak() { console.log(`My name is ${this.name}`); } } const dog = new Animal("Rover"); dog.speak();
- Function Types: Declare the types of function parameters and return values.
function add(a: number, b: number): number { return a + b; }
- Generics: Use generics for writing reusable and type-safe code.
function identity<T>(arg: T): T { return arg; }
- Union and Intersection Types: Create types that can be of multiple types (union) or must satisfy multiple conditions (intersection).
let result: number | string; let personOrAnimal: Person & Animal;
- Enums: Enumerations allow you to define a set of named constants.
enum Color { Red, Green, Blue, } let myColor: Color = Color.Red;
- Type Assertions: Use type assertions to tell TypeScript that you know more about a value's type than TypeScript does.
let value: any = "This is a string"; let strLength: number = (value as string).length;
- Type Compatibility: TypeScript has a system for checking compatibility between types, which is often referred to as "duck typing."
- Modules:
Organize your code into reusable modules using
import
andexport
.
// math.ts export function add(a: number, b: number): number { return a + b; } // app.ts import { add } from "./math";
Advanced Types
- Type Narrowing with
typeof
andinstanceof
: TypeScript allows you to narrow down types usingtypeof
andinstanceof
to make more specific type assertions.
function printIfNumber(value: number | string) { if (typeof value === 'number') { console.log(value.toFixed(2)); // Here, value is a number. } else { console.log(value.toUpperCase()); // Here, value is a string. } }
- Type Aliases: Type aliases let you create custom, reusable types.
type Point = { x: number; y: number; }; const origin: Point = { x: 0, y: 0 };
- Mapped Types: Mapped types allow you to create new types based on the properties of existing types.
type Options = { width: number; height: number; }; type ReadonlyOptions = { readonly [K in keyof Options]: Options[K] };
- Conditional Types: Conditional types enable you to create types based on conditions.
type NonNullable<T> = T extends null | undefined ? never : T; type StrOrNum = string | number; type NonNullStrOrNum = NonNullable<StrOrNum>;
- Template Literal Types: Template literal types let you create new types by concatenating string literals.
type Greeting = 'Hello, ' | 'Hi, '; type Language = 'English' | 'Spanish'; type Salutation = `${Greeting}${Language}`;
- Discriminated Unions: Discriminated unions use a common property to determine the specific type in a union.
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; sideLength: number }; function getArea(shape: Shape): number { if (shape.kind === 'circle') { return Math.PI * shape.radius ** 2; } else { return shape.sideLength ** 2; } }
- Function Overloads: Function overloads allow you to provide multiple type signatures for a function.
function greet(name: string): string; function greet(names: string[]): string; function greet(names: string | string[]): string { if (Array.isArray(names)) { return `Hello, ${names.join(' and ')}!`; } else { return `Hello, ${names}!`; } }
- Key Remapping in Objects: You can remap keys in objects using mapped types.
type AliasMap<T> = { [K in keyof T as `new_${K}`]: T[K] }; const original = { name: 'Alice', age: 30 }; const remapped: AliasMap<typeof original> = { new_name: 'Alice', new_age: 30 };
- Infer Keyword:
The
infer
keyword lets you extract type information from generic types.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type Result = ReturnType<() => number>; // Result is number.
- Tuple Types: TypeScript allows you to define tuple types with a specific length and type for each element.
type ThreeDCoordinates = [number, number, number]; const point: ThreeDCoordinates = [1, 2, 3];
- Type Definitions for External Libraries:
TypeScript provides type definitions (
.d.ts
) for JavaScript libraries to enable type checking and auto-completion in your code. You can install these types using npm or yarn.
# Install type definitions for a library npm install --save @types/library-name
- TypeScript Compiler (tsc): Use the TypeScript compiler (tsc) to transpile TypeScript code into JavaScript. First, install TypeScript globally or locally in your project:
npm install -g typescript # Global installation # OR npm install typescript --save-dev # Local installation
Create a TypeScript file (e.g.,
app.ts
) and compile it:// app.ts const message: string = "Hello, TypeScript!"; console.log(message);
Compile it using tsc:
tsc app.ts
This generates an
app.js
file that can be run in a Node.js environment or included in your web application.- tsconfig.json:
The
tsconfig.json
file configures your TypeScript project. Here's a simple example:
{ "compilerOptions": { "target": "ES6", "outDir": "./dist", "rootDir": "./src" } }
This configuration specifies the target JavaScript version, output directory for compiled code, and source code directory. You can customize it further to suit your project.
- TypeScript Tooling: TypeScript-aware editors like Visual Studio Code provide excellent TypeScript support out of the box. You'll get features like autocompletion, type checking, and quick error feedback.
- Declaration Files:
When you have a JavaScript library without type definitions, you can create a declaration file (
.d.ts
) to provide type information. For example, if you have amy-library.js
:
// my-library.js function add(a, b) { return a + b; }
Create a corresponding
my-library.d.ts
declaration file:// my-library.d.ts declare function add(a: number, b: number): number;
TypeScript will now recognize the types for the functions in
my-library.js
.- Advanced Types: Explore advanced TypeScript types like mapped types, conditional types, and template literal types based on your project needs.
Async/await
Async/await is a feature in TypeScript (and JavaScript) that simplifies asynchronous code by allowing you to write it in a more synchronous style. It's particularly useful when dealing with asynchronous operations like network requests or file I/O. Here's an explanation with code snippets:
Using async
and await
The
async
keyword is used to define a function as asynchronous. An asynchronous function returns a Promise implicitly. You can then use the await
keyword inside an asynchronous function to pause its execution until a Promise is resolved.async function fetchData() { try { const response = await fetch('<https://api.example.com/data>'); const data = await response.json(); return data; } catch (error) { console.error('Error:', error); throw error; } }
In this example:
fetchData
is an asynchronous function.
- We use
await
withfetch
to wait for the network request to complete and get the response.
- Then, we use
await
withresponse.json()
to wait for the parsing of JSON data.
- If there's an error, we catch it and handle it gracefully.
Error Handling
Async/await allows for more natural error handling using try-catch blocks. Any errors thrown in the
async
function can be caught and managed in the catch
block.Use Cases
- Fetching Data from APIs: Async/await is commonly used for making HTTP requests to APIs and handling the responses.
- Reading/Writing Files: When working with Node.js, you can use async/await for reading and writing files asynchronously.
- Database Operations: Async/await is useful for interacting with databases, where operations can be asynchronous.
- Promisified Functions: You can use async/await to simplify the use of functions that return Promises, making your code more readable.
- Sequential Execution: Async/await allows you to write asynchronous code in a sequential, easy-to-read manner.
- Concurrent Execution:
You can also use async/await in combination with
Promise.all
for concurrent execution of multiple asynchronous tasks.
Browser Support
Async/await is widely supported in modern browsers and is available in Node.js. However, if you're targeting older environments, consider using a transpiler like Babel to ensure compatibility.
Remember that async/await is just a syntax improvement for working with Promises. It doesn't change the fundamental asynchronous nature of JavaScript/TypeScript, so you still need to be mindful of potential issues like callback hell or concurrency. Async/await is a valuable tool for making your asynchronous code more readable and maintainable.
Decorators
Decorators allow you to add metadata and behavior to classes and class members. Here's a simple example of a class decorator:
Basic Decorator Example:
function logClass(target: any) { console.log(`Class ${target.name} is being constructed.`); } @logClass class MyClass { constructor() { console.log('MyClass constructor'); } } // Output: // Class MyClass is being constructed. // MyClass constructor
In this example,
logClass
is a decorator function that logs a message when a class is constructed. The @logClass
syntax is used to apply the decorator to the MyClass
class.Decorator Factories:
Decorators can accept arguments and return functions, allowing you to create decorator factories.
function logMethod(prefix: string) { return function (target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`${prefix} - Before method call`); const result = originalMethod.apply(this, args); console.log(`${prefix} - After method call`); return result; }; }; } class Example { @logMethod('Example') myMethod() { console.log('Inside myMethod'); } } const instance = new Example(); instance.myMethod(); // Output: // Example - Before method call // Inside myMethod // Example - After method call
Here,
logMethod
is a decorator factory that accepts a prefix
argument. The factory returns a decorator function that logs messages before and after the method call.Use Cases for Decorators:
- Logging and Debugging: Decorators can log method calls, measure execution times, and help with debugging.
- Authentication and Authorization: You can use decorators to restrict access to certain methods or routes based on user roles or authentication status.
- Validation: Decorators can validate method arguments or object properties.
- Memoization: Decorators can cache the results of function calls to improve performance.
- Dependency Injection: Frameworks like Angular use decorators for dependency injection and service registration.
- Route Handling (in Express.js): You can use decorators to define routes and middleware functions in Express.js controllers.
- Data Validation (in Mongoose): In Mongoose (a MongoDB library for Node.js), decorators can validate and define data schema for models.
- Redux Middleware: In Redux, decorators can be used to create custom middleware for actions.
- State Management (in MobX): MobX uses decorators to define observable properties and actions that update the state.
- Aspect-Oriented Programming: Decorators enable aspect-oriented programming by separating concerns like logging, caching, and authentication from the core business logic.
Keep in mind that decorators are not unique to TypeScript; they are part of the ECMAScript proposal. In TypeScript, they are well-suited for use with class-based programming and are widely used in frameworks like Angular and Nest.js. However, they should be used judiciously, as overuse can lead to complex and hard-to-maintain code.