只读修饰符

readonly 修饰符用于创建只读属性,属性只能在声明时或构造函数中赋值,之后无法修改。这有助于防止意外修改,保证数据的不可变性。

基本用法

在属性前添加 readonly 关键字,该属性就变成只读的。

class Config {
  readonly appName: string = "我的应用"
  readonly version: string

  constructor(version: string) {
    this.version = version
  }
}

const config = new Config("1.0.0")
console.log(config.appName)
console.log(config.version)

appName 在声明时初始化,version 在构造函数中赋值。之后尝试修改这两个属性都会报错。

参数属性中的 readonly

readonly 可以与访问修饰符一起用在构造函数参数上。

class User {
  constructor(
    readonly id: number,
    readonly name: string,
    public email: string
  ) {}
}

const user = new User(1, "张三", "zhangsan@example.com")
console.log(user.id)
console.log(user.name)
console.log(user.email)

user.email = "new@example.com"

idname 是只读属性,email 是可修改的公共属性。这种写法简洁明了,常用于定义不可变的标识属性。

readonly 与 private 配合

readonly 通常与 private 一起使用,创建对外只读但对内可写的属性。

class Counter {
  private _count: number = 0

  get count(): number {
    return this._count
  }

  increment(): void {
    this._count++
  }

  reset(): void {
    this._count = 0
  }
}

const counter = new Counter()
console.log(counter.count)
counter.increment()
counter.increment()
console.log(counter.count)
counter.reset()
console.log(counter.count)

通过私有属性 _count 和公共存取器 count,实现了对外只读的效果。类内部的方法可以修改 _count,但外部只能读取。

只读数组

ReadonlyArray 类型用于创建不可变的数组,数组元素不能被修改、添加或删除。

class DataStore {
  readonly items: ReadonlyArray<string>

  constructor(items: string[]) {
    this.items = items
  }

  addItem(item: string): DataStore {
    return new DataStore([...this.items, item])
  }
}

const store = new DataStore(["a", "b", "c"])
console.log(store.items)

const newStore = store.addItem("d")
console.log(newStore.items)

items 是只读数组,不能直接修改。addItem 方法返回一个新的 DataStore 实例,保持原实例不变。这种不可变数据模式在函数式编程中很常见。

readonly 与继承

子类可以继承父类的只读属性,但不能将其改为可写的。

class Entity {
  readonly id: number

  constructor(id: number) {
    this.id = id
  }
}

class User extends Entity {
  name: string

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

const user = new User(1, "张三")
console.log(user.id)
console.log(user.name)

id 在父类中定义为只读,子类继承后仍然是只读的。子类不能重新定义 id 为可写属性。

接口中的 readonly

接口中的属性也可以使用 readonly 修饰。

interface Point {
  readonly x: number
  readonly y: number
}

function movePoint(point: Point, dx: number, dy: number): Point {
  return {
    x: point.x + dx,
    y: point.y + dy
  }
}

const p1: Point = { x: 10, y: 20 }
const p2 = movePoint(p1, 5, 5)
console.log(p1)
console.log(p2)

接口定义的只读属性在赋值后不能修改。movePoint 函数返回一个新的点对象,而不是修改原来的点。

类型别名中的 readonly

使用类型别名可以创建只读的对象类型。

type ReadonlyUser = {
  readonly id: number
  readonly name: string
}

function printUser(user: ReadonlyUser): void {
  console.log(`ID: ${user.id}, Name: ${user.name}`)
}

const user: ReadonlyUser = { id: 1, name: "张三" }
printUser(user)

ReadonlyUser 类型的对象创建后,其属性不能被修改。

Readonly 工具类型

TypeScript 提供了 Readonly<T> 工具类型,将类型 T 的所有属性变为只读。

interface User {
  id: number
  name: string
  email: string
}

function freezeUser(user: User): Readonly<User> {
  return Object.freeze(user)
}

const user: User = {
  id: 1,
  name: "张三",
  email: "zhangsan@example.com"
}

const frozenUser = freezeUser(user)
console.log(frozenUser.id)

Readonly<User>User 的所有属性变为只读,配合 Object.freeze 可以实现真正的不可变对象。

深层只读

readonly 只作用于属性本身,不会递归应用到嵌套对象。要实现深层只读,需要自定义类型。

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}

interface Config {
  server: {
    host: string
    port: number
  }
  database: {
    host: string
    name: string
  }
}

const config: DeepReadonly<Config> = {
  server: {
    host: "localhost",
    port: 3000
  },
  database: {
    host: "localhost",
    name: "mydb"
  }
}

console.log(config.server.host)

DeepReadonly 递归地将所有嵌套属性变为只读,确保整个对象树都不可变。

实际应用场景

只读修饰符在配置对象、常量定义、不可变数据结构等场景中非常有用。

class Application {
  readonly config = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    maxRetries: 3
  }

  async fetchData(endpoint: string): Promise<any> {
    const url = `${this.config.apiUrl}/${endpoint}`
    console.log(`请求 ${url},超时 ${this.config.timeout}ms`)
  }
}

const app = new Application()
app.fetchData("users")

配置对象设为只读,确保运行时不会被意外修改,提高了代码的可靠性。

小结

readonly 修饰符用于创建只读属性,防止属性被意外修改。它可以用于类属性、接口属性、数组类型和工具类型。合理使用只读修饰符可以提高代码的安全性,明确表达数据不可变的意图,减少因意外修改导致的 bug。在需要不可变数据的场景中,readonly 是一个非常有用的工具。