IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> TypeScript 杂记十一 《Assert Array Index》 -> 正文阅读

[JavaScript知识库]TypeScript 杂记十一 《Assert Array Index》

TypeScript 杂记十一 《Assert Array Index》

Assert Array Index

简介

  • 在获取数组中某一项的值时候,如下:
const numbers = [5, 7];
console.log(numbers[1].toFixed());
  • TS 不会以任何方式检查我们正在访问数组的实际索引处的元素,如下使用会报错
const numbers = [5, 7];
// 校验不报错,但是运行报错
console.log(numbers[100].toFixed());
  • TS4.1 开始新加了一个配置项 noUncheckedIndexedAccess ,开启之后就会去推断对应数组实际索引的选项:
const numbers = [5, 7];
// 报错:对象可能为“未定义”。ts(2532)
console.log(numbers[1].toFixed());
// 正确
console.log(numbers[1]?.toFixed());
  • 但是我们实际上在循环中是这样使用的,如下:我们可以很确定的知道他不会超出,也不会报错
const numbers = [5, 7];
for (let i = 0; i < numbers.length; i += 1) {
  // 报错:对象可能为“未定义”。ts(2532)
  console.log(numbers[i].toFixed());
  // 正确
  console.log(numbers[i]?.toFixed());
}
  • 因此我们需要定义一个 assertArrayIndex(array, key) 断言函数用来包装我们的数组,同时通过 Index<typeof array> 来定义数组下标,使其可以使用。如下:(下一节我们来讲第二个参数的意义和作用)
const numbers = [5, 7];
assertArrayIndex(numbers, "numbers");
for (let i = 0 as Index<typeof numbers>; i < numbers.length; i += 1) {
  // 正确,允许使用 i
  console.log(numbers[i].toFixed());

  // 报错:对象可能为“未定义”。ts(2532)
  console.log(numbers[0].toFixed());
  // 正确
  console.log(numbers[0]?.toFixed());
}

思路

  • 我们先看一下下边的例子:
const numbers1 = [5, 7];
// 报错:对象可能为“未定义”。ts(2532)
numbers1[0].toFixed();

const numbers2 = [5, 7] as number[] & { 0: number };
// 正常
numbers2[0].toFixed();

const numbers3 = [5, 7] as number[] & { aaaa: number };
// 正常
numbers3.aaaa.toFixed();
  • 通过上边的例子我们可以知道,我们给原本的数组添加一个 { key: number } ,这样我们就可以直接使用 array[key] 去使用
  • 因为数组的下标是一个数字,所以我们使用一个数字作为 key
  • 最终效果如下:
  • assertArrayIndex(array, key) 生成 { 100: number }
  • Index<typeof array> 获取 100
const numbers = [5, 7] as number[] & { 100: number };
for (let i = 0 as 100; i < numbers.length; i += 1) {
  console.log(numbers[i].toFixed());
}
  • 为什么 assertArrayIndex 需要第二个参数
  • 我们需要根据第二个参数生成这个数字,这个数字要保证唯一。为什么要保证唯一?
  • 参考下例:
const matrix = [
  [3, 4],
  [5, 6],
  [7, 8],
];
assertArrayIndex(matrix, "test");
let sum = 0;
for (let i = 0 as Index<typeof matrix>; i < matrix.length; i += 1) {
  const columns: number[] = matrix[i];
  assertArrayIndex(columns, "test");
  for (let j = 0 as Index<typeof columns>; j < columns.length; j += 1) {
    // 如果 i 和 j 重复,那么下边的使用不会报错
    // 但是实际运行则会出现问题
    const y: number = columns[i];
    const u: number[] = matrix[j];
  }
}
  • 我们先去实现生成唯一值的函数
    • 大致如下:不过有一个缺点,目前采用的加法,aabbbbaa 结果一致。基于目前 TS 的机制,没有办法完全实现实现不同的字符串生成不同的 key。(至少我是没有想到解决的办法,无论加法、减法还是乘法都会出现)
    • 其实我们只要保证上述情况内唯一就行,所以即使重复也影响不大,只要我们保证在嵌套循环内使用不同的具有真实含义的单词就行
type HashMapHelper<
  T extends number,
  R extends unknown[] = []
> = R["length"] extends T ? R : HashMapHelper<T, [...R, unknown]>;

type HashMap = {
  "0": HashMapHelper<0>;
  "1": HashMapHelper<1>;
  "2": HashMapHelper<2>;
  "3": HashMapHelper<3>;
  "4": HashMapHelper<4>;
  "5": HashMapHelper<5>;
  "6": HashMapHelper<6>;
  "7": HashMapHelper<7>;
  "8": HashMapHelper<8>;
  "9": HashMapHelper<9>;
  a: HashMapHelper<1>;
  b: HashMapHelper<2>;
  c: HashMapHelper<3>;
  d: HashMapHelper<4>;
  e: HashMapHelper<5>;
  f: HashMapHelper<6>;
  g: HashMapHelper<7>;
  h: HashMapHelper<8>;
  i: HashMapHelper<9>;
  j: HashMapHelper<10>;
  k: HashMapHelper<11>;
  l: HashMapHelper<12>;
  m: HashMapHelper<13>;
  n: HashMapHelper<14>;
  o: HashMapHelper<15>;
  p: HashMapHelper<16>;
  q: HashMapHelper<17>;
  r: HashMapHelper<18>;
  s: HashMapHelper<19>;
  t: HashMapHelper<20>;
  u: HashMapHelper<21>;
  v: HashMapHelper<22>;
  w: HashMapHelper<23>;
  x: HashMapHelper<24>;
  y: HashMapHelper<25>;
  z: HashMapHelper<26>;
};

type Hash<
  T extends string,
  RR extends unknown[] = []
> = T extends `${infer L}${infer R}`
  ? Hash<R, [...RR, ...HashMap[keyof HashMap & L]]>
  : RR["length"];
  • 我们使用断言函数给原本的类型加上这个 { key: number }
function assertArrayIndex<A extends readonly unknown[], K extends string>(
  array: A,
  key: K
): asserts array is A & { readonly [key in Hash<K>]: A[number] } {}
  • 不能在元组上调用该函数,我们先看一个示例
const A = [1, 2, 3];
type AA = typeof A; // number[]
type AAA = AA["length"]; // number
const B = [1, 2, 3] as const;
type BB = typeof B; // readonly [1, 2, 3]
type BBB = BB["length"]; // 3
  • 根据上述的情况,我们来解决元组的问题
function assertArrayIndex<A extends readonly unknown[], K extends string>(
  array: number extends A["length"] ? A : never,
  key: K
): asserts array is number extends A["length"]
  ? A & { readonly [key in Hash<K>]: A[number] }
  : never {}
  • 之前我们生成的 key 要求是 0-9a-z 的字母组成的单词,且必填,我们来实现这个
type IsKeyHelper<K extends string> = K extends `${infer L}${infer R}`
  ? L extends keyof HashMap
    ? IsKeyHelper<R>
    : false
  : true;

type IsKey<K extends string> = K extends "" ? false : IsKeyHelper<K>;

function assertArrayIndex<A extends readonly unknown[], K extends string>(
  array: number extends A["length"] ? A : never,
  key: IsKey<K> extends true ? K : never
): asserts array is number extends A["length"]
  ? A & { readonly [key in Hash<K>]: A[number] }
  : never {}
  • 实现 Index,因为 Index 需要获取到对应的数字,因此我们需要通过一个约定的值去获取,如下:采用 symbol
declare const KEY: unique symbol;
function assertArrayIndex<A extends readonly unknown[], K extends string>(
  array: number extends A["length"] ? A : never,
  key: IsKey<K> extends true ? K : never
): asserts array is number extends A["length"]
  ? A & { readonly [KEY]: Hash<K> } & {
      readonly [key in Hash<K>]: A[number];
    }
  : never {}
type Index<Array extends { readonly [KEY]: number }> = Array[typeof KEY];

完整示例

type HashMapHelper<
  T extends number,
  R extends unknown[] = []
> = R["length"] extends T ? R : HashMapHelper<T, [...R, unknown]>;

type HashMap = {
  "0": HashMapHelper<0>;
  "1": HashMapHelper<1>;
  "2": HashMapHelper<2>;
  "3": HashMapHelper<3>;
  "4": HashMapHelper<4>;
  "5": HashMapHelper<5>;
  "6": HashMapHelper<6>;
  "7": HashMapHelper<7>;
  "8": HashMapHelper<8>;
  "9": HashMapHelper<9>;
  a: HashMapHelper<1>;
  b: HashMapHelper<2>;
  c: HashMapHelper<3>;
  d: HashMapHelper<4>;
  e: HashMapHelper<5>;
  f: HashMapHelper<6>;
  g: HashMapHelper<7>;
  h: HashMapHelper<8>;
  i: HashMapHelper<9>;
  j: HashMapHelper<10>;
  k: HashMapHelper<11>;
  l: HashMapHelper<12>;
  m: HashMapHelper<13>;
  n: HashMapHelper<14>;
  o: HashMapHelper<15>;
  p: HashMapHelper<16>;
  q: HashMapHelper<17>;
  r: HashMapHelper<18>;
  s: HashMapHelper<19>;
  t: HashMapHelper<20>;
  u: HashMapHelper<21>;
  v: HashMapHelper<22>;
  w: HashMapHelper<23>;
  x: HashMapHelper<24>;
  y: HashMapHelper<25>;
  z: HashMapHelper<26>;
};

type Hash<
  T extends string,
  RR extends unknown[] = []
> = T extends `${infer L}${infer R}`
  ? Hash<R, [...RR, ...HashMap[keyof HashMap & L]]>
  : RR["length"];

type IsKeyHelper<K extends string> = K extends `${infer L}${infer R}`
  ? L extends keyof HashMap
    ? IsKeyHelper<R>
    : false
  : true;

type IsKey<K extends string> = K extends "" ? false : IsKeyHelper<K>;

declare const KEY: unique symbol;

function assertArrayIndex<A extends readonly unknown[], K extends string>(
  array: number extends A["length"] ? A : never,
  key: IsKey<K> extends true ? K : never
): asserts array is number extends A["length"]
  ? A & { readonly [KEY]: Hash<K> } & {
      readonly [key in Hash<K>]: A[number];
    }
  : never {}

type Index<Array extends { readonly [KEY]: number }> = Array[typeof KEY];
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 12:53:24  更:2022-03-06 12:54:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:08:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码