The official TypeScript documentation has been updated for a long time, but the Chinese documentation I could find was still in older versions. Therefore, some new and revised chapters are translated and sorted out.

This translation is from the “Everyday Types” chapter in the TypeScript Handbook.

This paper does not strictly follow the translation of the original text, and some of the content has also been explained and supplemented.

Type Aliases

We’ve learned to use object types and union types directly in type annotations, which is convenient, but sometimes a type is used more than once and we prefer to refer to it by a single name.

This is called a type alias. A type alias is, as the name implies, a name that can refer to any type. The syntax for 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 });
Copy the code

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

type ID = number | string;
Copy the code

Note that aliases are unique 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 is still legal to 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";
Copy the code

Interfaces

Interface declarations are 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 });
Copy the code

Just like the type alias we used in the previous section, this example will work just as well as if we were using an anonymous object type. TypeScript only cares about the structure of the value passed to printCoord — whether the value has the desired properties. It is this focus on type structure and capability that led us to think of TypeScript as a structured type system.

Type aliases and interface differences

Type aliases are very similar to interfaces, and most of the time, you can use them any way you want. Almost all features of an interface can be used in Type. The key difference is that the type alias itself cannot add new attributes, whereas the interface can be extended.

// Interface
// Extend the type by inheritance
interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

const bear = getBear() 
bear.name
bear.honey
        
// Type
// Extend types by intersection
type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: boolean 
}

const bear = getBear();
bear.name;
bear.honey;
Copy the code
// Interface
// Add a new field to an existing interface
interface Window {
  title: string
}

interface Window {
  ts: TypeScriptAPI
}

const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});
        
// Type
// It cannot be changed after being created
type Window = {
  title: string
}

type Window = {
  ts: TypeScriptAPI
}

// Error: Duplicate identifier 'Window'.
Copy the code

You’ll learn more about this in subsequent chapters. So it doesn’t matter if the following is not immediately understood:

  • Prior to TypeScript 4.2, the name of a type alias might appear in error messages, sometimes in place of equivalent anonymous types (which might not be expected). The name of the interface always appears in the error message.
  • Type aliases may not implement declarative merging, but interfaces can
  • The interface may only be used to declare the shape of the object, not to rename the primitive type
  • When an interface is used by name, their name will always appear in the error message, if used directly, the original structure will appear

Most of the time, you can choose according to personal preference. TypeScript tells you if it needs declarations in other ways. If you prefer exploratory use, use interface until you need to use the Type feature.

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 an HTMLElement, but you know that you are fetching an HTMLCanvasElement.

At this point, you can specify it as a more specific type using type assertion:

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

Just like type annotations, type assertions are removed by the compiler and do not affect any runtime behavior.

You can also use Angle bracket syntax (note not in.tsx files), which is equivalent:

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
Copy the code

Remember: Because type assertions are removed at compile time, there is no checking for type assertions at runtime, and no exceptions or NULL are generated even if type assertions are incorrect.

TypeScript only allows type assertions to be cast to a more or less specific type. This rule prevents impossible casts, 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.
Copy the code

Sometimes, this rule can be very conservative and prevent you from doing an effective cast. If this happens, you can use a double assertion, asserting any (or unknown) and then the expected type:

const a = (expr as any) as T;
Copy the code

Literal Types

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

As you all know, there are several ways to declare variables in JavaScript. Variables declared this way can be changed later, such as var and let, and const variables declared this way can’t be changed, which affects TypeScript’s ability 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
Copy the code
const constantString = "Hello World";
// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
constantString;
// const constantString: "Hello World"
Copy the code

Literal types by themselves are not very useful:

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

If you combine union types, it becomes much more useful. For example, when a function can only pass in 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"'.
Copy the code

The same is true for numeric literals:

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

Of course, you can also associate 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"'.
Copy the code

There is another type of literal, Boolean literals. Because there are only two types of Boolean literals, true and false, the types Boolean is actually true joint type | false alias.

Literal Inference

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

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

TypeScript does not consider it an error to assign 1 to obj.counter, which was 0 before. In other words, obj.counter must be of type string, but it doesn’t have to be 0, because the type determines 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"'.
Copy the code

In the above example, req.method is inferred to be a string, not “GET”, because there could be other code between creating req and calling handleRequest, Method might be assigned a new string such as “Guess”. So TypeScript reports errors.

There are two ways to solve this problem:

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

Modification 1 means “I intentionally made req.method of type literal type “GET”, which prevents future assignments to fields such as “GUESS” “. Modification 2 means “I know the value of req.method is “GET”.

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

As const has a similar effect to const, but for the type system it ensures that all attributes are given a literal type, rather than a more generic type such as string or number.

nullundefined

JavaScript has two primitive types for empty or uninitialized values: null and undefined.

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

strictNullChecksShut down

When strictNullChecks is turned off, if a value can be null or undefined, it can still be accessed correctly, or assigned to any type of attribute. This is somewhat similar to languages without null checking (e.g. C#, Java). The lack of these checks is a major source of bugs, so strictNullChecks is always recommended.

strictNullChecksOpen the

When strictNullChecks is turned on, if a value may be null or undefined, you need to check that value before using its methods or attributes, just as you need to check undefined before using optional attributes. A role of type narrowing can also be used to check for null:

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

Non-null assertion operators (suffixes!) (Non – null an Assertion Operator)

TypeScript provides a special syntax to remove null and undefined from a type without checking. This means writing! This is a valid type assertion, indicating that its value cannot be null or undefined:

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

Just like other type assertions, this does not change any runtime behavior. The important thing is, only use this value if you know that it can’t be null or undefined! .

Enumeration (Enums)

Enumerations are a new TypeScript feature that describes how a value can be one of multiple constants. Unlike most TypeScript features, this is not a type-level increment, but is added to the language and runtime. Because of this, you should know about this feature. But you can wait until you’re sure you want to use it. You can learn more about this on the enumerated types page.

Less Common Primitives

Let’s mention the remaining primitive types in JavaScript. But we’re not going to go into that.

bigInt

ES2020 introduces the primitive type BigInt 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;
Copy the code

You can learn more about TypeScript 3.2 release Notes in the TypeScript 3.2 Release Notes.

symbol

This is also a primitive type in JavaScript, and with 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
}
Copy the code

You can learn more on the Symbol page.

The TypeScript series

  1. The basics of TypeScript
  2. Common TypeScript Types (Part 1)
  3. TypeScript type narrowing
  4. The function of TypeScript
  5. TypeScript object type
  6. The generic of TypeScript
  7. TypeScript’s Keyof operator
  8. TypeScript’s Typeof operator
  9. TypeScript index access types
  10. TypeScript conditional types

Wechat: “MQyqingfeng”, add me Into Hu Yu’s only readership group.

If there is any mistake or not precise place, please be sure to give correction, thank you very much. If you like or are inspired by it, welcome star and encourage the author.