对象扩展运算符和 rest 运算符及 keyof 和查找类型

更新日期: 2019-11-27阅读: 2.2k标签: 运算符

TypeScript 2.1 增加了对 对象扩展运算和 rest 属性提案的支持,该提案在 ES2018 中标准化。可以以类型安全的方式使用 rest 和 spread 属性。


对象 rest 属性

假设已经定义了一个具有三个属性的简单字面量对象

const marius = {
  name: "Marius Schulz",
  website: "https://mariusschulz.com/",
  twitterHandle: "@mariusschulz"
};

使用 ES6 解构语法,可以创建几个局部变量来保存相应属性的值。TypeScript 将正确地推断每个变量的类型:

const { name, website, twitterHandle } = marius;

name;          // Type string
website;       // Type string
twitterHandle; // Type string

这些都是正确的,但这到现在也啥新鲜的。除了提取感兴趣的一组属性之外,还可以使用...语法将所有剩余的属性收集到rest元素中:

const { twitterHandle, ...rest } = marius;

twitterHandle; // Type string
rest; // Type { name: string; website: string; }

TypeScript 会为得到结果的局部变量确定正确的类型。虽然 twitterHandle 变量是一个普通的字符串,但 rest 变量是一个对象,其中包含剩余两个未被解构的属性。


对象扩展属性

假设咱们希望使用 fetch() api 发出 HTTP 请求。它接受两个参数:一个 URL 和一个 options 对象,options 包含请求的任何自定义设置。

在应用程序中,可以封装对fetch()的调用,并提供默认选项和覆盖给定请求的特定设置。这些配置项类似如下:

const defaultOptions = {
  method: "GET",
  credentials: "same-origin"
};

const requestOptions = {
  method: "POST",
  redirect: "follow"
};

使用对象扩展,可以将两个对象合并成一个新对象,然后传递给 fetch() 方法

// Type { method: string; redirect: string; credentials: string; }
const options = {
  ...defaultOptions,
  ...requestOptions
};

对象扩展属性创建一个新对象,复制 defaultOptions 中的所有属性值,然后按照从左到右的顺序复制requestOptions中的所有属性值,最后得到的结果如下:

console.log(options);
// {
//   method: "POST",
//   credentials: "same-origin",
//   redirect: "follow"
// }

请注意,分配顺序很重要。如果一个属性同时出现在两个对象中,则后分配的会替换前面的。

当然,TypeScript 理解这种顺序。因此,如果多个扩展对象使用相同的键定义一个属性,那么结果对象中该属性的类型将是最后一次赋值的属性类型,因为它覆盖了先前赋值的属性:

const obj1 = { prop: 42 };
const obj2 = { prop: "Hello World" };

const result1 = { ...obj1, ...obj2 }; // Type { prop: string }
const result2 = { ...obj2, ...obj1 }; // Type { prop: number }


制作对象的浅拷贝

对象扩展可用于创建对象的浅拷贝。假设咱希望通过创建一个新对象并复制所有属性来从现有todo项创建一个新todo项,使用对象就可以轻松做到:

const todo = {
  text: "Water the flowers",
  completed: false,
  tags: ["garden"]
};

const shallowCopy = { ...todo };

实际上,你会得到一个新对象,所有的属性值都被复制:

console.log(todo === shallowCopy);
// false

console.log(shallowCopy);
// {
//   text: "Water the flowers",
//   completed: false,
//   tags: ["garden"]
// }

现在可以修改text属性,但不会修改原始的todo项:

hallowCopy.text = "Mow the lawn";

console.log(shallowCopy);
// {
//   text: "Mow the lawn",
//   completed: false,
//   tags: ["garden"]
// }

console.log(todo);
// {
//   text: "Water the flowers",
//   completed: false,
//   tags: ["garden"]
// }

但是,新的todo项引用与第一个相同的 tags 数组。由于是浅拷贝,改变数组将影响这两个todo

shallowCopy.tags.push("weekend");

console.log(shallowCopy);
// {
//   text: "Mow the lawn",
//   completed: false,
//   tags: ["garden", "weekend"]
// }

console.log(todo);
// {
//   text: "Water the flowers",
//   completed: false,
//   tags: ["garden", "weekend"]
// }

如果想创建一个序列化对象的深拷贝,可以考虑使用 JSON.parse(JSON.stringify(obj)) 或其他方法,如 object.assign()。对象扩展仅拷贝属性值,如果一个值是对另一个对象的引用,则可能导致意外的行为。


keyof 和查找类型

JS 是一种高度动态的语言。在静态类型系统中捕获某些操作的语义有时会很棘手。以一个简单的 prop 函数为例:

function prop(obj, key) {
  return obj[key];
}

它接受一个对象和一个键,并返回相应属性的值。一个对象的不同属性可以有完全不同的类型,咱们甚至不知道 obj 是什么样子的。

那么如何在 TypeScript 中编写这个函数呢?先尝试一下:


有了这两个类型注释,obj 必须是对象,key 必须是字符串。咱们现在已经限制了两个参数的可能值集。然而,TS 仍然推断返回类型为 any:

const todo = {
  id: 1,
  text: "Buy milk",
  due: new Date(2016, 11, 31)
};

const id = prop(todo, "id");     // any
const text = prop(todo, "text"); // any
const due = prop(todo, "due");   // any

如果没有更进一步的信息,TypeScript 就不知道将为 key 参数传递哪个值,所以它不能推断出prop函数的更具体的返回类型。咱们需要提供更多的类型信息来实现这一点。


keyof 操作符号

在 JS 中属性名称作为参数的 API 是相当普遍的,但是到目前为止还没有表达在那些 API 中出现的类型关系。

TypeScript 2.1 新增加 keyof 操作符。输入索引类型查询或 keyof,索引类型查询keyof T产生的类型是 T的属性名称。假设咱们已经定义了以下 Todo 接口:

interface Todo {
  id: number;
  text: string;
  due: Date;
}

各位可以将 keyof 操作符应用于 Todo 类型,以获得其所有属性键的类型,该类型是字符串字面量类型的联合

type TodoKeys = keyof Todo; // "id" | "text" | "due"

当然,各位也可以手动写出联合类型 "id" | "text" | "due",而不是使用 keyof,但是这样做很麻烦,容易出错,而且维护起来很麻烦。而且,它应该是特定于Todo类型的解决方案,而不是通用的解决方案。


索引类型查询

有了 keyof,咱们现在可以改进 prop 函数的类型注解。我们不再希望接受任意字符串作为 key 参数。相反,咱们要求参数 key 实际存在于传入的对象的类型上

function prop <T, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}

TypeScript 现在以推断 prop 函数的返回类型为 T[K],这个就是所谓的 索引类型查询 或 查找类型。它表示类型 T 的属性 K 的类型。如果现在通过 prop 方法访问下面 todo 的三个属性,那么每个属性都有正确的类型:

const todo = {
  id: 1,
  text: "Buy milk",
  due: new Date(2016, 11, 31)
};

const id = prop(todo, "id");     // number
const text = prop(todo, "text"); // string
const due = prop(todo, "due");   // Date

现在,如果传递一个 todo 对象上不存在的键会发生什么


编译器会报错,这很好,它阻止咱们试图读取一个不存在的属性。

另一个真实的示例,请查看与TypeScript编译器一起发布的 lib.es2017.object.d.ts 类型声明文件中Object.entries()方法:

interface ObjectConstructor {
  // ...
  entries<T extends { [key: string]: any }, K extends keyof T>(o: T): [keyof T, T[K]][];
  // ...
}

entries 方法返回一个元组数组,每个元组包含一个属性键和相应的值。不可否认,在返回类型中有大量的方括号,但是我们一直在寻找类型安全性。

原文:https://mariusschulz.com/

链接: https://fly63.com/article/detial/6671

理解强大的 ES6 「 ... 」运算符

... 运算符, 是ES6里一个新引入的运算法, 也叫展开/收集 运算符, 我们每天都要和它打交道。这篇文章,我就带你系统的回顾下这个运算符, 介绍一些基础和进阶的用法。

详解Js加法运算符

JavaScript是一门了不起的语言。我喜欢它的灵活性:只需以你喜欢的方式做事:更改变量类型,动态的向对象添加方法或属性,对不同的变量类型使用运算符等等。然而动态是要付出代价的,开发人员需要知道怎样处理对于不同操作符的类型转换

JS 中使用扩展运算符的10种方法

我们可以使用展开操作符复制数组,不过要注意的是这是一个浅拷贝。这样我们就可以复制一个基本的数组,注意,它不适用于多级数组或带有日期或函数的数组。

JavaScript中强大的操作符&运算符总汇

JS 里的操作符大家每天都在使用,还有一些 ES2020、ES2021 新加的实用操作符,这些共同构成了 JS 灵活的语法生态

4个你从未听说过的强大的 JavaScript 运算符

你有没有花一个下午的时间浏览过 Mozilla 文档?如果你有,你会很清楚网上有很多关于 JavaScript 的信息。这使得人们很容易忽略一些不同寻常的 JavaScript 操作符。

ES6 扩展运算符 三点...

扩展运算符( spread )是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。array.push(...items)和add(...numbers)这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。

JavaScript展开运算符和剩余运算符的区别

JavaScript使用符号三个点(...)作为剩余运算符和展开运算符,不过这两个运算符是有区别的。最主要的区别就是,剩余运算符将用户提供的某些特定值的其余部分放入JavaScript数组中

JavaScript 代码的一个新运算符:管道运算符 |>

首先,如果是嵌套写法的话,简单的嵌套还好,但是当嵌套变得很深的时候就有点难以阅读了。嵌套的执行流程是从右到左移动的,而不是我们正常阅读代码从左到右的方向

JavaScript运算符单竖杠|

很多人都对双竖杠||非常熟悉,因为这个经常在项目中经常会用到。单竖杠|,却很少在项目开发中使用到。|是位运算符,||是逻辑运算符。平常,经常使用以下这个几个方法对数字进行处理。

Js ES6扩展运算符(spread)和剩余运算符(rest)

Spread 和 Rest 是 ES6 Javascript 提供的两个功能,分别主要用于解构和函数参数处理。Spread 从可迭代对象(如数组、字符串或对象)中获取元素并将它们分散到各个部分。这就像将一副纸牌铺在桌子上一样。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!