参数属性
TypeScript 的参数属性是一种语法糖,它允许在构造函数参数中直接声明并初始化类的属性。这种方式减少了样板代码,让类定义更简洁。
参数属性的基本语法
在构造函数参数前添加访问修饰符(public
、private
或 protected
)或 readonly
修饰符,TypeScript 会自动将该参数转换为类的同名属性。例如:
class Person {
constructor(
public name: string,
private age: number,
readonly id: string
) {}
}
const person = new Person('Alice', 30, '123');
console.log(person.name); // 输出: Alice
console.log(person.age); // 错误: Property 'age' is private
console.log(person.id); // 输出: 123
参数属性的工作原理
参数属性实际上是以下传统写法的简写:
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;
}
}
TypeScript 编译器会将参数属性转换为常规的类属性声明和构造函数中的赋值语句。
参数属性的修饰符组合
参数属性支持多种修饰符组合使用:
class Employee {
constructor(
public readonly employeeId: string,
protected department: string,
private _salary: number
) {}
}
const emp = new Employee('E1001', 'IT', 50000);
console.log(emp.employeeId); // 输出: E1001
emp.employeeId = 'E1002'; // 错误: Cannot assign to 'employeeId' because it is a read-only property
参数属性与普通参数的混合使用
可以在构造函数中混合使用参数属性和普通参数:
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); // 输出: P100
console.log(product.description); // 错误: Property 'description' does not exist
参数属性的继承行为
子类继承父类时,参数属性的访问修饰符仍然有效:
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(); // 输出: Buddy the Golden Retriever says woof!
console.log(dog.name); // 错误: Property 'name' is protected
参数属性的类型推断
TypeScript 会根据参数属性的类型注解进行类型检查:
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'); // 错误: Type 'string' is not assignable to type 'number'
参数属性与接口实现
参数属性可以用于实现接口要求的属性:
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); // 输出: u1
参数属性的编译输出
查看编译后的 JavaScript 代码有助于理解参数属性的本质:
// TypeScript 源代码
class Car {
constructor(public model: string, private vin: string) {}
}
// 编译后的 JavaScript
"use strict";
class Car {
constructor(model, vin) {
this.model = model;
this.vin = vin;
}
}
参数属性的使用场景
参数属性特别适合以下情况:
-
DTO(数据传输对象):快速定义数据容器类
class UserDTO { constructor( public id: string, public name: string, public email: string ) {} }
-
实体类:简洁地定义领域模型
class Order { constructor( public orderId: string, public items: OrderItem[], private _total: number ) {} }
-
配置对象:初始化配置选项
class AppConfig { constructor( public apiUrl: string, public timeout: number, public debug: boolean ) {} }
参数属性的注意事项
-
初始化顺序:参数属性在构造函数体执行前就已经初始化
class Counter { count = 0; constructor(public initialValue: number) { this.count = initialValue; // 会覆盖参数属性的初始值 } }
-
装饰器限制:参数属性不能直接应用参数装饰器
class Example { constructor(@SomeDecorator public param: string) {} // 错误 }
-
文档生成:某些文档生成工具可能无法正确识别参数属性
参数属性与React组件
在React类组件中,参数属性可以简化props的类型定义:
interface Props {
title: string;
count: number;
}
class MyComponent extends React.Component<Props> {
constructor(props: Props) {
super(props);
}
// 使用参数属性简化
constructor(public props: Props) {
super(props);
}
}
参数属性的替代方案
如果不使用参数属性,可以通过其他方式实现类似效果:
-
传统属性声明:
class Traditional { prop: string; constructor(prop: string) { this.prop = prop; } }
-
Object.assign:
class AssignExample { constructor(options: any) { Object.assign(this, options); } }
-
解构赋值:
class DestructureExample { constructor({ a, b }: { a: string; b: number }) { this.a = a; this.b = b; } a: string; b: number; }
参数属性的性能考量
参数属性不会带来额外的运行时开销,因为它们在编译后会转换为常规的属性赋值语句。以下示例展示了编译前后的对比:
// 编译前
class CompileExample {
constructor(public a: string, private b: number) {}
}
// 编译后
class CompileExample {
constructor(a, b) {
this.a = a;
this.b = b;
}
}
参数属性与映射类型
结合映射类型可以创建动态的属性:
type Partial<T> = {
[P in keyof T]?: T[P];
};
class Configurable {
constructor(config: Partial<Configurable>) {
Object.assign(this, config);
}
id?: string;
name?: string;
}
参数属性与泛型
参数属性可以与泛型结合使用:
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); // 输出: hello
example.reset();
console.log(example.value); // 输出: default
参数属性的测试考虑
当使用参数属性时,测试中需要注意:
class Testable {
constructor(public dependency: SomeService) {}
method() {
return this.dependency.doSomething();
}
}
// 测试中
const mockService = { doSomething: jest.fn() };
const instance = new Testable(mockService);
instance.method();
expect(mockService.doSomething).toHaveBeenCalled();
参数属性与依赖注入
参数属性可以简化依赖注入的代码:
class InjectExample {
constructor(
@inject('ServiceA') public serviceA: ServiceA,
@inject('ServiceB') private serviceB: ServiceB
) {}
}
参数属性的代码风格建议
- 一致性:在项目中统一使用或不使用参数属性
- 可读性:对于简单类使用参数属性,复杂逻辑使用显式声明
- 文档:为参数属性添加JSDoc注释
class Documented { /** * 创建一个文档化示例 * @param value 主要值 * @param count 计数器 */ constructor(public value: string, private count: number) {} }
参数属性的工具支持
大多数TypeScript工具链都能正确处理参数属性:
- TypeScript Playground:可以实时查看编译结果
- VS Code:提供智能提示和类型检查
- ESLint:有相关规则检查参数属性的使用
参数属性的历史演变
参数属性自TypeScript 1.5版本引入,逐渐成为常见用法:
- TypeScript 1.5 (2015年7月):首次引入
- TypeScript 2.0:改进类型检查
- TypeScript 3.0:更好地与泛型配合
参数属性与其他语言的比较
与其他语言的类似特性对比:
- C#:主构造函数(Primary Constructors)提案
- Kotlin:直接在构造函数中声明属性
class Person(val name: String, var age: Int)
- Swift:需要显式声明存储属性
参数属性的高级模式
结合其他TypeScript特性可以实现更复杂的模式:
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
下一篇:装饰器基础与应用