177. 断言数组索引合法性

极难0

有时候我们希望使用传统的 for 循环和索引来遍历数组,但在这种情况下,TypeScript 并不会检查我们是否在真实的索引上访问数组元素(即没有超过数组的长度),也不会检查我们是否使用了一个任意的数字作为索引,或者来自其他数组的索引(比如在嵌套循环、遍历矩阵或图时):

const matrix = [
  [3, 4],
  [5, 6],
  [7, 8],
]

// 在没有启用 noUncheckedIndexedAccess 选项时,这个例子不会产生类型错误。
for (let i = 0; i < matrix.length; i += 1) {
  const columns: number[] = matrix[i]

  for (let j = 0; j < columns.length; j += 1) {
    const current: number = columns[i] // 错误!应该是 j 不是 i

    console.log(current.toFixed()) // TypeError: Cannot read property 'toFixed' of undefined
  }
}

你可以启用 noUncheckedIndexedAccess 选项(在 tsconfig.json 中),但每次访问数组元素时,你都需要检查该元素是否存在(即编写 if 语句),这会显得有些冗长且不方便,特别是对于这种 for 循环遍历的情况,我们可以确信索引不会超过数组的长度:

const numbers = [5, 7]

for (let i = 0; i < numbers.length; i += 1) {
  const current = numbers[i]

  if (current !== undefined) {
    console.log(current.toFixed())
  }
}

编写一个 assert 函数 assertArrayIndex(array, key),它可以应用于任何 array(并且需要一个唯一的字符串 key,用于在类型层面区分不同的数组),这样就可以通过特殊的泛型类型 Index<typeof array> 来访问数组元素,确保数组元素是存在的(这个功能要求启用 noUncheckedIndexedAccess 选项):

const numbers = [5, 7]

assertArrayIndex(numbers, 'numbers')

for (let i = 0 as Index<typeof numbers>; i < numbers.length; i += 1) {
  console.log(numbers[i].toFixed())
}

通过这样的索引访问时,必须保证数组中有该元素,而通过其他任何索引进行访问时,则没有这种保证(元素可能不存在):

const matrix = [
  [3, 4],
  [5, 6],
  [7, 8],
]

assertArrayIndex(matrix, 'rows')

let sum = 0

for (let i = 0 as Index<typeof matrix>; i < matrix.length; i += 1) {
    const columns: number[] = matrix[i]

    // @ts-expect-error: number | undefined 不能赋值给 number
    const x: number[] = matrix[0]

    assertArrayIndex(columns, 'columns')

    for (let j = 0 as Index<typeof columns>; j < columns.length; j += 1) {
        sum += columns[j]

        // @ts-expect-error: number | undefined 不能赋值给 number
        const y: number = columns[i]

        // @ts-expect-error: number | undefined 不能赋值给 number
        const z: number = columns[0]

        // @ts-expect-error: number[] | undefined 不能赋值给 number[]
        const u: number[] = matrix[j]
    }
}

assertArrayIndex 函数不能用于元组(因为元组的元素访问已经在类型上有良好的检查):

const tuple = [5, 7] as const

// @ts-expect-error
assertArrayIndex(tuple, 'tuple')
评论(0)
题库

TypeScript

加载中...