3

仅知道键和值的类型,如何定义 TS 对象类型?

 2 years ago
source link: https://www.fly63.com/article/detial/11353
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
更新日期: 2022-04-18阅读量: 14标签: 类型分享

扫一扫分享

在学习 TS 的过程中,你遇到过类似的错误么?

let user = {}
user.id = "TS001" // 类型“{}”上不存在属性“id”。
user.name = "阿宝哥" // 类型“{}”上不存在属性“name”。

除了 any “大招” 之外,你还知道几种解决方案?阅读完本文,你将会找到一些答案。

这是小王他的月薪是包含了基本工资和月度奖金,这是小郭他的月薪只包含合同工资。

const Wang = {
  baseSalary: 10000, // 基本工资
  monthlyBonus: 2000 // 月度奖金
};
const Guo = {
  contractSalary: 15000 // 合同工资
}

这里我们可以实现一个 calculateSalary 函数来计算他们的薪资,计算逻辑实现起来很简单。但问题来了,在 TS 中如何定义该函数参数的类型呢?

function calculateSalary(salaryObject: ???) {
  let total = 0;
  for (const name in salaryObject) {
    total += salaryObject[name];
  }
  return total;
}

给你 3 秒钟的时间思考一下。你想到答案了么?其中一种方案是使用索引签名。当我们仅知道对象键和值的类型时,就可以使用索引签名来定义该对象的类型。

这是索引签名的语法

{ [key: KeyType]: ValueType }

其中 Key 的类型,只能是 string,number,symbol 或模版字面量类型,而值的类型可以是任意类型。

interface Dictionary {
  [key: boolean]: string;
}

其中模版字面量类型是 TypeScript 4.1 版本引入的新类型,结合索引签名我们可以定义更强大的类型:

interface PropChangeHandler {
  [key: `${string}Changed`]: () => void;
}
let handlers: PropChangeHandler = {
  idChanged: () => {}, // Ok
  nameChanged: () => {}, // Ok
  ageChange: () => {} // Error
};

了解索引签名的语法之后,我们就可以轻松地定义出 salaryObject 参数的类型:

function calculateSalary(salaryObject: { [key: string]: number }) {
  let total = 0;
  for (const name in salaryObject) {
    total += salaryObject[name];
  }
  return total;
}

有些时候,在定义对象类型时,会含有一些已知和未知的键,这时我们可以结合索引签名来定义该对象的类型:

interface Options {
  [key: string]: string | number | boolean;
  timeout: number; // 已知的键
}
const options: Options = {
  timeout: 1000,
  errorMessage: 'The request timed out!',
  isSuccess: false
};

在使用索引签名时,你可能会遇到这些困惑:

interface NumbersNames {
  [key: string]: string
}
const names: NumbersNames = {
  '1': 'one',
  '2': 'two',
  '3': 'three'
};
const value1 = names['1'] // Ok
const value2 = names[1] // Ok

type N0 = keyof NumbersNames // string | number
  • 为什么可以通过字符串 1 和数字 1 来访问对应的属性值。
  • 为什么 keyof NumbersNames 返回的 string 和 number 类型组成的联合类型。

这是因为当用作属性访问器中的键时,JavaScript 会隐式地将数字强制转换为字符串,TypeScript 也会执行这种转换。

除了使用索引签名之外,我们还可以使用 TS 内置的工具类型 Record 类型来定义 calculateSalary 函数的参数类型:

type Record<K extends keyof any, T> = {
  [P in K]: T;
};
function calculateSalary(salaryObject: Record<string, number>) {
  let total = 0;
  for (const name in salaryObject) {
    total += salaryObject[name];
  }
  return total;
}

那么索引签名和 Record 工具类型有什么区别呢?在一些场合下,它们都能定义出期望的类型。

const user1: Record<string, string> = { name: "阿宝哥" }; // Ok
const user2: { [key: string]: string } = { name: "阿宝哥" }; // Ok

对于索引签名来说,其键的类型,只能是 string,number,symbol 或模版字面量类型。而 Record 工具类型,键的类型可以是字面量类型或字面量类型组成的联合类型:

type User1 = {
  [key: "id"]: string; // Error
};
type User2 = {
  [key: "id" | "name"]: string; // Error
};
type User3 = Record<"id", string>; // Ok
type User4 = Record<"id" | "name", string>; // Ok
const user: User4 = {
  id: "TS001",
  name: "阿宝哥",
};

看完以上内容,你学会索引签名和 Record 工具类型了么?

来源: 全栈修仙之路

链接: https://www.fly63.com/article/detial/11353


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK