Typescript - 索引签名

目录

  • 1,什么是索引签名
  • [2,与 Record 对比](#2,与 Record 对比)
  • 3,遇到的问题
    • [1,索引 key 的类型问题,`keyof`](#1,索引 key 的类型问题,keyof)
    • [2,索引 key 的类型问题2:受具体定义的影响](#2,索引 key 的类型问题2:受具体定义的影响)

1,什么是索引签名

1,js 中使用对象的属性

在 js 中,访问对象的 key

js 复制代码
let user = {
  name: "下雪天的夏风",
  19: "19value",
};

console.log(user.name);
console.log(user[19]);
user.age = 18

如果key对象 ,会默认执行它的 toString() 方法。

js 复制代码
let user = {
  name: "下雪天的夏风",
};
const obj = {
  toString() {
    console.log("be string");
    return "_obj";
  },
};

user[obj] = "关注一波";
console.log(user);
// be string
// { name: '下雪天的夏风', _obj: '关注一波' }

其他的类型,来看下结果

js 复制代码
let user = {};

const obj1 = {
  toString() {
    console.log("be string");
    return "_obj";
  },
};
const obj2 = {};
function foo() {}
const sym = Symbol();

user[obj1] = "obj1 的 value";
user[obj2] = "obj2 的 value";
user[foo] = "foo 的 value";
user[sym] = "sym 的 value";
console.log(user); // { name: '下雪天的夏风', _obj: '关注一波' }

/* 
be string
{
  _obj: 'obj1 的 value',
  '[object Object]': 'obj2 的 value',
  'function foo() {}': 'foo 的 value',
  [Symbol()]: 'sym 的 value'
}
*/

可以看到,对象默认的 toString() 方法执行结果比较特殊。

2,ts 中的索引签名

可以简单的理解:key 就是索引

基于在 js 中对象和函数等作为索引 产生的特殊行为,ts 做了进一步的约束。

索引类型只能是:string | number | symbol,而value 可以是任意类型

  1. 其中string 也可以是模板字面量
ts 复制代码
interface HandleEvents {
  [key: `${string}Changed`]: () => void;
}
  1. 对象如果想作为索引,必须显示的调用.toString() 方法
ts 复制代码
let user: any = {};

const obj1 = {
  toString() {
    return "_obj1";
  },
};

// Type '{ toString(): string; }' cannot be used as an index type.ts(2538)
user[obj1] = "obj1 的 value";
user[obj1.toString()] = "ok";

索引签名 就是在约束了索引(key)类型的基础上,统一定义了对象的 keyvalue 的类型

换句话说,索引签名可以在只知道keyvalue 的类型下,来统一定义对象的类型

规定:当声明一个索引签名后,所有成员都必须符合索引签名:

举例:

ts 复制代码
interface Sign1 {
  // key 只是占位符,随便什么单词都可以
  [key: string]: string;
}

type Sign2 = {
  [index: number]: string | number;
};

const foo: {
  [aaa: string]: { message: string }; // value 只能是1个对象,并且只有1个属性 message
} = {};

3,扩展索引签名定义的类型

  1. 可同时指定已确定的类型
ts 复制代码
// 必须包含 x 属性
interface Sign1 {
  [key: string]: number;
  x: number;
}

interface Sign2 {
  [key: string]: number;
  y: string; // Error: 属性 y 的类型只能是'number'.ts(2411)
}
  1. 多个索引签名

在多个索引签名存在时,string 类型的索引最严格,书写时应该包含所有的 value 类型(假设为 All)。

其他类型的索引,对应的 value 类型只能是 All 的子级。

ts 复制代码
interface Sign3 {
  [key: string]: string | number | boolean; // 必须包括所用成员类型
  [index: symbol]: string;
  [index2: number]: number;
}
  1. 扩展

即便使用多个索引签名,也有覆盖不到的情况。

比如定义的对象中有一个list:string[] 字段,其他字段都不是string[] 类型。如果按照下面的写法,任何字段都可以是string[] 类型

ts 复制代码
interface Sign4 {
  [key: string]: string | number | string[]; // 必须包括所用成员类型
}

我们可以用索引签名+联合类型。

ts 复制代码
type Sign4 = {
  [key: string]: string | number;
} | { list: string[] }

2,与 Record 对比

先看下 Record 的定义

ts 复制代码
// node_modules\typescript\lib\lib.es5.d.ts
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

鼠标放上去之后,会发现其实 keyvalue的类型的规则和索引签名的一致,也是:string | number | symbol,而value 可以是任意类型。

所以,二者并没有多大的区别,只是使用上略有不同而已。目前只发现的一个区别:

Record 的 key 可以为具体的类型 ,一般用于创建具有特定键值对的对象类型。

ts 复制代码
type Type1 = Record<'a | b | c', string>

3,遇到的问题

1,索引 key 的类型问题,keyof

ts 复制代码
type Sign = {
  [key: string]: boolean;
}
// string | number
type keys = keyof Sign

const obj: Sign = {};
obj[2] = 123; // ok
obj["3"] = 123;

原因是:当数字作为 key 时,js 会隐式地将数字强制转换为字符串,ts 也会执行这种转换。

2,索引 key 的类型问题2:受具体定义的影响

这是我的提问

html 复制代码
<script setup lang="ts">
interface NumberDictionary {
  [index: string]: string;
}

const formInfo: NumberDictionary = {
  name: "john",
  want: "eat",
};

Object.entries(formInfo).forEach(([key, value]) => {
  console.log(key, value);
});
</script>
<template>
  <form>
    <input v-for="(value, key) in formInfo" :name="key" :value="value" :key="key" type="text" />
  </form>
</template>

可以看到2个地方 key 的类型不一致:


原因:

  1. Object.entries 的类型声明
ts 复制代码
// node_modules\typescript\lib\lib.es2017.object.d.ts
entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];

可以看到接受的就是 string 类型,相当于在 forEach 使用时,key 已经被断言为 string 类型了。

  1. v-for 的类型声明
ts 复制代码
// node_modules\@vue\runtime-core\dist\runtime-core.d.ts  第 1602 行
/**
 * v-for object
 */
export declare function renderList<T>(
  source: T, 
  renderItem: <K extends keyof T>(
    value: T[K], 
    key: K, 
    index: number
  ) => VNodeChild)
: VNodeChild[];

可以看到 K extends keyof T ,很熟悉!这就是上面遇到的第1个问题。

对上面这个例子来说,v-forkey 的类型就是 string | number

知道原因了,解决方法就有了,用Record 即可

ts 复制代码
interface NumberDictionary {
  [index: string]: string;
}
// ↓↓
type NumberDictionary = Record<string, string>

以上。


参考

索引签名-参考1

索引签名-参考2

索引签名-参考3

相关推荐
货拉拉技术3 分钟前
货拉拉开源:鸿蒙路由 TheRouter
android·前端·harmonyos
中杯可乐多加冰5 分钟前
工业4.0数字孪生新引擎:星图云开发者平台全景评测
前端·低代码·掘金·金石计划
云边小卖铺.12 分钟前
运行vue项目报错 errors and 0 warnings potentially fixable with the `--fix` option.
前端·javascript·vue.js
我是若尘13 分钟前
前端处理大量并发请求的实用技巧
前端
Lstmxx15 分钟前
Electron:使用数据流的形式加载本地视频
前端·electron·node.js
JunjunZ22 分钟前
unibest框架开发uniapp项目:兼容小程序问题
前端·vue.js
lyc23333324 分钟前
鸿蒙Next应用启动框架AppStartup:流程管理与性能优化🚀
前端
Data_Adventure24 分钟前
Vue 3 作用域插槽:原理剖析与高级应用
前端·vue.js
Splendid28 分钟前
Geneformer:基于Transformer的基因表达预测深度学习模型
javascript·算法
EndingCoder29 分钟前
React Native 开发环境搭建(全平台详解)
javascript·react native·react.js·前端框架