Master TypeScript mastery: template literal types, type assertions, and project configuration.
Template literal types, type assertions & guards, and tsconfig.json configuration.
type World = "world";
type Greeting = `hello ${World}`; // "hello world"
type Color = "red" | "blue" | "green";
type Size = "small" | "large";
type ColoredSize = `${Size}-${Color}`;
// "small-red" | "small-blue" | "small-green" | "large-red" | ...type Upper = Uppercase<"hello">; // "HELLO"
type Lower = Lowercase<"HELLO">; // "hello"
type Cap = Capitalize<"hello">; // "Hello"
type Uncap = Uncapitalize<"Hello">; // "hello"type Events = "click" | "focus" | "blur";
type EventHandlers = `on${Capitalize}`;
// "onClick" | "onFocus" | "onBlur"
interface Element {
onClick: () => void;
onFocus: () => void;
onBlur: () => void;
} type CSSUnit = "px" | "em" | "rem" | "%";
type CSSValue = `${number}${CSSUnit}`;
const width: CSSValue = "100px"; // OK
const height: CSSValue = "2.5em"; // OK
// const bad: CSSValue = "100"; // Error!type APIVersion = "v1" | "v2";
type Resource = "users" | "posts" | "comments";
type APIPath = `/api/${APIVersion}/${Resource}`;
// "/api/v1/users" | "/api/v1/posts" | "/api/v2/users" | ...
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = `${HTTPMethod} ${APIPath}`;
// "GET /api/v1/users" | "POST /api/v1/users" | ...type ExtractRoute = T extends `/${infer First}/${infer Rest}`
? { first: First; rest: Rest }
: never;
type Route = ExtractRoute<"/users/123">;
// { first: "users"; rest: "123" } Template literal: "hello world"
Event handlers: onClick, onFocus, onBlur
CSS value: 100px is valid
API path: /api/v1/users
Route extracted: users/123
type T = `prefix ${SomeType} suffix`Uppercase<"hello"> = "HELLO"Capitalize<"hello"> = "Hello"`${infer X}suffix`Review feedback below
// When you know more than TypeScript
const input = document.getElementById("myInput") as HTMLInputElement;
input.value = "Hello";
// Alternative syntax (not in JSX)
const el = document.createElement("div");
// Double assertion for incompatible types (use sparingly!)
const x = "hello" as unknown as number; function getLength(str: string | null): number {
// Tell TypeScript str is definitely not null here
return str!.length;
}
// Better: use optional chaining or guards instead
const len = str?.length ?? 0;function padLeft(value: string | number, padding: string | number) {
if (typeof padding === "number") {
// padding is number here
return " ".repeat(padding) + value;
}
// padding is string here
return padding + value;
}class Dog { bark() { console.log("Woof!"); } }
class Cat { meow() { console.log("Meow!"); } }
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // TypeScript knows it's Dog
} else {
animal.meow(); // TypeScript knows it's Cat
}
}interface Fish { swim(): void; }
interface Bird { fly(): void; }
// Type predicate: "pet is Fish"
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim(); // TypeScript knows it's Fish
} else {
pet.fly(); // TypeScript knows it's Bird
}
}interface Circle { kind: "circle"; radius: number; }
interface Square { kind: "square"; side: number; }
type Shape = Circle | Square;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
}
}Type assertion: HTMLInputElement
typeof guard: number path taken
instanceof guard: Dog.bark() called
Custom guard: isFish returned true
Discriminated union: circle area = 78.54
value as Type or <Type>valueif (typeof x === "string")if (x instanceof MyClass)function isX(val): val is X { }kind: "typename" propertyReview feedback below
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}{
"compilerOptions": {
"strict": true, // Enable all strict options
"noImplicitAny": true, // Error on implicit any
"strictNullChecks": true, // null/undefined handled strictly
"strictFunctionTypes": true, // Strict function type checking
"strictBindCallApply": true, // Check bind/call/apply
"strictPropertyInitialization": true, // Class props must be initialized
"noImplicitThis": true, // Error on implicit this
"alwaysStrict": true // Emit "use strict"
}
}{
"compilerOptions": {
"moduleResolution": "node", // Node.js style resolution
"baseUrl": "./src", // Base for non-relative imports
"paths": { // Path aliases
"@utils/*": ["utils/*"],
"@components/*": ["components/*"]
},
"esModuleInterop": true, // Better CommonJS/ES interop
"allowSyntheticDefaultImports": true
}
}{
"compilerOptions": {
"outDir": "./dist", // Output directory
"rootDir": "./src", // Root of source files
"declaration": true, // Generate .d.ts files
"declarationMap": true, // Source maps for .d.ts
"sourceMap": true, // Generate source maps
"inlineSources": true, // Include source in maps
"removeComments": false // Keep comments in output
}
}{
"compilerOptions": {
"noUnusedLocals": true, // Error on unused variables
"noUnusedParameters": true, // Error on unused params
"noImplicitReturns": true, // Error if not all paths return
"noFallthroughCasesInSwitch": true,// Error on switch fallthrough
"noUncheckedIndexedAccess": true, // Add undefined to index access
"exactOptionalPropertyTypes": true // Strict optional properties
}
}// tsconfig.json (root)
{
"files": [],
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/utils" }
]
}
// packages/core/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist"
},
"references": [
{ "path": "../utils" }
]
}tsconfig.json created
Strict mode: enabled
Module resolution: node
Path aliases: configured
Output: ./dist
Type checking: strict
"ES2020", "ES2021", "ESNext""commonjs", "ES2020", "ESNext"true enables all strict checks"@alias/*": ["path/*"]["src/**/*"] for glob patternsReview feedback below