TypeScript Concepts: A Deep Dive with Examples
This article is a comprehensive exploration of TypeScript, aimed at both beginners and experienced developers. Throughout the article, readers will gain a thorough understanding of TypeScript’s key concepts, Also we will describe complex types in the real-world. Each concept is accompanied by practical examples, equipping readers with the knowledge and tools to leverage TypeScript effectively in their own projects. Whether you’re new to TypeScript or seeking to deepen your understanding, this article will provide valuable insights and practical guidance for mastering TypeScript concepts.
Look at this Code
export type IsEqual<T1, T2> = T1 extends T2
? (<G>() => G extends T1 ? 1 : 2) extends <G>() => G extends T2 ? 1 : 2
? true
: false
: false;
type AnyIsEqual<T1, T2> = T1 extends T2
? IsEqual<T1, T2> extends true
? true
: never
: never;
type PathImpl<K extends string | number, V, TraversedTypes> = V extends
| Primitive
| BrowserNativeObject
? `${K}`
: // Check so that we don't recurse into the same type
// by ensuring that the types are mutually assignable
// mutually required to avoid false positives of subtypes
true extends AnyIsEqual<TraversedTypes, V>
? `${K}`
: `${K}` | `${K}.${PathInternal<V, TraversedTypes | V>}`;
type PathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V>
? IsTuple<T> extends true
? {
[K in TupleKeys<T>]-?: PathImpl<K & string, T[K], TraversedTypes>;
}[TupleKeys<T>]
: PathImpl<ArrayKey, V, TraversedTypes>
: {
[K in keyof T]-?: PathImpl<K & string, T[K], TraversedTypes>;
}[keyof T];
/**
* Type which eagerly collects all paths through a type
* @typeParam T - type which should be introspected
* @example
* ```
* Path<{foo: {bar: string}}> = 'foo' | 'foo.bar'
* ```
*/
// We want to explode the union type and process each individually
// so assignable types don't leak onto the stack from the base.
export type Path<T> = T extends any ? PathInternal<T> : never;
/**
* See {@link Path}
*/
export type FieldPath<TFieldValues extends FieldValues> = Path<TFieldValues>;
type ArrayPathImpl<K extends string | number, V, TraversedTypes> = V extends
| Primitive
| BrowserNativeObject
? IsAny<V> extends true
? string
: never
: V extends ReadonlyArray<infer U>
? U extends Primitive | BrowserNativeObject
? IsAny<V> extends true
? string
: never
: // Check so that we don't recurse into the same type
// by ensuring that the types are mutually assignable
// mutually required to avoid false positives of subtypes
true extends AnyIsEqual<TraversedTypes, V>
? never
: `${K}` | `${K}.${ArrayPathInternal<V, TraversedTypes | V>}`
: true extends AnyIsEqual<TraversedTypes, V>
? never
: `${K}.${ArrayPathInternal<V, TraversedTypes | V>}`;
This segment of the react-hook-form source code showcases robust TypeScript typings, offering valuable insights for mastering advanced TypeScript concepts.
Type Aliases:
The ‘type’ keyword serves as a versatile tool for defining custom types, enabling developers to create aliases for existing types and construct complex data structures with specific shapes and compositions. By using the ‘type’ keyword, developers can enhance code readability and maintainability, while also leveraging features such as union types, intersection types, and conditional types.
type UserID = number;
type Username = string;
// Usage
let userId: UserID = 123;
let username: Username = "john_doe";
// Object
type Car = {
make: string;
model: string;
year: number;
};
// Usage
let myCar: Car = {
make: "Toyota",
model: "Camry",
year: 2020
};
// Union
type Status = "active" | "inactive";
// Usage
let userStatus: Status = "active";
Unions (|
) and intersections (&
):
Unions:
type Vehicle = "Car" | "Bicycle" | "Motorcycle";
function getVehicleDescription(vehicle: Vehicle): string {
switch (vehicle) {
case "Car":
return "Four-wheeled vehicle";
case "Bicycle":
return "Two-wheeled, human-powered vehicle";
case "Motorcycle":
return "Two-wheeled motorized vehicle";
default:
return "Unknown vehicle type";
}
}
Intersections:
type Employee = {
id: number;
name: string;
};
type Role = {
role: string;
};
type Developer = Employee & Role;
const softwareEngineer: Developer = {
id: 1,
name: "John Doe",
role: "Software Engineer",
};
Nullable Type:
function findUserById(id: number): User | null {
// Search for a user by ID and return it if found; otherwise, return null
}
const user = findUserById(123);
if (user !== null) {
console.log(`User found: ${user.name}`);
} else {
console.log("User not found");
}
The unknown
Type:
let userInput: unknown;
if (typeof userInput === "string") {
console.log(userInput.toUpperCase());
} else {
console.log("User input is not a string");
}
‘is’:
function isObject(x: unknown): x is object {
return typeof x === "object" && x !== null && !Array.isArray(x);
}
// Usage
function processInput(data: unknown) {
if (isObject(data)) {
// 'data' is now narrowed to type 'object'
// Perform operations specific to objects
console.log("Data is an object:", data);
} else {
// 'data' is still of type 'unknown' here
console.log("Data is not an object");
}
}
// Example usage
processInput({ name: "John", age: 30 }); // Output: Data is an object: { name: "John", age: 30 }
processInput("Hello, world"); // Output: Data is not an object
Never type:
Certainly! The never
type in TypeScript is a type that represents values that never occur. It is often used in conditional types to express situations where a certain branch of the type should never be reached or produce a value.
type NonNullable<T> = T extends null | undefined ? never : T;
// Example usage
let variable1: string | null = "Hello";
let variable2: number | undefined = 42;
let variable3: null | undefined;
let result1: NonNullable<typeof variable1>; // Result: string
let result2: NonNullable<typeof variable2>; // Result: number
let result3: NonNullable<typeof variable3>; // Result: never (as it can be null or undefined)
It’s time to act.
Let’s collaboratively code a type-safe navigation service with ‘navigate’ and ‘goBack’ functions that intelligently handle parameters for each page.
class NavigationService<ParamListT extends Record<string, object | undefined>> {
navigate<KeyT extends keyof ParamListT, Key2T extends Exclude<keyof ParamListT, KeyT>>(
name: KeyT,
params?: { screen?: { name: Key2T; params?: ParamListT[Key2T] } } | ParamListT[KeyT]
) {
name;
params;
}
goBack() {}
}
const navigationService = new NavigationService<{
Home: undefined;
Product: { id: string };
Comment: { id: string };
}>();
navigationService.navigate('Product', { id: '', screen: { name: 'Home' } });
ParamListT
Type Parameter:
This is a generic type parameter for the NavigationService
class. It extends Record<string, object | undefined>
, which means it must be an object type where keys are strings (navigation route names), and values (navigation route parameters) are either objects or undefined
.
navigate
Method:
KeyT
and Key2T
Type Parameters:
KeyT
represents the keys of the ParamListT
type (navigation route names). Key2T
represents keys that can be used in nested navigate.
name
Parameter:
It must be of type KeyT
, which means it should be a valid key of the ParamListT
type.
params
Parameter:
It’s optional and can have two types:
{ screen?: { name: Key2T; params?: ParamListT[Key2T] } }
: An object with a screen
property that has a name
property of type Key2T
and an optional params
property of type ParamListT[Key2T]
.
ParamListT[KeyT]
: Directly the value associated with the key name
in the ParamListT
type. Presenting parameters of route.
End
I hope this article could help you typescript developers. Please help me to edit it.