阿里云主机折上折
  • 微信号
Current Site:Index > Parameter attributes

Parameter attributes

Author:Chuan Chen 阅读数:24358人阅读 分类: TypeScript

TypeScript's parameter properties are a syntactic sugar that allows declaring and initializing class properties directly within constructor parameters. This approach reduces boilerplate code and makes class definitions more concise.

Basic Syntax of Parameter Properties

By adding an access modifier (public, private, or protected) or the readonly modifier before a constructor parameter, TypeScript automatically converts that parameter into a class property with the same name. For example:

class Person {
  constructor(
    public name: string,
    private age: number,
    readonly id: string
  ) {}
}

const person = new Person('Alice', 30, '123');
console.log(person.name); // Output: Alice
console.log(person.age); // Error: Property 'age' is private
console.log(person.id); // Output: 123

How Parameter Properties Work

Parameter properties are essentially shorthand for the following traditional syntax:

class Person {
  name: string;
  private age: number;
  readonly id: string;

  constructor(name: string, age: number, id: string) {
    this.name = name;
    this.age = age;
    this.id = id;
  }
}

The TypeScript compiler converts parameter properties into regular class property declarations and assignment statements in the constructor.

Modifier Combinations for Parameter Properties

Parameter properties support various modifier combinations:

class Employee {
  constructor(
    public readonly employeeId: string,
    protected department: string,
    private _salary: number
  ) {}
}

const emp = new Employee('E1001', 'IT', 50000);
console.log(emp.employeeId); // Output: E1001
emp.employeeId = 'E1002'; // Error: Cannot assign to 'employeeId' because it is a read-only property

Mixing Parameter Properties with Regular Parameters

You can mix parameter properties with regular parameters in a constructor:

class Product {
  constructor(
    public sku: string,
    private cost: number,
    description: string
  ) {
    console.log(`Product ${sku} described as: ${description}`);
  }
}

const product = new Product('P100', 19.99, 'High-quality widget');
console.log(product.sku); // Output: P100
console.log(product.description); // Error: Property 'description' does not exist

Inheritance Behavior of Parameter Properties

When a subclass inherits from a parent class, the access modifiers of parameter properties remain effective:

class Animal {
  constructor(protected name: string) {}
}

class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name);
  }
  
  bark() {
    console.log(`${this.name} the ${this.breed} says woof!`);
  }
}

const dog = new Dog('Buddy', 'Golden Retriever');
dog.bark(); // Output: Buddy the Golden Retriever says woof!
console.log(dog.name); // Error: Property 'name' is protected

Type Inference for Parameter Properties

TypeScript performs type checking based on the type annotations of parameter properties:

class Point {
  constructor(
    public x: number,
    public y: number,
    public z?: number
  ) {}
}

const point2D = new Point(1, 2);
const point3D = new Point(3, 4, 5);
const invalidPoint = new Point('a', 'b'); // Error: Type 'string' is not assignable to type 'number'

Parameter Properties and Interface Implementation

Parameter properties can be used to implement properties required by interfaces:

interface Identifiable {
  id: string;
}

class User implements Identifiable {
  constructor(public id: string, public username: string) {}
}

const user = new User('u1', 'alice');
console.log(user.id); // Output: u1

Compiled Output of Parameter Properties

Examining the compiled JavaScript code helps understand the essence of parameter properties:

// TypeScript source code
class Car {
  constructor(public model: string, private vin: string) {}
}

// Compiled JavaScript
"use strict";
class Car {
  constructor(model, vin) {
    this.model = model;
    this.vin = vin;
  }
}

Use Cases for Parameter Properties

Parameter properties are particularly suitable for:

  1. DTOs (Data Transfer Objects): Quickly define data container classes

    class UserDTO {
      constructor(
        public id: string,
        public name: string,
        public email: string
      ) {}
    }
    
  2. Entity Classes: Concisely define domain models

    class Order {
      constructor(
        public orderId: string,
        public items: OrderItem[],
        private _total: number
      ) {}
    }
    
  3. Configuration Objects: Initialize configuration options

    class AppConfig {
      constructor(
        public apiUrl: string,
        public timeout: number,
        public debug: boolean
      ) {}
    }
    

Considerations for Parameter Properties

  1. Initialization Order: Parameter properties are initialized before the constructor body executes

    class Counter {
      count = 0;
      
      constructor(public initialValue: number) {
        this.count = initialValue; // Overrides the initial value of the parameter property
      }
    }
    
  2. Decorator Limitations: Parameter properties cannot directly use parameter decorators

    class Example {
      constructor(@SomeDecorator public param: string) {} // Error
    }
    
  3. Documentation Generation: Some documentation tools may not correctly recognize parameter properties

Parameter Properties in React Components

In React class components, parameter properties can simplify props type definitions:

interface Props {
  title: string;
  count: number;
}

class MyComponent extends React.Component<Props> {
  constructor(props: Props) {
    super(props);
  }
  
  // Simplified using parameter properties
  constructor(public props: Props) {
    super(props);
  }
}

Alternatives to Parameter Properties

If parameter properties are not used, similar effects can be achieved through other methods:

  1. Traditional Property Declaration:

    class Traditional {
      prop: string;
      
      constructor(prop: string) {
        this.prop = prop;
      }
    }
    
  2. Object.assign:

    class AssignExample {
      constructor(options: any) {
        Object.assign(this, options);
      }
    }
    
  3. Destructuring Assignment:

    class DestructureExample {
      constructor({ a, b }: { a: string; b: number }) {
        this.a = a;
        this.b = b;
      }
      a: string;
      b: number;
    }
    

Performance Considerations for Parameter Properties

Parameter properties do not introduce additional runtime overhead, as they are compiled into regular property assignments. The following example shows the comparison before and after compilation:

// Before compilation
class CompileExample {
  constructor(public a: string, private b: number) {}
}

// After compilation
class CompileExample {
  constructor(a, b) {
    this.a = a;
    this.b = b;
  }
}

Parameter Properties and Mapped Types

Combining with mapped types allows creating dynamic properties:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

class Configurable {
  constructor(config: Partial<Configurable>) {
    Object.assign(this, config);
  }
  id?: string;
  name?: string;
}

Parameter Properties and Generics

Parameter properties can be used with generics:

class GenericExample<T> {
  constructor(public value: T, private _default: T) {}
  
  reset() {
    this.value = this._default;
  }
}

const example = new GenericExample<string>('hello', 'default');
console.log(example.value); // Output: hello
example.reset();
console.log(example.value); // Output: default

Testing Considerations for Parameter Properties

When using parameter properties, testing should account for:

class Testable {
  constructor(public dependency: SomeService) {}
  
  method() {
    return this.dependency.doSomething();
  }
}

// In tests
const mockService = { doSomething: jest.fn() };
const instance = new Testable(mockService);
instance.method();
expect(mockService.doSomething).toHaveBeenCalled();

Parameter Properties and Dependency Injection

Parameter properties can simplify dependency injection code:

class InjectExample {
  constructor(
    @inject('ServiceA') public serviceA: ServiceA,
    @inject('ServiceB') private serviceB: ServiceB
  ) {}
}

Code Style Recommendations for Parameter Properties

  1. Consistency: Uniformly use or avoid parameter properties across the project
  2. Readability: Use parameter properties for simple classes and explicit declarations for complex logic
  3. Documentation: Add JSDoc comments for parameter properties
    class Documented {
      /**
       * Creates a documented example
       * @param value The primary value
       * @param count The counter
       */
      constructor(public value: string, private count: number) {}
    }
    

Tool Support for Parameter Properties

Most TypeScript toolchains handle parameter properties correctly:

  1. TypeScript Playground: Allows real-time viewing of compiled results
  2. VS Code: Provides IntelliSense and type checking
  3. ESLint: Includes rules for checking parameter property usage

Historical Evolution of Parameter Properties

Parameter properties were introduced in TypeScript 1.5 and have gradually become a common practice:

  • TypeScript 1.5 (July 2015): Initial introduction
  • TypeScript 2.0: Improved type checking
  • TypeScript 3.0: Better integration with generics

Comparison with Other Languages

Comparing with similar features in other languages:

  1. C#: Primary Constructors proposal
  2. Kotlin: Direct property declaration in constructors
    class Person(val name: String, var age: Int)
    
  3. Swift: Requires explicit storage property declarations

Advanced Patterns with Parameter Properties

Combining with other TypeScript features enables more complex patterns:

function createEntity<T>() {
  return class Entity {
    constructor(public props: T) {}
  };
}

const UserEntity = createEntity<{ name: string; age: number }>();
const user = new UserEntity({ name: 'Alice', age: 30 });

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.