可索引类型

可索引类型用于描述可以通过索引访问的类型,如数组和对象。

基本语法

interface StringArray {
  [index: number]: string
}

const names: StringArray = ["张三", "李四", "王五"]

console.log(names[0])  // "张三"

字符串索引

interface StringDictionary {
  [key: string]: string
}

const dict: StringDictionary = {
  name: "张三",
  city: "北京",
  country: "中国"
}

console.log(dict["name"])  // "张三"

混合类型

同时具有固定属性和索引签名:

interface UserCollection {
  [key: string]: User | number
  count: number
}

interface User {
  id: number
  name: string
}

const users: UserCollection = {
  count: 2,
  "user1": { id: 1, name: "张三" },
  "user2": { id: 2, name: "李四" }
}

索引类型限制

类型兼容

索引签名的返回值类型必须兼容所有属性:

interface Dictionary {
  [key: string]: string | number
  name: string      // 正确:string 兼容
  age: number       // 正确:number 兼容
  active: boolean   // 错误:boolean 不兼容
}

只读索引

interface ReadonlyStringArray {
  readonly [index: number]: string
}

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

names[0] = "王五"  // 错误:只读

实际应用

配置对象

interface Config {
  [key: string]: string | number | boolean
}

const config: Config = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  debug: true
}

function getConfig(key: string): string | number | boolean | undefined {
  return config[key]
}

缓存存储

interface Cache<T> {
  [key: string]: T
}

class MemoryCache<T> {
  private cache: Cache<T> = {}

  set(key: string, value: T): void {
    this.cache[key] = value
  }

  get(key: string): T | undefined {
    return this.cache[key]
  }

  has(key: string): boolean {
    return key in this.cache
  }

  delete(key: string): void {
    delete this.cache[key]
  }
}

const userCache = new MemoryCache<User>()

userCache.set("user:1", { id: 1, name: "张三" })
console.log(userCache.get("user:1"))

事件映射

interface EventMap {
  [eventName: string]: (...args: any[]) => void
}

class EventEmitter {
  private events: EventMap = {}

  on(event: string, handler: (...args: any[]) => void): void {
    this.events[event] = handler
  }

  emit(event: string, ...args: any[]): void {
    const handler = this.events[event]
    if (handler) {
      handler(...args)
    }
  }
}

翻译字典

interface TranslationDictionary {
  [key: string]: string
}

const translations: TranslationDictionary = {
  "hello": "你好",
  "world": "世界",
  "goodbye": "再见"
}

function translate(key: string): string {
  return translations[key] ?? key
}

console.log(translate("hello"))   // "你好"
console.log(translate("unknown")) // "unknown"

动态属性

interface Style {
  [property: string]: string
}

const style: Style = {
  color: "red",
  fontSize: "16px",
  marginTop: "10px"
}

function applyStyle(element: HTMLElement, style: Style): void {
  Object.assign(element.style, style)
}

Record 工具类型

使用 Record 创建索引类型:

type UserMap = Record<string, User>

const users: UserMap = {
  "user1": { id: 1, name: "张三" },
  "user2": { id: 2, name: "李四" }
}

索引访问类型

使用索引访问获取类型:

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

type UserId = User["id"]      // number
type UserName = User["name"]  // string
type UserKeys = keyof User    // "id" | "name" | "email"

注意事项

数字索引与字符串索引

JavaScript 会将数字索引转换为字符串:

interface ArrayLike {
  [index: number]: string
  length: number
}

const arr: ArrayLike = {
  0: "a",
  1: "b",
  length: 2
}

console.log(arr[0])  // "a"
console.log(arr["0"])  // "a"(相同)

类型安全

索引签名会放宽类型检查:

interface Loose {
  [key: string]: any
}

const obj: Loose = {
  name: "张三",
  age: 25,
  doSomething: () => {}
}

obj.anything  // any 类型

更严格的类型

interface Strict {
  [key: string]: string | number | undefined
  name: string
  age: number
}

const obj: Strict = {
  name: "张三",
  age: 25
}

obj.unknown  // string | number | undefined