- Description : There is currently no Chinese translation of the latest official documents of TypeScript on the Internet, so there is such a translation plan. Because I am also a beginner in TypeScript, I cannot guarantee that the translation will be 100% accurate. If there are errors, please point out in the comment section;
- translation content : The tentative translation content is TypeScript Handbook , and other parts of the translation document will be added later;
- project address : TypeScript-Doc-Zh , if it helps you, you can click a star~
The official document address of this chapter: Everyday Types
Common type
In this chapter, our content will involve some of the most common data types in JavaScript code, and will also explain how these types are described in TypeScript. This chapter will not introduce all types in detail, and we will introduce more methods of naming and using other types in subsequent chapters.
Types can appear not only in type annotations, but also in many other places. While learning the types themselves, we will also learn how to use these types in certain places to form new structures.
First, let's review the most basic and commonly used types when writing JavaScript or TypeScript code. They will later become core components of more complex types.
Original type: string
, number
and boolean
JavaScript There are three very common original type : string
, number
and boolean
. Each type has a corresponding type in TypeScript. As you might expect, their names are the same as the strings obtained typeof
string
represents a string value similar to"Hello, world!"
number
represents a value similar to42
For integers, JavaScript has no special runtime value, so there is noint
orfloat
type-all numbers are of typenumber
boolean
represents the boolean valuestrue
andfalse
The type namesString
,Number
andBoolean
(beginning with a capital letter) are also legal, but they refer to built-in types that rarely appear in the code. Please always usestring
,number
andboolean
Array
To express [1,2,3]
, you can use the syntax number[]
. This syntax can also be used for any type (for example, string[]
means that the array elements are all string types). There is another way of writing Array<number>
, the effect of the two is the same. When we explain generics later, we will introduce the T<U>
grammar in detail.
Note that [number]
different from an ordinary array, it represents the tuple
any
TypeScript also has a special type any
When you don't want a value to cause a type check error, you can use any
.
When a value is of any
, you can access any of its properties (these properties will also be of any
), you can call it as a function, and you can assign it to any type of value (or put any type of value Assign to it), or any grammatically compliant operation:
let obj: any = { x: 0 };
// 下面所有代码都不会引起编译错误。使用 any 将会忽略类型检查,并且假定了
// 你比 TypeScript 更了解当前环境
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
any
type is useful when you don't want to write a long list of types to convince TypeScript that a certain line of code is OK.
noImplicitAny
When you do not explicitly specify a type and TypeScript cannot infer the type from the context, the compiler will treat it as the any
type by default.
However, usually you will avoid this situation, because any
will bypass type checking. Enabling the noImplicitAny configuration item can mark any implicitly inferred any
as an error.
Variable type annotation
When you use const
, var
or let
declare a variable, you can optionally add a type annotation to explicitly specify the type of the variable:
let myName: string = 'Alice';
TypeScript does not adopt the style of "declaring the type on the left side of the expression" int x = 0
Type annotations always follow the thing to declare the type.
However, in most cases, annotations are not necessary. TypeScript will automatically perform type inference in your code as much as possible. For example, the type of a variable is inferred based on its initial value:
// 不需要添加类型注解 —— myName 会被自动推断为 string 类型
let myName = 'Alice';
In most cases, you don't need to deliberately learn the rules of type inference. If you are a beginner, try to use as few type annotations as possible-you may be surprised to find that TypeScript needs so few annotations to fully understand what is happening.
function
Functions are the main way to pass data in JavaScript. TypeScript allows you to specify the type of input and output of a function.
Parameter type annotation
When you declare a function, you can add type annotations after each parameter to declare what types of parameters the function can accept. The type annotation of the parameter follows the name of each parameter:
// 参数类型注解
function greet(name: string){
console.log('Hello, ' + name.toUpperCase() + '!!');
}
When a parameter of a function has type annotations, TypeScript will type-check the actual parameters passed to the function:
// 如果执行,会有一个运行时错误!
greet(42);
// Argument of type 'number' is not assignable to parameter of type 'string'.
Even if you do not add type annotations to the parameters, TypeScript will check whether the number of parameters you pass is correct
Return value type annotation
You can also add type annotations to the return value. The return value type annotation appears after the parameter list:
function getFavourNumber(): number {
return 26;
}
Like variable type annotations, we usually don't need to add a type annotation to the return value, because TypeScript will infer the type of the function return value return
The type annotations in the above example will not change anything. Some code libraries will explicitly specify the type of return value, which may be for documentation needs, or to prevent accidental modification, or just personal preference.
Anonymous function
Anonymous functions are a bit different from function declarations. When a function appears somewhere and TypeScript can infer how it is called, the parameters of the function are automatically assigned types.
for example:
// 这里没有类型注解,但 TypeScript 仍能在后续代码找出 bug
const names = ["Alice", "Bob", "Eve"];
// 基于上下文推断匿名函数参数的类型
names.forEach(function (s) {
console.log(s.toUppercase());
^^^^^^^^^^^^
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
// 对于箭头函数,也可以正确推断
names.forEach((s) => {
console.log(s.toUppercase());
^^^^^^^^^^^^^
//Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
Even if there is no s
, TypeScript can forEach
the type of 061a1e58b3e26c based on the type of the function name
array type of s
.
This process is called context type inference , because the context in which the function is called determines the type of its parameters.
Similar to the rules of inference, you don't need to deliberately learn how this process happens, but after you know that this process does happen, you naturally know when you don't need to add type annotations. Later we will see more examples to understand how the context of a value affects its type.
Object type
In addition to primitive types, the most common type is the object type. It refers to any JavaScript value that contains attributes. To define an object type, simply enumerate its attributes and types.
For example, the following is a function that accepts an object type as a parameter:
// 参数的类型注解是一个对象类型
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, the type annotation we added for the parameter is an x
and y
(both types are number
). You can use ,
or ;
separate each attribute, and the separator for the last attribute can be added or not.
The type part of each attribute is also optional. If you do not specify the type, it will use the any
type.
Optional attributes
The object type can also specify that some or all attributes are optional. You only need to add a ?
after the corresponding attribute name:
function printName(obj: { first: string; last?: string }) {
// ...
}
// 下面两种写法都行
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
In JavaScript, if you access a non-existent property, you will get undefined
instead of a runtime error. Therefore, when you read an optional attribute, you need to check whether it is undefined
before using it.
function printName(obj: { first: string; last?: string }) {
// 如果 obj.last 没有对应的值,可能会报错!
console.log(obj.last.toUpperCase());
// Object is possibly 'undefined'.
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}
// 下面是使用现代 JavaScript 语法的另一种安全写法:
console.log(obj.last?.toUpperCase());
}
Union type
TypeScript's type system allows you to create new types based on existing types using a large number of operators. Now that we know how to write basic types, it's time to start combining them in an interesting way.
Define a union type
The first way to combine types is to use union types. The union type consists of two or more types, and it represents the value that can take any one of these types. Each type is called a member of the union type.
Let's write a function that can handle strings or numbers:
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// 报错
printId({ myID: 22342 });
// Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
// Type '{ myID: number; }' is not assignable to type 'number'.
Use union type
Providing a value that matches the union type is very simple-just provide a type that matches a member of the union type. If a value is a union type, how do you use it?
TypeScript will limit the operations you can take on the union type, and the operation will only take effect when the operation is effective for each member of the union type. For example, if you have the union type string | number
, then you will not be able to use methods string
function printId(id: number | string) {
console.log(id.toUpperCase());
// Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.
}
The solution is to narrow the union type in the code, which is the same as JavaScript without type annotations. Narrowing occurs when TypeScript can infer a more specific type based on the code structure.
For example, TypeScript knows that only string
type typeof
will return "string"
after using 061a1e58b3e5fa:
function printId(id: number | string) {
if (typeof id === "string") {
// 在这个分支中,id 的类型是 string
console.log(id.toUpperCase());
} else {
// 这里,id 的类型是 number
console.log(id);
}
}
Another example is to use a function Array.isArray
function welcomePeople(x: string[] | string) {
if (Array.isArray(x)) {
// 这里,x 是 string[]
console.log("Hello, " + x.join(" and "));
} else {
// 这里,x 是 string
console.log("Welcome lone traveler " + x);
}
}
Note that in the else
branch, we do not need to make additional judgments-if x
is not string[]
, then it must be string
.
Sometimes, all members of the union type may have commonality. For example, arrays and strings have slice
methods. If each member of a union type has a common attribute, then you can use the attribute directly without narrowing it down:
// 返回值会被推断为 number[] | string
function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}
The attributes of the various types of the union type overlap, and you may feel a little confused. In fact, this is not surprising, the term "union" comes from type theory. The union type number | string
is composed of the union of the values of each type. Assuming that two sets and their corresponding facts are given, only the intersection of facts can be applied to the intersection of the sets itself. For example, if the people in one room are tall and they wear hats, and the people in the other room are Spanish and also wear hats, then the people in the two rooms are put together, and the only fact we can get is: Everyone must wear a hat.
Type alias
So far, we have used object types or union types directly in type annotations. This is convenient, but usually, we prefer to refer to a type multiple times by a single name.
The type alias is used to do this-it can be used as a name to refer to any type. The syntax of a type alias is as follows:
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 });
More than object types, you can use type aliases for any type. For example, you can name the union type:
type ID = number | string;
Note that aliases are just aliases-you cannot use type aliases to create different "versions" of the same type. When you use an alias, the effect is the same as if you directly wrote the actual type. In other words, the code looks illegal, but in TypeScript it is okay. Both the alias and the actual type refer to the same type:
type UserInputSanitizedString = string;
function sanitizeInput(str: string): UserInputSanitizedString {
return sanitize(str);
}
// 创建一个输入
let userInput = sanitizeInput(getInput());
// 可以重新给它赋值一个字符串
userInput = "new input";
interface
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 using type aliases above, this example can also run normally, and its effect is the same as using an anonymous object type directly. TypeScript only cares about printCoord
-it only cares about whether the value has the desired properties. It is precisely because of this feature that only focuses on the structure and capabilities of types, so we say that TypeScript is a structured and typed type system.
The difference between type alias and interface
Type aliases are very similar to interfaces. In most cases, you can choose one of them to use. Almost all features of the interface can be used in type aliases. The key difference between the two is that the type alias cannot be "opened" again and add new attributes, and the interface can always be expanded.
// 接口可以自由拓展
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'
In later chapters, you will learn more about this, so it doesn’t matter if you don’t understand it yet.
- Before TypeScript 4.2, the name of the type alias may appear in the error message, sometimes replacing the equivalent anonymous type (may or may not be needed). The name of the interface always appears in the error message
- Type alias cannot be declaration, but the interface can be
- The interface can only be used to , and cannot name the original type
- In the error message, the name of the interface will always appear in the original form, but only when they are used as names
In most cases, you can choose one of them according to your personal preference, and TypeScript will also tell you whether it needs to use another declaration method. If you like heuristics, you can use interfaces, and then use type aliases when you need to use other features.
Type assertion
Sometimes, you know the type of a value better than TypeScript.
For example, if you use document.getElementById
, TypeScript only knows that this call will return a certain HTMLElement
, but you know that your page always has a HTMLCanvasElement
a given ID.
In this case, you can use type assertions to specify a more specific type:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
Just like type annotations, the compiler will eventually remove the type assertion to ensure that it will not affect the runtime behavior of the code.
You can also use the equivalent angle bracket syntax (provided that the code is not in a .tsx
file):
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
Remember: because type assertions are removed during compilation, there are no runtime checks related to type assertions. Even if the type assertion is wrong, it will not throw an exception or generate null
TypeScript only allows the type after the assertion to be more specific or less specific than the previous type. This rule can prevent the following "impossible" forced type conversions:
const x = "hello" as number;
// 类型 "string" 到类型 "number" 的转换可能是错误的,因为两种类型不能充分重叠。如果这是有意的,请先将表达式转换为 "unknown"
Sometimes, this rule may be too conservative and will prevent us from performing more complex and effective conversion operations. If so, you can use a two-step assertion, first asserting as any
(or unknown
, which will be introduced later), and then asserting as the desired type:
const a = (expr as any) as T;
Literal type
In addition to the general string
and number
types, we can also regard specific strings or numbers as a type.
How to understand? In fact, we only need to consider the different ways JavaScript declares variables. var
and let
can be modified, but const
not. This characteristic is reflected in how TypeScript creates types for literals.
let changingString = "Hello World";
changingString = "Olá Mundo";
// 因为 changingString 可以表示任意可能的字符串,这是 TypeScript
// 在类型系统中描述它的方式
changingString;
^^^^^^^^^^^^^^
// let changingString: string
let changingString: string
const constantString = "Hello World";
// 因为 constantString 只能表示一种可能的字符串,所以它有一个
// 字面量类型的表示形式
constantString;
^^^^^^^^^^^^^^^
// const constantString: "Hello World"
When used alone, the literal type is not very useful:
let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";
// Type '"howdy"' is not assignable to type '"hello"'.
In the above example, the variable has only one possible value, which is meaningless!
But by combining literal types into union types, you can express a more practical concept—for example, to declare a function that only accepts certain fixed values:
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, the union type can also contain 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. There are only two types of Boolean literals, namely true
and false
. boolean
type itself is actually an alias of the true | false
Literal inference
When you initialize a variable to an object, TypeScript assumes that the properties of the object may change later. For example, the following code:
const obj = { counter: 0 };
if (someCondition) {
obj.counter = 1;
}
TypeScript does not feel that assigning a value of 1 to a property that was previously 0 is an error. Another perspective is that obj.counter
must be of number
, not 0, because the type can be used to determine the read and write behavior.
The same is true for strings:
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"'.
(Translator's Note: Here handleRequest
signature (url: string, method: "GET" | "POST") => void
)
In the example above, req.method
is inferred to be string
instead of "GET"
. Because other code may be executed between the creation of req
and the call of handleRequest
req.method
may be assigned "GUESS"
, so TypeScript will consider such code to be wrong.
There are two ways to solve this problem:
Change the inference result of the type by adding a type assertion:
// 方法一: const req = { url: "https://example.com", method: "GET" as "GET" }; // 方法二: handleRequest(req.url, req.method as "GET");
Method One said, "I intend to give req.method
has been using a literal type "GET"
", thereby preventing subsequent assigned to it with other strings; Method II said, "For some reason, I'm sure req.method
value must be “GET”
."
You can also use
as const
convert the entire object into a literal type:const req = { url: "https://example.com", method: "GET" } as const; handleRequest(req.url, req.method);
as const
suffix const
the same effect as 061a1e58b3f1a4, but it is used in the type system. It can ensure that all attributes of the object are given a literal type, instead of using a more general type string
or number
null
and undefined
There are two primitive values in JavaScript that represent missing or uninitialized values: null
and undefined
.
Correspondingly, TypeScript also has two types with the same names. Their behavior depends on whether you enable the strictNullChecks option.
Disable strictNullChecks
After disabling the strictNullChecks option, you can still access null
and undefined
. These two values can also be assigned to any type. This behavior is similar to languages that lack null checking (such as C#, Java). The lack of checking for these values may be the source of a large number of bugs. If feasible, we recommend that developers always enable the strictNullChecks option.
Enable strictNullChecks
After enabling the strictNullChecks option, when a value is null
or undefined
, you need to check it before using the method or property of the value. undefined
before using an optional attribute, we can use type narrowing to check whether a value is possible to be null
:
function doSomething(x: string | null) {
if (x === null) {
// do nothing
} else {
console.log("Hello, " + x.toUpperCase());
}
}
Non-null value assertion operator ( !
suffix)
TypeScript also provides a special syntax that can null
and undefined
from the type without explicitly checking. Adding the suffix !
after any expression can effectively assert that a value cannot be null
or undefined
:
function liveDangerously(x?: number | null) {
// 不会报错
console.log(x!.toFixed());
}
And other types of assertions as non-null value assertion does not change the runtime behavior of the code, so remember: only you can not determine if a value is null
or undefined
when we come to use !
.
enumerate
Enumeration is a feature added to JavaScript by TypeScript. It allows describing a value, which can be one of a set of possible named constants. Unlike most TypeScript features, enumerations are not added to JavaScript at the type level, but to the language itself and its runtime. Because of this, you should understand the existence of this feature, but unless you are sure, you may need to postpone using it. You can learn more about enumeration enumeration reference page
Other uncommon primitive types
It is worth mentioning that other primitive types of JavaScript also have corresponding representations in the type system. But we will not go into it in depth here.
BigInt
ES2020 introduced BigInt
to represent very large integers in JavaScript:
// 通过 BigInt 函数创建大整数
const oneHundred: bigint = BigInt(100);
// 通过字面量语法创建大整数
const anotherHundred: bigint = 100n;
You can learn more about BigInt TypeScript 3.2 release log
symbol
In JavaScript, we can create a globally unique reference Symbol()
const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {
// 此条件将始终返回 "false",因为类型 "typeof firstName" 和 "typeof secondName" 没有重叠。
}
You can learn more about it on the Symbol reference page
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。