Skip to content

docs(zh-Hans): translate /handbook-v2/Type Manipulation/Conditional Types.md to Chinese #93

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 14, 2022

Conversation

htmlin
Copy link
Contributor

@htmlin htmlin commented Jul 4, 2021

No description provided.

@github-actions
Copy link
Contributor

github-actions bot commented Jul 4, 2021

Thanks for the PR!

This section of the codebase is owned by @Kingwl - if they write a comment saying "LGTM" then it will be merged.

@github-actions
Copy link
Contributor

github-actions bot commented Jul 4, 2021

Translation of Conditional Types.md

title: The condition type
layout: docs
permalink: /zh/docs/handbook/2/conditional-types.html

oneline: "Create types which act like if statements in the type system."

At the heart of most effective procedures is that we have to make some decisions based on input.
The same is true of JavaScript programs, but because values can be easily introspected, these decisions are also based on the type of input.
The type of condition Helps describe the relationship between input and output types.

interface Animal {
  live(): void;
}
interface Dog extends Animal {
  woof(): void;
}

type Example1 = Dog extends Animal ? number : string;
//   ^?

type Example2 = RegExp extends Animal ? number : string;
//   ^?

The condition type looks a bit like the conditional expression in JavaScript (条件 ? true 表达式 : false 表达式):

type SomeType = any;
type OtherType = any;
type TrueType = any;
type FalseType = any;
type Stuff =
  // ---cut---
  SomeType extends OtherType ? TrueType : FalseType;

while extends When the type on the left can be assigned to the type on the right, you get the type in the first branch (the "true" branch);

From the example above, the condition type may not immediately appear useful - we can tell ourselves whether Dog extends Animal and select number or string
But the power of conditional types comes from using them with generics.

Let's take the following createLabel Functions, for example:

interface IdLabel {
  id: number /* 一些字段 */;
}
interface NameLabel {
  name: string /* 其它字段 */;
}

function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
  throw "unimplemented";
}

These createLabel overloads describe a single JavaScript function that is selected based on the input type. Note the following:

  1. This can be cumbersome if a library has to make the same choice over and over again in its API.
  2. We have to create three overloads: one for us Are you sure Each case at the time of type (one for string, one for number), one for the most general situation (accept one). string | number)。 about createLabel For each new type that can be processed, the number of overloads grows exponentially.

Instead, we can convert the logic to a conditional type:

interface IdLabel {
  id: number /* 一些字段 */;
}
interface NameLabel {
  name: string /* 其它字段 */;
}
// ---cut---
type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;

We can then use this condition type to simplify overloading to a single function without overloading.

interface IdLabel {
  id: number /* 一些字段 */;
}
interface NameLabel {
  name: string /* 其它字段 */;
}
type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;
// ---cut---
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
  throw "unimplemented";
}

let a = createLabel("typescript");
//  ^?

let b = createLabel(2.8);
//  ^?

let c = createLabel(Math.random() ? "hello" : 42);
//  ^?

Conditional type constraints

Typically, the check of the condition type will provide us with some new information.
Just as narrowing down with type guards can give us more specific types, the true branch of a conditional type further constrains generics based on the type we examine.

Let's take a look at the following example:

// @errors: 2536
type MessageOf<T> = T["message"];

In this case, TypeScript makes an error because it does not know T There is a name message The property of the .
We can constrain TTypeScript won't complain anymore:

type MessageOf<T extends { message: unknown }> = T["message"];

interface Email {
  message: string;
}

interface Dog {
  bark(): void;
}

type EmailMessageContents = MessageOf<Email>;
//   ^?

However, if we wish MessageOf Accept any type, and in message The default is when the property is not available never What should we do with a type like that?
We can do this by moving out of constraints and introducing conditional types:

type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;

interface Email {
  message: string;
}

interface Dog {
  bark(): void;
}

type EmailMessageContents = MessageOf<Email>;
//   ^?

type DogMessageContents = MessageOf<Dog>;
//   ^?

In the true branch, TypeScript knows T will There's one message attribute.

As another example, we can also write a name Flatten The types of , which flatten the array types to their element types, but in other cases do not process them:

type Flatten<T> = T extends any[] ? T[number] : T;

// Extracts out the element type.
type Str = Flatten<string[]>;
//   ^?

// Leaves the type alone.
type Num = Flatten<number>;
//   ^?

while Flatten When an array type is given, it uses a band number index access to extract string[] The type of element of .
Otherwise, it returns only the given type.

Inferred in the condition type

We find ourselves using conditional types to apply constraints, and then extract the types.
This eventually becomes a very common operation, and the condition type makes it easier.

Conditional types provide us with a use infer The method by which the keyword is inferred from the type with which it is compared in the true branch.
For example, we can be in Flatten Infer the element type instead of "manually" extracting it using the index access type:

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

Here we use it infer The keyword introduces a name named in a declarative manner Item new generic type variable, rather than specifying how element types are retrieved in the true branch T
This allows us to think about how to dig and explore the structures of the types we are interested in.

We can use it infer Keywords write some useful assistant type aliases.
For example, in a simple case, we can extract the return type from the function type:

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
  ? Return
  : never;

type Num = GetReturnType<() => number>;
//   ^?

type Str = GetReturnType<(x: string) => string>;
//   ^?

type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
//   ^?

When inferred from a type that has multiple call signatures, such as an overloaded function, it is taken from The last one Signatures are inferred (this is perhaps the most relaxed 1000m). Overload resolutions cannot be executed based on the list of parameter types.

declare function stringOrNum(x: string): number;
declare function stringOrNum(x: number): string;
declare function stringOrNum(x: string | number): string | number;

type T1 = ReturnType<typeof stringOrNum>;
//   ^?

The type of allocation condition

When incoming type parameters are union types, they are The assignment type
Take the following example:

type ToArray<Type> = Type extends any ? Type[] : never;

If we pass in a union type ToArray, the condition type is applied to each member of the union type.

type ToArray<Type> = Type extends any ? Type[] : never;

type StrArrOrNumArr = ToArray<string | number>;
//   ^?

What's happening here is StrOrNumArray Distributed in the following locations:

type StrArrOrNumArr =
  // ---cut---
  string | number;

and map to valid content on each member type of the federate type:

type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr =
  // ---cut---
  ToArray<string> | ToArray<number>;

So we get:

type StrArrOrNumArr =
  // ---cut---
  string[] | number[];

In general, distribution is the desired behavior.
To avoid this behavior, you can surround it with square brackets extends Both sides of the keyword.

type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;

// 'StrOrNumArr' 不再是一个联合类型
type StrOrNumArr = ToArrayNonDist<string | number>;
//   ^?
Translation of Everyday Types.md

title: Common type
layout: docs
permalink: /zh/docs/handbook/2/everyday-types.html

oneline: "The language primitives."

In this chapter, we'll cover some of the most common types of values in JavaScript code and explain how to describe them in TypeScript.
This is not an exhaustive list, and subsequent chapters will describe naming and using other types of more methods.

Types can also appear in many place , not just type comments.
As we look at the types themselves, we'll also look at where they can be referenced to form new structures.

Let's first review the most basic and common types you might encounter when writing JavaScript or TypeScript code.
These will form a more complex type of core building block later.

Basic type:stringnumberand boolean

JavaScript has three very commonly used primitives: string, number, and boolean.
Each has a corresponding type in TypeScript.
As you might expect, these are the same names you'd see if you used the JavaScript typeof operator on a value of those types:

  • string represents string values like "Hello, world"
  • number is for numbers like 42. JavaScript does not have a special runtime value for integers, so there's no equivalent to int or float - everything is simply number
  • boolean is for the two values true and false

The type names String, Number, and Boolean (starting with capital letters) are legal, but refer to some special built-in types that will very rarely appear in your code. Always use string, number, or boolean for types.

Arrays

To specify the type of an array like [1, 2, 3], you can use the syntax number[]; this syntax works for any type (e.g. string[] is an array of strings, and so on).
You may also see this written as Array<number>, which means the same thing.
We'll learn more about the syntax T<U> when we cover generics.

Note that [number] is a different thing; refer to the section on tuple types.

any

TypeScript also has a special type, any, that you can use whenever you don't want a particular value to cause typechecking errors.

When a value is of type any, you can access any properties of it (which will in turn be of type any), call it like a function, assign it to (or from) a value of any type, or pretty much anything else that's syntactically legal:

let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

The any type is useful when you don't want to write out a long type just to convince TypeScript that a particular line of code is okay.

noImplicitAny

When you don't specify a type, and TypeScript can't infer it from context, the compiler will typically default to any.

You usually want to avoid this, though, because any isn't type-checked.
Use the compiler flag noImplicitAny to flag any implicit any as an error.

Type Annotations on Variables

When you declare a variable using const, var, or let, you can optionally add a type annotation to explicitly specify the type of the variable:

let myName: string = "Alice";
//        ^^^^^^^^ Type annotation

TypeScript doesn't use "types on the left"-style declarations like int x = 0;
Type annotations will always go after the thing being typed.

In most cases, though, this isn't needed.
Wherever possible, TypeScript tries to automatically infer the types in your code.
For example, the type of a variable is inferred based on the type of its initializer:

// No type annotation needed -- 'myName' inferred as type 'string'
let myName = "Alice";

For the most part you don't need to explicitly learn the rules of inference.
If you're starting out, try using fewer type annotations than you think - you might be surprised how few you need for TypeScript to fully understand what's going on.

Functions

Functions are the primary means of passing data around in JavaScript.
TypeScript allows you to specify the types of both the input and output values of functions.

Parameter Type Annotations

When you declare a function, you can add type annotations after each parameter to declare what types of parameters the function accepts.
Parameter type annotations go after the parameter name:

// Parameter type annotation
function greet(name: string) {
  //                 ^^^^^^^^
  console.log("Hello, " + name.toUpperCase() + "!!");
}

When a parameter has a type annotation, arguments to that function will be checked:

// @errors: 2345
declare function greet(name: string): void;
// ---cut---
// Would be a runtime error if executed!
greet(42);

Even if you don't have type annotations on your parameters, TypeScript will still check that you passed the right number of arguments.

Return Type Annotations

You can also add return type annotations.
Return type annotations appear after the parameter list:

function getFavoriteNumber(): number {
  //                        ^^^^^^^^
  return 26;
}

Much like variable type annotations, you usually don't need a return type annotation because TypeScript will infer the function's return type based on its return statements.
The type annotation in the above example doesn't change anything.
Some codebases will explicitly specify a return type for documentation purposes, to prevent accidental changes, or just for personal preference.

Anonymous Functions

Anonymous functions are a little bit different from function declarations.
When a function appears in a place where TypeScript can determine how it's going to be called, the parameters of that function are automatically given types.

Here's an example:

// @errors: 2551
// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];

// Contextual typing for function
names.forEach(function (s) {
  console.log(s.toUppercase());
});

// Contextual typing also applies to arrow functions
names.forEach((s) => {
  console.log(s.toUppercase());
});

Even though the parameter s didn't have a type annotation, TypeScript used the types of the forEach function, along with the inferred type of the array, to determine the type s will have.

This process is called contextual typing because the context that the function occurred in informed what type it should have.
Similar to the inference rules, you don't need to explicitly learn how this happens, but understanding that it does happen can help you notice when type annotations aren't needed.
Later, we'll see more examples of how the context that a value occurs in can affect its type.

Object Types

Apart from primitives, the most common sort of type you'll encounter is an object type.
This refers to any JavaScript value with properties, which is almost all of them!
To define an object type, we simply list its properties and their types.

For example, here's a function that takes a point-like object:

// The parameter's type annotation is an object type
function printCoord(pt: { x: number; y: number }) {
  //                      ^^^^^^^^^^^^^^^^^^^^^^^^
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });

Here, we annotated the parameter with a type with two properties - x and y - which are both of type number.
You can use , or ; to separate the properties, and the last separator is optional either way.

The type part of each property is also optional.
If you don't specify a type, it will be assumed to be any.

Optional Properties

Object types can also specify that some or all of their properties are optional.
To do this, add a ? after the property name:

function printName(obj: { first: string; last?: string }) {
  // ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

In JavaScript, if you access a property that doesn't exist, you'll get the value undefined rather than a runtime error.
Because of this, when you read from an optional property, you'll have to check for undefined before using it.

// @errors: 2532
function printName(obj: { first: string; last?: string }) {
  // Error - might crash if 'obj.last' wasn't provided!
  console.log(obj.last.toUpperCase());
  if (obj.last !== undefined) {
    // OK
    console.log(obj.last.toUpperCase());
  }

  // A safe alternative using modern JavaScript syntax:
  console.log(obj.last?.toUpperCase());
}

Union Types

TypeScript's type system allows you to build new types out of existing ones using a large variety of operators.
Now that we know how to write a few types, it's time to start combining them in interesting ways.

Defining a Union Type

The first way to combine types you might see is a union type.
A union type is a type formed from two or more other types, representing values that may be any one of those types.
We refer to each of these types as the union's members.

Let's write a function that can operate on strings or numbers:

// @errors: 2345
function printId(id: number | string) {
  console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });

Working with Union Types

It's easy to provide a value matching a union type - simply provide a type matching any of the union's members.
If you have a value of a union type, how do you work with it?

TypeScript will only allow you to do things with the union if that thing is valid for every member of the union.
For example, if you have the union string | number, you can't use methods that are only available on string:

// @errors: 2339
function printId(id: number | string) {
  console.log(id.toUpperCase());
}

The solution is to narrow the union with code, the same as you would in JavaScript without type annotations.
Narrowing occurs when TypeScript can deduce a more specific type for a value based on the structure of the code.

For example, TypeScript knows that only a string value will have a typeof value "string":

function printId(id: number | string) {
  if (typeof id === "string") {
    // In this branch, id is of type 'string'
    console.log(id.toUpperCase());
  } else {
    // Here, id is of type 'number'
    console.log(id);
  }
}

Another example is to use a function like Array.isArray:

function welcomePeople(x: string[] | string) {
  if (Array.isArray(x)) {
    // Here: 'x' is 'string[]'
    console.log("Hello, " + x.join(" and "));
  } else {
    // Here: 'x' is 'string'
    console.log("Welcome lone traveler " + x);
  }
}

Notice that in the else branch, we don't need to do anything special - if x wasn't a string[], then it must have been a string.

Sometimes you'll have a union where all the members have something in common.
For example, both arrays and strings have a slice method.
If every member in a union has a property in common, you can use that property without narrowing:

// Return type is inferred as number[] | string
function getFirstThree(x: number[] | string) {
  return x.slice(0, 3);
}

It might be confusing that a union of types appears to have the intersection of those types' properties.
This is not an accident - the name union comes from type theory.
The union number | string is composed by taking the union of the values from each type.
Notice that given two sets with corresponding facts about each set, only the intersection of those facts applies to the union of the sets themselves.
For example, if we had a room of tall people wearing hats, and another room of Spanish speakers wearing hats, after combining those rooms, the only thing we know about every person is that they must be wearing a hat.

Type alias

We use them by writing object types and union types directly in type annotations.
This is convenient, but you often want to use the same type multiple times and reference it by a name.

Type alias That's exactly what it is -- arbitrary type one name
The syntax of type aliases is:

type 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 });

In fact, it's not just object types, you can use type aliases to name any type.
For example, a type alias can name a union type:

type ID = number | string;

Note that the alias It's just that Alias - You can't create different "versions" of the same type using type aliases.
When you use an alias, it's exactly the same type of alias you wrote.
In other words, this code looks It may be illegal, but it is true for TypeScript because both types are aliases of the same type:

declare function getInput(): string;
declare function sanitize(str: string): string;
// ---分割---
type UserInputSanitizedString = string;

function sanitizeInput(str: string): UserInputSanitizedString {
  return sanitize(str);
}

// 创建一个经过清理的输入框
let userInput = sanitizeInput(getInput());

// 仍然可以使用字符串重新赋值
userInput = "new input";

interface

Interface declaration is another way to name 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 as we used type aliases above, this example works just as we do with anonymous object types.
TypeScript only cares about what we pass on printCoord The structure of the value of - it only cares if it has the expected properties.
Only care about the structure and functionality of the type, which is why we say TypeScript is one Structured type The type of system.

The difference between type aliases and interfaces

Type aliases and interfaces are very similar and in most cases you are free to choose between them.
Almost all interface Features are available type The key difference is that the type cannot be reopened to add new properties, and the interface is always scalable.

Interface Type

扩展接口

interface Animal {
  name: string
}
interface Bear extends Animal { honey: boolean }
const bear = getBear() bear.name bear.honey

通过 "&" 扩展类型

type Animal = {
  name: string
}
type Bear = Animal & { honey: Boolean }
const bear = getBear(); bear.name; bear.honey;

向现有接口添加新字段

interface Window {
  title: string
}
interface Window { ts: TypeScriptAPI }
const src = 'const a = "Hello World"'; window.ts.transpileModule(src, {});

类型创建后不能更改

type Window = {
  title: string
}
type Window = { ts: TypeScriptAPI }
// Error: Duplicate identifier 'Window'.

You'll learn more about these concepts in later chapters, so don't worry if you don't understand them right away.

In most cases, you can choose according to your preferences, and TypeScript will tell you if it needs other types of claims. If you want heuristics, you can use them interface until you need to use it type features in .

Type Assertions

Sometimes you will have information about the type of a value that TypeScript can't know about.

For example, if you're using document.getElementById, TypeScript only knows that this will return some kind of HTMLElement, but you might know that your page will always have an HTMLCanvasElement with a given ID.

In this situation, you can use a type assertion to specify a more specific type:

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

Like a type annotation, type assertions are removed by the compiler and won't affect the runtime behavior of your code.

You can also use the angle-bracket syntax (except if the code is in a .tsx file), which is equivalent:

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

Reminder: Because type assertions are removed at compile-time, there is no runtime checking associated with a type assertion.
There won't be an exception or null generated if the type assertion is wrong.

TypeScript only allows type assertions which convert to a more specific or less specific version of a type.
This rule prevents "impossible" coercions like:

// @errors: 2352
const x = "hello" as number;

Sometimes this rule can be too conservative and will disallow more complex coercions that might be valid.
If this happens, you can use two assertions, first to any (or unknown, which we'll introduce later), then to the desired type:

declare const expr: any;
type T = { a: 1; b: 2; c: 3 };
// ---cut---
const a = expr as any as T;

Literal Types

In addition to the general types string and number, we can refer to specific strings and numbers in type positions.

One way to think about this is to consider how JavaScript comes with different ways to declare a variable. Both var and let allow for changing what is held inside the variable, and const does not. This is reflected in how TypeScript creates 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;
// ^?

const constantString = "Hello World";
// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
constantString;
// ^?

By themselves, literal types aren't very valuable:

// @errors: 2322
let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";

It's not much use to have a variable that can only have one value!

But by combining literals into unions, you can express a much more useful concept - for example, functions that only accept a certain set of known values:

// @errors: 2345
function printText(s: string, alignment: "left" | "right" | "center") {
  // ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");

Numeric literal types work the same way:

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

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

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

There's one more kind of literal type: boolean literals.
There are only two boolean literal types, and as you might guess, they are the types true and false.
The type boolean itself is actually just an alias for the union true | false.

Literal Inference

When you initialize a variable with an object, TypeScript assumes that the properties of that object might change values later.
For example, if you wrote code like this:

declare const someCondition: boolean;
// ---cut---
const obj = { counter: 0 };
if (someCondition) {
  obj.counter = 1;
}

TypeScript doesn't assume the assignment of 1 to a field which previously had 0 is an error.
Another way of saying this is that obj.counter must have the type number, not 0, because types are used to determine both reading and writing behavior.

The same applies to strings:

// @errors: 2345
declare function handleRequest(url: string, method: "GET" | "POST"): void;
// ---cut---
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);

In the above example req.method is inferred to be string, not "GET". Because code can be evaluated between the creation of req and the call of handleRequest which could assign a new string like "GUESS" to req.method, TypeScript considers this code to have an error.

There are two ways to work around this.

  1. You can change the inference by adding a type assertion in either location:

    declare function handleRequest(url: string, method: "GET" | "POST"): void;
    // ---cut---
    // Change 1:
    const req = { url: "https://example.com", method: "GET" as "GET" };
    // Change 2
    handleRequest(req.url, req.method as "GET");

    Change 1 means "I intend for req.method to always have the literal type "GET"", preventing the possible assignment of "GUESS" to that field after.
    Change 2 means "I know for other reasons that req.method has the value "GET"".

  2. You can use as const to convert the entire object to be type literals:

    declare function handleRequest(url: string, method: "GET" | "POST"): void;
    // ---cut---
    const req = { url: "https://example.com", method: "GET" } as const;
    handleRequest(req.url, req.method);

The as const suffix acts like const but for the type system, ensuring that all properties are assigned the literal type instead of a more general version like string or number.

null and undefined

JavaScript has two primitive values used to signal absent or uninitialized value: null and undefined.

TypeScript has two corresponding types by the same names. How these types behave depends on whether you have the strictNullChecks option on.

strictNullChecks off

With strictNullChecks off, values that might be null or undefined can still be accessed normally, and the values null and undefined can be assigned to a property of any type.
This is like to how languages without null checks (e.g.C, Java) dorc.
The lack of checking for these values tends to be a major source of bugs; we always recommend people turn strictNullChecks on if it's practical to do so in their codebase.

strictNullChecks on

With strictNullChecks on, when a value is null or undefined, you will need to test for those values before using methods or properties on that value.
Just like checking for undefined before using an optional property, we can use narrowing to check for values that might be null:

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

Non-null Assertion Operator (Postfix !)

TypeScript also has a special syntax for removing null and undefined from a type without doing any explicit checking.
Writing ! after any expression is effectively a type assertion that the value isn't null or undefined:

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

Just like other type assertions, this doesn't change the runtime behavior of your code, so it's important to only use ! when you know that the value can't be null or undefined.

Enums

Enums are a feature added to JavaScript by TypeScript which allows for describing a value which could be one of a set of possible named constants. Unlike most TypeScript features, this is not a type-level addition to JavaScript but something added to the language and runtime. Because of this, it's a feature which you should know exists, but maybe hold off on using unless you are sure. You can read more about enums in the Enum reference page.

Less Common Primitives

It's worth mentioning the rest of the primitives in JavaScript which are represented in the type system.
Though we will not go into depth here.

bigint

From ES2020 onwards, there is a primitive in JavaScript used for very large integers, BigInt:

// @target: es2020

// 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 about BigInt in the TypeScript 3.2 release notes.

symbol

There is a primitive in JavaScript used to create a globally unique reference via the function Symbol():

// @errors: 2367
const firstName = Symbol("name");
const secondName = Symbol("name");

if (firstName === secondName) {
  // Can't ever happen
}

You can learn more about them in Symbols reference page.

Generated by 🚫 dangerJS against 45ea68c

Copy link
Contributor

@Kingwl Kingwl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! Some minor issues.

@Kingwl
Copy link
Contributor

Kingwl commented Jul 14, 2022

LGTM

@github-actions github-actions bot merged commit f5d46f4 into microsoft:main Jul 14, 2022
@github-actions
Copy link
Contributor

Merging because @Kingwl is a code-owner of all the changes - thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants