[typescript evolution history — 6] object extension operators and rest operators and keyof and lookup types

Time:2019-12-15

By Marius Schulz
Translator: front-end wit
Source: Marius Schulz


Hot wire: Alibaba cloud server 2019hi group promotion is in hot progress, new and old users can participate, 2-core 1g cloud server only needs79 yuan, 1-core 2G Hong Kong server only needs719 yuan /3, for more server configuration and price, please pay attention to: Hi group, or click here to learn about the “20% discount event of cloud top hits”. At the same time, it is recommended to get the alicloud 2000 voucher before purchasing alicloud related products.

Tencent cloud is currently doing activities, with 100 cloud products as low as 10% discount. You can click this content or link to participate


In order to ensure readability, this paper adopts free translation instead of literal translation.

Typescript 2.1 adds support for object extension operations and rest attribute proposals, which are standardized in es2018. Can be used in a type safe wayrestandspreadAttribute.

Object rest property

Suppose you have defined a simple literal object with three attributes

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

Using the ES6 deconstruction syntax, you can create several local variables to hold the values of the corresponding attributes. Typescript correctly infers the type of each variable:

const { name, website, twitterHandle } = marius;

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

These are all right, but they are nothing new until now. In addition to extracting a set of attributes of interest, you can also use the...Syntax collects all remaining properties intorestElement:

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

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

Typescript determines the correct type for the local variable that gets the result. althoughtwitterHandleVariable is a normal string, butrestA variable is an object that contains the remaining two properties that have not been deconstructed.

Object extended properties

Suppose we want to usefetch()The API makes an HTTP request. It takes two parameters: oneURLAnd oneoptionsObject,optionsContains any custom settings requested.

In an application, you can encapsulate a pair offetch()And provide default options and override specific settings for a given request. These configuration items are similar to the following:

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

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

With object extension, you can merge two objects into a new object and pass it tofetch()Method

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

Object extension property create a new object, copydefaultOptionsAll property values in and then copied in left to right orderrequestOptionsThe final result is as follows:

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

Note that the order of distribution is important. If a property appears in two objects at the same time, the latter will replace the former.

Of course, typescript understands this order. Therefore, if multiple extension objects define a property with the same key, the type of the property in the result object will be the property type of the last assignment, because it covers the previously assigned property:

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

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

Make a shallow copy of the object

Object extensions can be used to create shallow copies of objects. Suppose I want to create a new object and copy all the properties from the existingtodoItem create a newtodoWith objects, you can easily:

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

const shallowCopy = { ...todo };

In fact, you get a new object and all the property values are copied:

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

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

You can now modifytextProperty, but does not modify the originaltodoItem:

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"]
// }

However, the new todo item reference is the same as the firsttagsArray. Because it is a shallow copy, changing the array will affect bothtodo

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"]
// }

If you want to create a deep copy of a serialized object, consider using theJSON.parse(JSON.stringify(obj))Or other methods, such asobject.assign()。 Object extensions copy only property values, which can cause unexpected behavior if one is a reference to another.

Keyof and find type

JS is a highly dynamic language. Capturing the semantics of certain operations in a static type system can sometimes be tricky. With a simplepropFunction as an example:

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

It takes an object and a key and returns the value of the corresponding property. Different properties of an object can have totally different types, we don’t even knowobjWhat it looks like.

So how to write this function in typescript? Try it first:

[typescript evolution history -- 6] object extension operators and rest operators and keyof and lookup types

With these two types of annotations,objMust be an object,keyMust be a string. We have now limited the set of possible values for two parameters. However, TS still infers that the return type isany

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

Without further information, typescript does not know that it willkeyParameter, so it cannot inferpropThe more specific return type of the function. We need to provide more type information to achieve this.

Keyof operation symbol

API with attribute name as parameter is quite common in JS, but up to now, it has not expressed the type relationship in those APIs.

Typescript 2.1 NEWkeyofOperator. Enter index type query orkeyof, index type querykeyof TThe type of generation isTProperty name of. Suppose we have defined the followingTodoInterface:

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

You cankeyofOperator applied toTodoType to get the type of all its property keys, which is a union of string literal types

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

Of course, you can also write the union type manually"id" | "text" | "due"Instead of usingkeyof, but it’s troublesome, error prone, and troublesome to maintain. Moreover, it should be specific toTodoType of solution, not a generic solution.

Index type query

Yes.keyof, we can improve nowpropType annotation for the function. We no longer want to accept any strings askeyParameters. Instead, we need parameterskeyActually exists on the type of the incoming object

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

Typescript now inferspropThe return type of the function isT[K]This is what we callIndex type queryorLookup classType. It represents the typeTAttributeKType. If you pass nowpropMethod access the followingtodoEach of the three attributes has the correct type:

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

Now, if you pass atodoWhat happens to keys that do not exist on objects

[typescript evolution history -- 6] object extension operators and rest operators and keyof and lookup types

The compiler will report an error, which is good. It prevents us from trying to read a nonexistent property.

For another real-world example, see thelib.es2017.object.d.tsMethod object. Entries() in type declaration file:

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

entriesMethod returns an array of tuples, each containing an attribute key and a corresponding value. Admittedly, there are a lot of square brackets in the return type, but we have been looking for type security.


The bugs that may exist in the editing cannot be known in real time. In order to solve these bugs afterwards, a lot of time has been spent on log debugging. In this way, we recommend a good bug monitoring tool fundebug.

Original text:
https://mariusschulz.com/blog…
https://mariusschulz.com/blog…


Communication

The articles of dry goods series are summarized as follows. I think it’s a good idea to order a star. Welcome to learn from each other.

https://github.com/qq449245884/xiaozhi

Because of the space limitation, today’s sharing is only here. If you want to know more about it, you can scan the bottom two dimensional code of each article, then pay attention to our WeChat public address, and learn more information and valuable content.

[typescript evolution history -- 6] object extension operators and rest operators and keyof and lookup types