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.
Common Types (Everyday Types)
In this chapter, we will explain some of the most common types in JavaScript and the corresponding description methods. Note that the content of this chapter is not exhaustive, and subsequent chapters will explain more ways to name and use types.
Types can appear in many places, not just in type annotations. We have to learn not only the types themselves, but also where to use these types to generate new structures.
Let's review the most basic and common types first. These are the basis for building more complex types.
Original types: string
, number
and boolean
(The primitives)
JavaScript has three very commonly used primitive types : string
, number
and boolean
, each of which has a corresponding type in TypeScript. Their names are the same as the result you get by typeof
string
represents a string, such as "Hello, world"number
represents a number, such as42
, there is noint
orfloat
JavaScript, all numbers are of typenumber
boolean
represents a Boolean value, in fact there are two values:true
andfalse
The type names
String
,Number
andBoolean
(initial capitalization) are also legal, but they are some very rare special built-in types. So the type is alwaysstring
,number
orboolean
.
Array
To declare an [1, 2, 3]
, you need to use the syntax number[]
. This syntax can be applied to any type (for example, string[]
represents an array of strings). You may also see this writing Array<number>
, which is the same. We will introduce the T<U>
syntax in the generic chapter.
Note that[number]
andnumber[]
have different meanings, refer to the chapter tuple
any
TypeScript has a special type, any
. When you don't want a value to cause a type check error, you can set it to any
.
When a value is of any
, you can get any of its attributes (which will also be converted to any
), or call it like a function, assign it to a value of any type, or assign a value of any type Give it, or other grammatically correct operations, you can:
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;
any
type is very useful when you don't want to write a long type code, but just want TypeScript to know that a certain piece of code is no problem.
noImplicitAny
If you do not specify a type, TypeScript cannot infer its type from the context, and the compiler will default to the type any
If you always want to avoid this situation, after all TypeScript any
, you can turn on the compilation item noImplicitAny . When it is implicitly inferred to be any
, TypeScript will report an error.
Type Annotations on Variables
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 use the form of "type declaration on the left", such as int x = 0
; type annotations often follow the content of the type to be declared.
But most of the time, this is not necessary. Because TypeScript will automatically infer the type. For example, the type of a variable can be inferred based on the initial value:
// No type annotation needed -- 'myName' inferred as type 'string'
let myName = "Alice";
Most of the time, you don't need to learn the rules of inference. If you are just getting started, try to use as few type annotations as possible. You may be surprised that TypeScript only needs very little content to fully understand what is going to happen.
Function
Functions are the main method for JavaScript to pass data. TypeScript allows you to specify the type of input value and output value of a function.
Parameter Type Annotations
When you declare a function, you can add a type annotation after each parameter to declare what type of parameters the function can accept. The parameter type annotation follows the parameter name:
// Parameter type annotation
function greet(name: string) {
console.log("Hello, " + name.toUpperCase() + "!!");
}
When the parameters have type annotations, TypeScript will check the actual parameters of the function:
// Would be a runtime error if executed!
greet(42);
// Argument of type 'number' is not assignable to parameter of type 'string'.
Even if you do not type annotate the parameters, TypeScript will still check whether the number of incoming parameters is correct
Return Type Annotations
You can also add type annotations for the return value. The type annotation of the return value follows the parameter list:
function getFavoriteNumber(): number {
return 26;
}
Like variable type annotations, you don't always need to add return value type annotations. TypeScript will infer the return type of the function return
Like this example, the type annotations are the same as if they are not written, but some code libraries will explicitly specify the type of the return value, which may be due to the need to write documentation, prevent accidental modification, or just personal preference.
Anonymous Functions
Anonymous functions are a bit different from function declarations. When TypeScript knows how an anonymous function will be called, the parameters of the anonymous function will be automatically assigned the type.
This is an example:
// 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());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
// Contextual typing also applies to arrow functions
names.forEach((s) => {
console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
Although the parameters s
does not add type annotations, but TypeScript according to forEach
type of the function, and the type of the array passed, last inferred s
type.
This process is called contextual typing , because it is from the context in which the function appears that the type it should have is inferred.
Like inference rules, you don’t need to learn how it happens, just know that it does exist and help you save some unneeded notes. Later, we will see more examples of this, to understand how the context in which a value appears affects its type.
Object Types
In addition to primitive types, the most common type is the object type. To define an object type, we only need to simply list its attributes and corresponding types.
for example:
// 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 added a type to the parameter. This type has two attributes, x
and y
, both of which are of type number
You can use ,
or ;
separate the attributes, and the delimiter of the last attribute does not matter.
The type corresponding to each attribute is optional. If you do not specify it, the any
type is used by default.
Optional Properties
The object type can specify some or all attributes as optional, you only need to add a ?
after the attribute name:
function printName(obj: { first: string; last?: string }) {
// ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
In JavaScript, if you get a non-existent property, you will get a undefined
instead of a runtime error. Therefore, when you get an optional attribute, you need to check if it is undefined
before using it.
function printName(obj: { first: string; last?: string }) {
// Error - might crash if 'obj.last' wasn't provided!
console.log(obj.last.toUpperCase());
// Object is possibly 'undefined'.
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}
// A safe alternative using modern JavaScript syntax:
console.log(obj.last?.toUpperCase());
}
Union Types
The TypeScript type system allows you to use a series of operators to build new types based on existing types. Now that we know how to write some basic types, it's time to put them together.
Define a Union Type (Defining a Union Type)
The first way to combine types is to use a union type. A union type is a type composed of two or more types, and the value may be any of these types. Each of these types is members .
Let's write a function to handle strings or numbers:
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
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'.
Working with Union Types
It is easy to provide a value that conforms to the union type, you only need to provide a value that conforms to any union member type. So after you have a value of a union type, how do you use it?
What TypeScript requires you to do must be valid for each member of the union. For example, if you have a union type string | number
, you cannot use the method 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 use code to narrow the union type, just as you would use it without type annotations in JavaScript. When TypeScript can infer a more specific type based on the structure of the code, type narrowing occurs.
For example, TypeScript knows that using typeof
string
type 061d45c73f2606 will return the string "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);
}
}
To give another example, use a function, such as 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);
}
}
Note that in the else
branch, we don't need to do anything special. If x
is not string[]
, then it must be string
.
Sometimes, if each member in the union type has an attribute, for example, numbers and strings have the slice
method, you can use this attribute directly without type narrowing:
// Return type is inferred as number[] | string
function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}
You may be wondering why the joint type can only use the intersection of these types of attributes. Let us give an example. Now there are two rooms, one room is a person with a height of 8 feet and a hat, and the other room speaks Spanish. People in hats, after merging these two rooms, the only thing we know is: everyone wears a hat.
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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。