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.
Type Aliases
We have learned to directly use object types and union types in type annotations, which is very convenient, but sometimes, a type will be used multiple times, and we would prefer to refer to it by a single name.
This is the type alias (type alias). The so-called type alias, as the name implies, a name that can refer to any type. The syntax of 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 });
You can use type aliases to give any type a name, for example, to name a union type:
type ID = number | string;
Note that aliases are the only 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 it is still legal for 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";
Interfaces
An 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 the type alias we used in the previous section, this example can also work, just as we used an anonymous object type. TypeScript only cares about printCoord
-it cares whether the value has the desired properties. It is this characteristic that only cares about the structure and capabilities of types that we consider TypeScript to be a structurally type system.
The difference between type aliases and interfaces
Type aliases are very similar to interfaces, and most of the time, you can choose to use them at will. Almost all the characteristics of the interface can be used in type
. The most important difference between the two is that the type alias itself cannot add new attributes, and the interface can be extended.
// Interface
// 通过继承扩展类型
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
const bear = getBear()
bear.name
bear.honey
// Type
// 通过交集扩展类型
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
const bear = getBear();
bear.name;
bear.honey;
// Interface
// 对一个已经存在的接口添加新的字段
interface Window {
title: string
}
interface Window {
ts: TypeScriptAPI
}
const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});
// Type
// 创建后不能被改变
type Window = {
title: string
}
type Window = {
ts: TypeScriptAPI
}
// Error: Duplicate identifier 'Window'.
You will learn more in subsequent chapters. So it doesn't matter if the following content cannot be understood immediately:
- Before TypeScript 4.2, type alias may appear in the error message , sometimes replacing the equivalent anonymous type (maybe not expected). The name of the interface will always appear in the error message.
- Type aliases may not implement declaration merging, but the interface can be
- The interface may only be used for declare the shape of the object, the original type
- When the interface is used by name, their name will , if used directly, the original structure
Most of the time, you can choose according to personal preference. TypeScript will tell you if it needs other declarations. If you like exploratory use, use interface
until you need to use the features of type
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 a HTMLElement
, but you know that what you want to get is a HTMLCanvasElement
.
At this time, you can use type assertions to specify it as a more specific type:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
Just like type annotations, type assertions will also be removed by the compiler and will not affect any runtime behavior.
You can also use angle bracket syntax (note that it cannot be used in the .tsx
file), which is equivalent:
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
Remember: because the type assertion will be removed at compile time, there will be no type assertion check at runtime. Even if the type assertion is wrong, there will be no exception or null
.
TypeScript only allows type assertions to be converted to a more specific or less specific type. This rule can prevent some impossible forced type conversions, 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.
Sometimes, this rule is very conservative and prevents you from valid type conversions. If this happens, you can use a double assertion, first assert as any
(or unknown
), and then assert as the desired type:
const a = (expr as any) as T;
Literal Types
In addition to the common types string
and number
, we can also declare the types as more specific numbers or strings.
As we all know, there are many ways to declare variables in JavaScript. For example, var
and let
, variables declared in this way can be modified later, and const
, variables declared in this way cannot be modified, which will affect TypeScript 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
const constantString = "Hello World";
// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
constantString;
// const constantString: "Hello World"
The literal type itself is not very useful:
let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";
// Type '"howdy"' is not assignable to type '"hello"'.
If combined with the joint type, it becomes much more useful. For example, when the function can only pass in some 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"'.
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, you can also combine 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"'.
There is also a literal type, Boolean literals. Because there are only two Boolean literal types, true
and false
, the type boolean
actually an alias for the joint type true | false
Literal Inference
When you initialize a variable to an object, TypeScript will assume that the value of the object's properties will be modified in the future. For example, if you write code like this:
const obj = { counter: 0 };
if (someCondition) {
obj.counter = 1;
}
TypeScript does not think that obj.counter
to be 0
, and it is now assigned the value 1
be an error. In other words, obj.counter
must be of string
, but it is not required to be 0
, because the type can determine the 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"'.
In the above example, req.method
be inferred string
, rather than "GET"
, because creating req
and calls handleRequest
between function, there may be other code that perhaps would req.method
assign a new string such as "Guess"
. So TypeScript reported an error.
There are two ways to solve it:
- Add a type assertion to change the inference result:
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");
Modification 1 means "I deliberately let req.method
be the literal type "GET"
, which will prevent possible assignment of "GUESS"
future." Amendment 2 said, "I know req.method
value is "GET"
."
- You can also use
as const
to convert the entire object into a type literal:
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
as const
effect of const
similar to 061d45c5e97b72, but for the type system, it can ensure that all attributes are given a literal type instead of a more general type such as string
or number
.
null
and undefined
JavaScript has two primitive values that are used to indicate vacancy or uninitialized. They are null
and undefined
.
TypeScript has two corresponding types with the same name. Their behavior depends on whether the strictNullChecks option is turned on.
strictNullChecks
closed
When the strictNullChecks option is turned off, if a value may be null
or undefined
, it can still be accessed correctly or assigned to any type of attribute. This is a bit similar to languages without null checking (such as C#, Java). The lack of these checks is the main source of bugs, so we always recommend developers to enable the strictNullChecks option.
strictNullChecks
open
When the strictNullChecks option is turned on, if a value may be null
or undefined
, you need to check these values before using its methods or attributes, just like the optional attributes, check if they are undefined
. We can also use type narrowing to check if the value is null
:
function doSomething(x: string | null) {
if (x === null) {
// do nothing
} else {
console.log("Hello, " + x.toUpperCase());
}
}
Non-null Assertion Operator (Suffix !
) (Non-null Assertion Operator)
TypeScript provides a special grammar that can remove null
and undefined
from the type without any checks. This is to write !
after any expression, which is a valid type assertion that represents its value It cannot be null
or undefined
:
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
Just like other type assertions, this will not change any runtime behavior. The important thing to say it again, only if you know exactly the value can not be null
or undefined
when using !
.
Enumeration (Enums)
Enumeration is a new feature added by TypeScript, used to describe that a value may be one of multiple constants. Unlike most TypeScript features, this is not a type-level increment, but will be added to the language and runtime. Because of this, you should understand this feature. But you can wait and use it again, unless you are sure to use it. You can learn more about the enumerated type
Less Common Primitives
Let's mention some primitive types remaining in JavaScript. But we will not go into it in depth.
bigInt
ES2020 introduces the original type BigInt
, which is used 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;
You can learn more TypeScript 3.2 release log
symbol
This is also a primitive type in JavaScript. Through 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
}
You can learn more about Symbol on page
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) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。