TypeScript 4 3

Release Overview

TypeScript 4.3 was released on May 26, 2021, introducing separate read/write property types for accessors, enabling more precise modeling of getters and setters.

Key Metrics:

  • Release Date: May 26, 2021
  • Major Focus: Accessor type flexibility and class improvements
  • Breaking Changes: Minimal
  • Performance: Improved type checking speed

Separate Read/Write Types on Properties

Major Feature: Get and set accessors can now have different types.

Syntax

class SafeBox {
  private _value: string = "";

  // Read returns string | undefined
  get value(): string | undefined {
    return this._value || undefined;
  }

  // Write accepts string (not undefined)
  set value(newValue: string) {
    this._value = newValue;
  }
}

const box = new SafeBox();
box.value = "secret"; // ✅ Accepts string
const retrieved: string | undefined = box.value; // ✅ Returns string | undefined
box.value = undefined; // ❌ Error - setter only accepts string

Real-World Application: Validation on Write

Sanitize input while preserving read type:

class EmailField {
  private _email: string = "";

  // Read returns normalized email
  get email(): string {
    return this._email.toLowerCase();
  }

  // Write accepts string but validates
  set email(value: string) {
    if (!value.includes("@")) {
      throw new Error("Invalid email format");
    }
    this._email = value.trim();
  }
}

const field = new EmailField();
field.email = "  USER@EXAMPLE.COM  "; // Trims and stores
console.log(field.email); // "user@example.com" (lowercase)

Real-World Application: Coercion

Accept flexible input, return precise type:

class Port {
  private _port: number = 8080;

  get port(): number {
    return this._port;
  }

  // Accept string or number, coerce to number
  set port(value: string | number) {
    const parsed = typeof value === "string" ? parseInt(value, 10) : value;
    if (isNaN(parsed) || parsed < 1 || parsed > 65535) {
      throw new Error("Invalid port number");
    }
    this._port = parsed;
  }
}

const config = new Port();
config.port = "3000"; // ✅ Accepts string
config.port = 8080; // ✅ Accepts number
const portNumber: number = config.port; // ✅ Always returns number

override Keyword

Feature: Explicitly mark methods that override base class methods, preventing accidental overrides.

Syntax

class Base {
  greet() {
    console.log("Hello from Base");
  }
}

class Derived extends Base {
  override greet() {
    // ✅ Explicit override
    console.log("Hello from Derived");
  }

  override goodbye() {
    // ❌ Error - no method 'goodbye' in base class
  }
}

Compiler Flag: --noImplicitOverride

Enforce explicit override keyword:

// tsconfig.json
{
  "compilerOptions": {
    "noImplicitOverride": true
  }
}

Real-World Application: Framework Components

Prevent accidental method name typos:

abstract class Component {
  abstract render(): string;
  mount(): void {
    console.log("Mounting component");
  }
  unmount(): void {
    console.log("Unmounting component");
  }
}

class Button extends Component {
  override render(): string {
    return "<button>Click me</button>";
  }

  override mount(): void {
    super.mount();
    console.log("Button mounted");
  }

  // Typo protection
  override unmout(): void {
    // ❌ Error - no 'unmout' method in base (catches typo)
  }
}

Static Index Signatures

Feature: Allow index signatures on static side of classes.

Syntax

class APIEndpoints {
  [endpoint: string]: string;

  static [key: string]: string;
  static users = "/api/users";
  static posts = "/api/posts";
}

// Access static members dynamically
const endpoint = APIEndpoints["users"]; // "/api/users"

Real-World Application: Configuration Registry

class FeatureFlags {
  [key: string]: boolean;

  static [key: string]: boolean;
  static darkMode = true;
  static betaFeatures = false;
  static analytics = true;

  static getAll(): Record<string, boolean> {
    const flags: Record<string, boolean> = {};
    for (const key in this) {
      if (typeof this[key] === "boolean") {
        flags[key] = this[key];
      }
    }
    return flags;
  }
}

// Dynamic access
const isDarkModeEnabled = FeatureFlags["darkMode"]; // true
console.log(FeatureFlags.getAll());
// { darkMode: true, betaFeatures: false, analytics: true }

Template String Type Improvements

Improvement: Better inference for template literal types in generic contexts.

Example

function makeEventName<T extends string>(prefix: T, action: string) {
  return `${prefix}:${action}` as const;
}

const event = makeEventName("user", "created");
// Type inferred: "user:created" (literal type, not just string)

await in Non-Async Functions (Top-Level)

Feature: Support for top-level await in modules (ES2022 feature).

Syntax

// module.ts
const data = await fetch("/api/data").then((r) => r.json());
export { data };

// No async wrapper needed for module-level await

Requirement: Module must have "module": "es2022" or later in tsconfig.json.

Performance Improvements

Build Performance:

  • 10-15% faster type checking with getters/setters
  • Improved inference for template literal types
  • Better caching for project references

Editor Performance:

  • Faster autocomplete for large class hierarchies
  • Reduced memory usage for mapped types

Breaking Changes

Minor breaking changes:

  1. lib.d.ts updates - DOM type refinements
  2. Always-truthy promise checks - if (promise) now flagged as likely bug
  3. Getters/setters with different types - May surface latent type mismatches

Migration Guide

Step 1: Update TypeScript

npm install -D typescript@4.3

Step 2: Leverage override Keyword

Enable --noImplicitOverride for safer inheritance:

{
  "compilerOptions": {
    "noImplicitOverride": true
  }
}

Step 3: Refactor Accessors

Use different read/write types where appropriate:

// Before: Same type for get/set
get value(): string | undefined {
  return this._value;
}
set value(v: string | undefined) {
  this._value = v;
}

// After: Different types
get value(): string | undefined {
  return this._value;
}
set value(v: string) {
  // No undefined accepted
  this._value = v;
}

Summary

TypeScript 4.3 (May 2021) enhanced class and accessor flexibility:

  • Separate read/write types - Different types for getters and setters
  • override keyword - Explicit method override marking
  • Static index signatures - Dynamic access to static members
  • Template literal improvements - Better inference in generics
  • Top-level await - ES2022 module feature support

Next Steps:

References

Last updated