Typescript - 索引签名

目录

  • 1,什么是索引签名
    • 1,js 中使用对象的属性
    • 2,ts 中的索引签名
    • 3,扩展索引签名定义的类型
  • 2,与 Record 对比
  • 3,遇到的问题
    • 1,索引 key 的类型问题,`keyof`
    • 2,索引 key 的类型问题2:受具体定义的影响

1,什么是索引签名

1,js 中使用对象的属性

在 js 中,访问对象的 key

let user = {
  name: "下雪天的夏风",
  19: "19value",
};

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

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

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

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

其他的类型,来看下结果

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 也可以是模板字面量
interface HandleEvents {
  [key: `${string}Changed`]: () => void;
}
  1. 对象如果想作为索引,必须显示的调用.toString() 方法
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 的类型下,来统一定义对象的类型

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

举例:

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

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

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

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

  1. 可同时指定已确定的类型
// 必须包含 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 的子级。

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

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

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

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

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

type Sign4 = {
  [key: string]: string | number;
} | { list: string[] }

2,与 Record 对比

先看下 Record 的定义

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

Typescript - 索引签名_第1张图片

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

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

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

type Type1 = Record<'a | b | c', string>

3,遇到的问题

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

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:受具体定义的影响

这是我的提问

<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 的类型不一致:

Typescript - 索引签名_第2张图片
Typescript - 索引签名_第3张图片

原因:

  1. Object.entries 的类型声明
// 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 的类型声明
// 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 即可

interface NumberDictionary {
  [index: string]: string;
}
// ↓↓
type NumberDictionary = Record<string, string>

以上。


参考

索引签名-参考1

索引签名-参考2

索引签名-参考3

你可能感兴趣的:(typescript,问题解决,typescript,javascript,前端)