DevToolBoxGRATIS
Blog

TypeScript 5 Nuove Funzionalita: Decoratori, Const Type Params e Satisfies

12 mindi DevToolBox

TypeScript 5 introduced significant new features that improve type safety, developer productivity, and code organization. From standardized ECMAScript decorators to const type parameters, satisfies operator improvements, and module resolution enhancements, TypeScript 5 represents a major step forward. This guide covers every important new feature with practical examples you can use in your projects today.

ECMAScript Decorators

TypeScript 5 implements the TC39 Stage 3 decorator proposal, replacing the experimental decorator implementation from TypeScript 4.x. The new decorators are standardized, do not require experimentalDecorators flag, and work differently from the legacy implementation.

Class Decorators

Class decorators receive the class constructor and can return a new class that replaces it. They are called at class definition time.

// Class decorator: adds metadata and logging
function sealed(target: Function, context: ClassDecoratorContext) {
  Object.seal(target);
  Object.seal(target.prototype);
  console.log(`Class ${String(context.name)} has been sealed`);
}

function withTimestamp<T extends new (...args: any[]) => {}>(
  target: T,
  context: ClassDecoratorContext
) {
  return class extends target {
    createdAt = new Date();
  };
}

@sealed
@withTimestamp
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

const user = new User("Alice");
console.log(user.createdAt); // Date object

Method Decorators

Method decorators receive the method function and context information. They can wrap, replace, or modify methods.

// Method decorator: logging and timing
function log(
  target: Function,
  context: ClassMethodDecoratorContext
) {
  return function (this: any, ...args: any[]) {
    console.log(`Calling ${String(context.name)} with`, args);
    const result = target.apply(this, args);
    console.log(`${String(context.name)} returned`, result);
    return result;
  };
}

function memoize(
  target: Function,
  context: ClassMethodDecoratorContext
) {
  const cache = new Map();
  return function (this: any, ...args: any[]) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = target.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

class MathService {
  @log
  @memoize
  fibonacci(n: number): number {
    if (n <= 1) return n;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }
}

Auto-Accessor Fields

The accessor keyword creates auto-accessor fields with automatic getter and setter generation. Decorators can intercept these accessors.

// Auto-accessor with decorator
function observable(
  target: ClassAccessorDecoratorTarget<any, any>,
  context: ClassAccessorDecoratorContext
) {
  return {
    get(this: any) {
      return target.get.call(this);
    },
    set(this: any, value: any) {
      console.log(`${String(context.name)} changed to ${value}`);
      target.set.call(this, value);
    },
  };
}

class Settings {
  @observable
  accessor theme: string = "dark";

  @observable
  accessor fontSize: number = 14;
}

const settings = new Settings();
settings.theme = "light"; // logs: "theme changed to light"

const Type Parameters

The const modifier on type parameters tells TypeScript to infer the most specific (literal) type possible instead of widening to the base type. This eliminates the need for "as const" at call sites.

// Without const: types are widened
function createRoutes<T extends Record<string, string>>(routes: T): T {
  return routes;
}
const routes1 = createRoutes({ home: "/", about: "/about" });
// Type: { home: string; about: string }  (widened!)

// With const: literal types preserved
function createRoutes2<const T extends Record<string, string>>(routes: T): T {
  return routes;
}
const routes2 = createRoutes2({ home: "/", about: "/about" });
// Type: { readonly home: "/"; readonly about: "/about" }  (exact!)

// Practical example: type-safe event system
function defineEvents<const T extends Record<string, (...args: any[]) => void>>(
  events: T
): T {
  return events;
}

const events = defineEvents({
  click: (x: number, y: number) => {},
  keypress: (key: string) => {},
  scroll: (offset: number) => {},
});
// events.click is typed as (x: number, y: number) => void
// Object.keys(events) preserves "click" | "keypress" | "scroll"

// Array literal types
function firstElement<const T extends readonly unknown[]>(arr: T): T[0] {
  return arr[0];
}
const first = firstElement([1, "hello", true] as const);
// Type: 1 (not number)

satisfies Operator Enhancements

The satisfies operator (introduced in TypeScript 4.9 and enhanced in 5.x) validates that an expression matches a type without changing the inferred type. This gives you type checking and precise inference simultaneously.

// Problem: type annotation loses specificity
type Colors = Record<string, string | number[]>;

const colors1: Colors = {
  red: "#ff0000",
  green: [0, 255, 0],
};
// colors1.red is string | number[] -- lost specificity!

// Solution: satisfies preserves inferred types
const colors2 = {
  red: "#ff0000",
  green: [0, 255, 0],
} satisfies Colors;

colors2.red.toUpperCase();    // OK! TypeScript knows it's a string
colors2.green.map(x => x);   // OK! TypeScript knows it's number[]

// satisfies catches errors while preserving types
const config = {
  port: 3000,
  host: "localhost",
  debug: true,
  // timeout: "not a number",  // Error! Does not satisfy Config type
} satisfies {
  port: number;
  host: string;
  debug: boolean;
};
// config.port is number (not string | number)
// config.debug is true (not boolean)

Module Resolution: bundler Mode

TypeScript 5 introduces a new module resolution mode called "bundler" that matches how modern bundlers like Vite, Webpack, and esbuild resolve modules. This is more practical than the strict "node16" or "nodenext" modes for frontend projects.

// tsconfig.json for bundler projects (Vite, Webpack)
{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "module": "esnext",
    "target": "esnext",
    "noEmit": true,
    // Bundler mode supports:
    // - Package.json "exports" and "imports" fields
    // - Extensionless imports (import "./utils" resolves to "./utils.ts")
    // - Directory imports (import "./components" resolves to "./components/index.ts")
    // - .ts extension in imports (optional)
  }
}

// tsconfig.json for Node.js applications
{
  "compilerOptions": {
    "moduleResolution": "nodenext",
    "module": "nodenext",
    // Strict mode: requires file extensions in imports
    // import "./utils.js"  (not "./utils")
  }
}

Improved Type Narrowing

TypeScript 5 improves type narrowing in several scenarios, making the type checker smarter about understanding your code.

switch(true) Narrowing

TypeScript 5 properly narrows types in switch(true) patterns, which are a clean alternative to if-else chains.

// switch(true) narrowing - now works in TypeScript 5
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

function area(shape: Shape): number {
  switch (true) {
    case shape.kind === "circle":
      return Math.PI * shape.radius ** 2;  // shape is narrowed to circle
    case shape.kind === "rect":
      return shape.width * shape.height;    // shape is narrowed to rect
    case shape.kind === "triangle":
      return (shape.base * shape.height) / 2; // shape is narrowed to triangle
  }
}

Performance Improvements

TypeScript 5 delivers significant performance improvements across the board.

  • Package size reduced by 44% (from 63.8MB to 35.6MB)
  • Build times improved by 10-25% for most projects
  • Editor responsiveness improved due to faster type checking
  • Memory usage reduced through better internal data structures
  • Faster project loading with optimized module resolution caching

New Configuration Options

TypeScript 5 introduces several new tsconfig.json options for better project configuration.

// tsconfig.json - recommended TypeScript 5 settings
{
  "compilerOptions": {
    // New in TS 5
    "moduleResolution": "bundler",       // For Vite/Webpack projects
    "verbatimModuleSyntax": true,         // Replaces importsNotUsedAsValues
    "allowImportingTsExtensions": true,   // Allow .ts imports (with noEmit)

    // Recommended strict settings
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,

    // Modern output
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],

    // Other
    "noEmit": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "jsx": "react-jsx"
  }
}

Migrating to TypeScript 5

Upgrading to TypeScript 5 is straightforward for most projects. Here are the key steps and considerations.

Breaking Changes

  • Runtime requirements: TypeScript 5 requires Node.js 14.17 or later
  • Deprecated options removed: out, charset, keyofStringsOnly, noImplicitUseStrict, suppressExcessPropertyErrors, suppressImplicitAnyIndexErrors, and prepend in project references
  • lib.d.ts changes: DOM type definitions updated, some rarely-used types may have changed
  • Decorators: If using experimentalDecorators, the new standard decorators have different semantics
# Upgrade to TypeScript 5
npm install typescript@latest

# Check for issues without emitting
npx tsc --noEmit

# If using deprecated options, remove them from tsconfig.json
# "out" -> use "outDir" instead
# "keyofStringsOnly" -> removed, no replacement needed

# Run your test suite to catch any runtime issues
npm test

Frequently Asked Questions

Do I need to change my decorator code for TypeScript 5?

Not immediately. TypeScript 5 still supports the legacy experimentalDecorators flag. However, the new standard decorators have different behavior and are recommended for new code. Libraries like class-validator and TypeORM may need updates to use the new decorator format.

Is TypeScript 5 backward compatible?

Mostly yes. The major breaking changes are removal of deprecated compiler options and updates to lib.d.ts types. Most codebases can upgrade without changes. Run tsc --noEmit after upgrading to catch any issues.

What is the difference between satisfies and as?

The "as" keyword asserts a type and changes the inferred type. The "satisfies" operator validates against a type but preserves the original inferred type. Use satisfies when you want type checking without losing specificity.

Should I use the bundler module resolution?

Yes, for most frontend projects using Vite, Webpack, or other bundlers. It more accurately represents how your bundler resolves imports. Use "node16" or "nodenext" for Node.js libraries and applications that run directly in Node.js.

How do const type parameters differ from as const?

The const modifier on type parameters makes a generic function infer literal types automatically, without the caller needing to add "as const". It is more ergonomic for library authors because it removes the burden from the consumer.

𝕏 Twitterin LinkedIn
È stato utile?

Resta aggiornato

Ricevi consigli dev e nuovi strumenti ogni settimana.

Niente spam. Cancella quando vuoi.

Prova questi strumenti correlati

🔷JSON to TypeScript OnlineTS→TypeScript to JavaScript Converter{ }JSON Formatter

Articoli correlati

TypeScript Type Guards: Guida Completa al Controllo dei Tipi

Padroneggia i type guards TypeScript: typeof, instanceof, in e guards personalizzati.

Guida Zod: Schemi, Trasformazioni, Refinements e Integrazione tRPC

Padroneggiare la validazione Zod in TypeScript: schemi, trasformazioni, refinements e tRPC.