只读属性

只读属性使用 readonly 修饰,表示属性只能在初始化时赋值,之后不能修改。

基本语法

interface User {
  readonly id: number
  name: string
}

const user: User = {
  id: 1,
  name: "张三"
}

user.name = "李四"  // 正确
user.id = 2         // 错误:只读属性不能修改

使用场景

不可变 ID

interface Entity {
  readonly id: number
  name: string
  createdAt: Date
}

function updateEntity(entity: Entity, name: string): Entity {
  return {
    ...entity,
    name
    // id 和 createdAt 保持不变
  }
}

配置常量

interface Config {
  readonly apiKey: string
  readonly endpoint: string
  timeout?: number
}

const config: Config = {
  apiKey: "abc123",
  endpoint: "/api"
}

config.timeout = 5000  // 正确
config.apiKey = "xyz"  // 错误:只读属性

坐标点

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

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

const origin: Point = { x: 0, y: 0 }
const moved = move(origin, 10, 20)

ReadonlyArray

只读数组,不能修改:

const numbers: ReadonlyArray<number> = [1, 2, 3]

numbers.push(4)     // 错误
numbers[0] = 10     // 错误
numbers.length = 0  // 错误

const newNumbers = [...numbers, 4]  // 正确:创建新数组

简写形式

const names: readonly string[] = ["张三", "李四"]

Readonly 工具类型

将所有属性变为只读:

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

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

user.name = "李四"  // 错误:所有属性都是只读的

深层只读

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

interface User {
  id: number
  profile: {
    name: string
    address: {
      city: string
    }
  }
}

const user: DeepReadonly<User> = {
  id: 1,
  profile: {
    name: "张三",
    address: {
      city: "北京"
    }
  }
}

user.profile.name = "李四"  // 错误:深层只读

实际应用

不可变状态

interface State {
  readonly users: readonly User[]
  readonly loading: boolean
  readonly error: string | null
}

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

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "ADD_USER":
      return {
        ...state,
        users: [...state.users, action.payload]
      }
    default:
      return state
  }
}

函数参数保护

interface Config {
  readonly apiKey: string
  readonly endpoint: string
}

function fetchData(config: Config): void {
  // config.apiKey = "hacked"  // 错误:无法修改
  console.log(`请求 ${config.endpoint}`)
}

元组只读

type ReadonlyTuple = readonly [string, number]

const tuple: ReadonlyTuple = ["hello", 10]

tuple[0] = "world"  // 错误:只读
tuple.push("extra") // 错误:只读

注意事项

浅层只读

readonly 只影响第一层:

interface User {
  readonly profile: {
    name: string
  }
}

const user: User = {
  profile: { name: "张三" }
}

user.profile = { name: "李四" }  // 错误
user.profile.name = "李四"       // 正确:profile 内部不受影响

运行时可变

readonly 只在编译时检查:

interface User {
  readonly id: number
}

const user: User = { id: 1 }

(user as any).id = 2  // 运行时可以修改

与 const 的区别

// const 用于变量
const user = { id: 1 }
user = { id: 2 }  // 错误:不能重新赋值
user.id = 2       // 正确:可以修改属性

// readonly 用于属性
interface User {
  readonly id: number
}
const user: User = { id: 1 }
user.id = 2       // 错误:属性只读