头图

The official documentation of TypeScript has long been updated, but the Chinese documents I can find are still in the older version. Therefore, some new and revised chapters have been translated and sorted out.

This article is translated from the " Everyday Types " chapter in the TypeScript Handbook.

This article does not strictly follow the original translation, but also explains and supplements part of the content.

Type Aliases

We have learned to directly use object types and union types in type annotations, which is very convenient, but sometimes, a type will be used multiple times, and we would prefer to refer to it by a single name.

This is the type alias (type alias). The so-called type alias, as the name implies, a name that can refer to any type. The syntax of a type alias is:

type Point = {
  x: number;
  y: number;
};
 
// Exactly the same as the earlier example
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });

You can use type aliases to give any type a name, for example, to name a union type:

type ID = number | string;

Note that aliases are the only aliases, and you cannot use type aliases to create different versions of the same type. When you use a type alias, it is the same as the type you wrote. In other words, the code may look illegal, but it is still legal for TypeScript, because both types are aliases of the same type:

type UserInputSanitizedString = string;
 
function sanitizeInput(str: string): UserInputSanitizedString {
  return sanitize(str);
}
 
// Create a sanitized input
let userInput = sanitizeInput(getInput());
 
// Can still be re-assigned with a string though
userInput = "new input";

Interfaces

An interface declaration is another way of naming object types:

interface Point {
  x: number;
  y: number;
}
 
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });

Just like the type alias we used in the previous section, this example can also work, just as we used an anonymous object type. TypeScript only cares about printCoord -it cares whether the value has the desired properties. It is this characteristic that only cares about the structure and capabilities of types that we consider TypeScript to be a structurally type system.

The difference between type aliases and interfaces

Type aliases are very similar to interfaces, and most of the time, you can choose to use them at will. Almost all the characteristics of the interface can be used in type . The most important difference between the two is that the type alias itself cannot add new attributes, and the interface can be extended.

// Interface
// 通过继承扩展类型
interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

const bear = getBear() 
bear.name
bear.honey
        
// Type
// 通过交集扩展类型
type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: boolean 
}

const bear = getBear();
bear.name;
bear.honey;
// Interface
// 对一个已经存在的接口添加新的字段
interface Window {
  title: string
}

interface Window {
  ts: TypeScriptAPI
}

const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});
        
// Type
// 创建后不能被改变
type Window = {
  title: string
}

type Window = {
  ts: TypeScriptAPI
}

// Error: Duplicate identifier 'Window'.

You will learn more in subsequent chapters. So it doesn't matter if the following content cannot be understood immediately:

Most of the time, you can choose according to personal preference. TypeScript will tell you if it needs other declarations. If you like exploratory use, use interface until you need to use the features of type

Type Assertions

Sometimes, you know the type of a value, but TypeScript doesn't.

For example, if you use document.getElementById , TypeScript only knows that it will return a HTMLElement , but you know that what you want to get is a HTMLCanvasElement .

At this time, you can use type assertions to specify it as a more specific type:

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

Just like type annotations, type assertions will also be removed by the compiler and will not affect any runtime behavior.

You can also use angle bracket syntax (note that it cannot be used in the .tsx file), which is equivalent:

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
Remember: because the type assertion will be removed at compile time, there will be no type assertion check at runtime. Even if the type assertion is wrong, there will be no exception or null .

TypeScript only allows type assertions to be converted to a more specific or less specific type. This rule can prevent some impossible forced type conversions, such as:

const x = "hello" as number;
// Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

Sometimes, this rule is very conservative and prevents you from valid type conversions. If this happens, you can use a double assertion, first assert as any (or unknown ), and then assert as the desired type:

const a = (expr as any) as T;

Literal Types

In addition to the common types string and number , we can also declare the types as more specific numbers or strings.

As we all know, there are many ways to declare variables in JavaScript. For example, var and let , variables declared in this way can be modified later, and const , variables declared in this way cannot be modified, which will affect TypeScript to create types for literals.

let changingString = "Hello World";
changingString = "Olá Mundo";
// Because `changingString` can represent any possible string, that
// is how TypeScript describes it in the type system
changingString;
// let changingString: string
const constantString = "Hello World";
// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
constantString;
// const constantString: "Hello World"

The literal type itself is not very useful:

let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";
// Type '"howdy"' is not assignable to type '"hello"'.

If combined with the joint type, it becomes much more useful. For example, when the function can only pass in some fixed strings:

function printText(s: string, alignment: "left" | "right" | "center") {
  // ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.

The same is true for numeric literal types:

function compare(a: string, b: string): -1 | 0 | 1 {
  return a === b ? 0 : a > b ? 1 : -1;
}

Of course, you can also combine with non-literal types:

interface Options {
  width: number;
}
function configure(x: Options | "auto") {
  // ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");

// Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.

There is also a literal type, Boolean literals. Because there are only two Boolean literal types, true and false , the type boolean actually an alias for the joint type true | false

Literal Inference

When you initialize a variable to an object, TypeScript will assume that the value of the object's properties will be modified in the future. For example, if you write code like this:

const obj = { counter: 0 };
if (someCondition) {
  obj.counter = 1;
}

TypeScript does not think that obj.counter to be 0 , and it is now assigned the value 1 be an error. In other words, obj.counter must be of string , but it is not required to be 0 , because the type can determine the read and write behavior.

This also applies to strings:

declare function handleRequest(url: string, method: "GET" | "POST"): void;

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);

// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

In the above example, req.method be inferred string , rather than "GET" , because creating req and calls handleRequest between function, there may be other code that perhaps would req.method assign a new string such as "Guess" . So TypeScript reported an error.

There are two ways to solve it:

  1. Add a type assertion to change the inference result:
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");

Modification 1 means "I deliberately let req.method be the literal type "GET" , which will prevent possible assignment of "GUESS" future." Amendment 2 said, "I know req.method value is "GET" ."

  1. You can also use as const to convert the entire object into a type literal:
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);

as const effect of const similar to 061d45c5e97b72, but for the type system, it can ensure that all attributes are given a literal type instead of a more general type such as string or number .

null and undefined

JavaScript has two primitive values that are used to indicate vacancy or uninitialized. They are null and undefined .

TypeScript has two corresponding types with the same name. Their behavior depends on whether the strictNullChecks option is turned on.

strictNullChecks closed

When the strictNullChecks option is turned off, if a value may be null or undefined , it can still be accessed correctly or assigned to any type of attribute. This is a bit similar to languages without null checking (such as C#, Java). The lack of these checks is the main source of bugs, so we always recommend developers to enable the strictNullChecks option.

strictNullChecks open

When the strictNullChecks option is turned on, if a value may be null or undefined , you need to check these values before using its methods or attributes, just like the optional attributes, check if they are undefined . We can also use type narrowing to check if the value is null :

function doSomething(x: string | null) {
  if (x === null) {
    // do nothing
  } else {
    console.log("Hello, " + x.toUpperCase());
  }
}

Non-null Assertion Operator (Suffix ! ) (Non-null Assertion Operator)

TypeScript provides a special grammar that can remove null and undefined from the type without any checks. This is to write ! after any expression, which is a valid type assertion that represents its value It cannot be null or undefined :

function liveDangerously(x?: number | null) {
  // No error
  console.log(x!.toFixed());
}

Just like other type assertions, this will not change any runtime behavior. The important thing to say it again, only if you know exactly the value can not be null or undefined when using ! .

Enumeration (Enums)

Enumeration is a new feature added by TypeScript, used to describe that a value may be one of multiple constants. Unlike most TypeScript features, this is not a type-level increment, but will be added to the language and runtime. Because of this, you should understand this feature. But you can wait and use it again, unless you are sure to use it. You can learn more about the enumerated type

Less Common Primitives

Let's mention some primitive types remaining in JavaScript. But we will not go into it in depth.

bigInt

ES2020 introduces the original type BigInt , which is used to represent very large integers:

// Creating a bigint via the BigInt function
const oneHundred: bigint = BigInt(100);
 
// Creating a BigInt via the literal syntax
const anotherHundred: bigint = 100n;

You can learn more TypeScript 3.2 release log

symbol

This is also a primitive type in JavaScript. Through the function Symbol() , we can create a globally unique reference:

const firstName = Symbol("name");
const secondName = Symbol("name");
 
if (firstName === secondName) {
  // This condition will always return 'false' since the types 'typeof firstName' and 'typeof secondName' have no overlap.
  // Can't ever happen
}

You can learn more about Symbol on page

TypeScript series

The TypeScript series of articles consists of three parts: official document translation, important and difficult analysis, and practical skills. It covers entry, advanced, and actual combat. It aims to provide you with a systematic learning TS tutorial. The entire series is expected to be about 40 articles. Click here to browse the full series of articles, and suggest to bookmark the site by the way.

WeChat: "mqyqingfeng", add me to the only reader group in Kongyu.

If there are mistakes or not rigorous, please correct me, thank you very much. If you like or have some inspiration, star is welcome, which is also an encouragement to the author.


冴羽
9.3k 声望6.3k 粉丝

17 年开始写前端文章,至今 6 个系列,上百篇文章,全网千万阅读