docs(zh-Hans): translate /handbook-v2/Type Manipulation/Conditional Types.md to Chinese#84
docs(zh-Hans): translate /handbook-v2/Type Manipulation/Conditional Types.md to Chinese#84htmlin wants to merge 4 commits intomicrosoft:mainfrom
/handbook-v2/Type Manipulation/Conditional Types.md to Chinese#84Conversation
|
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. |
Translation of Everyday Types.mdtitle: Daily type oneline: "The language primitives."In this chapter, we'll cover some of the most common value types in JavaScript code and explain how to describe them in TypeScript. Types can also appear in many place , not just type comments. Let's first review the most basic and common types you might encounter when writing JavaScript or TypeScript code. The primitives:
|
Interface |
Type |
|---|---|
|
扩展接口
|
通过 "&" 扩展类型
|
|
向现有接口添加新字段
|
类型创建后不能更改
|
You'll learn more about these concepts in later chapters, so don't worry if you don't understand them right away.
- Before TypeScript 4.2, type aliases were named possible appears in the error message, sometimes replacing the equivalent type of anonymous (which may or may not be required). The interface is always named in the error message.
- Type aliases cannot participate Declare merge, but the interface can。
- Interfaces can only be used Declares the object and cannot rename the base type.
- The interface name will always appears in its original form In the error message, however only It does not appear until it is used by name.
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 ornullgenerated 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.
-
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.methodto always have the literal type"GET"", preventing the possible assignment of"GUESS"to that field after.
Change 2 means "I know for other reasons thatreq.methodhas the value"GET"". -
You can use
as constto 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.
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 useful programs, we must make decisions based on input.
JavaScript programs are no different, but given the fact that values can be easily reflected, 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 form of a condition type looks a bit like a 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 seem immediately 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:
- This can be cumbersome if a library has to make the same choice over and over again in its API.
- We have to create three overloads: one for us Are you sure Each case at the time of type (one for
string, one fornumber), one for the most general situation (accept one).string | number)。 aboutcreateLabelFor each new type that can be processed, the number of overloads grows exponentially.
Instead, we can encode the logic as 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 is wrong because T I don't know if there's a name message The property of the .
We can limit it 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 Take any type, and in message The default is when the property is not available never What about types like this?
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 just 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 probably the most allowable capture of all cases). Overload resolution cannot be performed 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 conditional types act on generic types, they become given a union type The assignment type 。
Take the following example:
type ToArray<Type> = Type extends any ? Type[] : never;If we insert 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 all we have left is:
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>;
// ^?|
Thanks for the contribution. Are you still work in progress on it now? |
@Kingwl Yes, I will continue to translate it, but it needs to be done in stages. I also need to request a review. Thanks! |
/handbook-v2/Type Manipulation/Conditional Types.md to Chinese
No description provided.