1
头图

Preface

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 organized from the " The Basics " chapter in the TypeScript Handbook.

This article does not strictly follow the original translation, but also explains and supplements part of the content.

text

Each value of JavaScript will behave differently when performing different operations. This sounds a bit abstract, so let's take an example, suppose we have a message , just imagine what we can do:

// Accessing the property 'toLowerCase'
// on 'message' and then calling it
message.toLowerCase();
// Calling 'message'
message();

The first line of code is to get the attribute toLowerCase and then call it. The second line of code calls message directly.

But in fact, we message the value of 061d45c7fbfc86, and naturally we don't know the execution result of this code. Each operation behavior depends first on what value we have.

  • message callable?
  • message have an attribute toLowerCase
  • If so, toLowerCase be called?
  • If these values can be called, what will they return?

When we write JavaScript, we need to keep the answers to these questions in mind and expect to handle all the details.

Let us assume that message is defined like this:

const message = "Hello World!";

You can completely guess the result of this code. If we try to run message.toLowerCase() , we can get the lowercase form of this character.

What about the second code? If you are familiar with JavaScript, you must know that the following errors will be reported:

TypeError: message is not a function

It would be great if we can avoid such errors.

When we run the code, JavaScript will first calculate the type of the value at runtime, and then decide what to do. The so-called type of value also includes what behavior and capabilities the value has. Of course, TypeError will also tell us one thing implicitly. For example, in this example, it tells us that the string Hello World cannot be called as a function.

For some values, such as the basic values string and number , we can use the typeof operator to confirm their type. But for other functions, such as functions, there is no corresponding method to confirm their type. For example, consider this function:

function fn(x) {
  return x.flip();
}

We can know by reading the code that the function will execute normally flip But JavaScript does not reflect this information when the code is executed. In JavaScript, the only way to know what fn when 061d45c7fbfe93 is passed in a special value is to call it and see what happens. This behavior makes it difficult for you to predict the results of code execution before the code runs. It also means that when you write the code, it is more difficult for you to know what will happen to your code.

From this perspective, the type describes what kind of value can be passed to fn , and what kind of value will cause a crash. JavaScript only provides dynamic typing, which requires you to run the code first and then see what happens.

The alternative is to use a static type system to predict what code is needed before the code runs.

Static type-checking

Let us look back to this string be called as a function generated TypeError , most people do not like to get an error when running the code. These will be considered bugs. When we write new code, we also try to avoid creating new bugs.

If we add a bit of code, save the file, and then rerun the code, we can see the error immediately. We can locate the problem quickly, but this is not always the case. For example, if we do not do sufficient testing, we will encounter Less likely to go wrong. Or if we are lucky enough to see this error, we may have to do a major refactoring and add a lot of different code to find out the problem.

Ideally, we should have a tool that can help us find errors before the code runs. This is what static type checkers such as TypeScript do. Static types systems describe the structure and behavior of values. A type checker like TypeScript will use this information and tell us when something might go wrong:

const message = "hello!";
 
message();

// This expression is not callable.
// Type 'String' has no call signatures.

In this example, TypeScript will throw an error message before it runs.

Non-exceptional failure (Non-exception failure)

So far, what we have discussed are runtime errors. The so-called runtime errors are when JavaScript tells us something that it thinks is meaningless at runtime. These things happen because the ECMAScript specification has clearly stated the behavior of these abnormalities.

For example, the specification states that an error should be thrown when calling a non-callable thing. It may sound like a matter of course, so you may think that if you get an object that does not exist, an error should be thrown, but JavaScript does not do this, it does not report an error, and returns the value undefined .

const user = {
  name: "Daniel",
  age: 26,
};
user.location; // returns undefined

A static type needs to mark which code is an error, even if the actual JavaScript does not report an error immediately. In TypeScript, the following code will generate an location does not exist:

const user = {
  name: "Daniel",
  age: 26,
};
 
user.location;
// Property 'location' does not exist on type '{ name: string; age: number; }'.

Although sometimes this means that you need to make some trade-offs when expressing, the purpose is to find some reasonable errors in our project. TypeScript can now catch many reasonable errors.

For example, such as spelling mistakes:

const announcement = "Hello World!";
 
// How quickly can you spot the typos?
announcement.toLocaleLowercase();
announcement.toLocalLowerCase();
 
// We probably meant to write this...
announcement.toLocaleLowerCase();

Function is not called:

function flipCoin() {
  // Meant to be Math.random()
  return Math.random < 0.5;
// Operator '<' cannot be applied to types '() => number' and 'number'.
}

Basic logic error:

const value = Math.random() < 0.5 ? "a" : "b";
if (value !== "a") {
  // ...
} else if (value === "b") {
  // This condition will always return 'false' since the types '"a"' and '"b"' have no overlap.
  // Oops, unreachable
}

Types for Tooling

TypeScript can not only find errors when we make mistakes, but also prevent us from making mistakes.

Because the type checker has type information, it can check, for example, whether the attribute of a variable is correctly obtained. It is precisely because of this information that it can also list the attributes you may want to use as you type.

This means that TypeScript is also very helpful for you to write code. The core type checker can not only provide error information, but also provide code completion. This is the role of TypeScript in terms of tools.

TypeScript is very powerful, except for providing completion and error messages when you type. It can also support the "quick fix" function, which automatically fixes errors and reorganizes the code into a clear organization. It also supports navigation functions, such as jumping to the place where a variable is defined, or finding all references to a given variable.

All these functions are built on the type checker and are supported across platforms. is possible that your favorite editor already supports TypeScript.

tsc TypeScript compiler (tsc, the TypeScript compiler)

So far we have only discussed type checkers, but they have not been used yet. Now let us understand our new friend tsc -TypeScript compiler. First, we can install it via npm:

npm install -g typescript
This will install the TypeScript compiler globally. If you want to tsc in a local node_modules , you can also use npx or similar tools.

Let's create an empty folder, and then write our first TypeScript program: hello.ts :

// Greets the world.
console.log("Hello world!");

Note that there is no superfluous modification here. This hello world project is the same as if you wrote it in JavaScript. Now you can run the tsc command to perform type checking:

tsc hello.ts

Now we have run tsc , but you will find that nothing happened. This is true, because there is no type error here, so there will be no output on the command line.

But if we check again, we will find that we have got a new file. Checking the current directory, we will find that hello.ts same level directory of hello.js , which is the output file of hello.ts tsc will compile the ts file into a pure JavaScript file. Let's take a look at the compiled files:

// Greets the world.
console.log("Hello world!");

In this example, because TypeScript does not have anything to be compiled and processed, it looks the same as what we wrote. The compiler will output as clean code as possible, just like a normal developer writes. Of course, this is not an easy task, but TypeScript will insist on doing this, such as keeping indentation, paying attention to cross-line code, keeping comments, etc.

What if we insist on generating a type checking error? We can write hello.ts like this:

// This is an industrial-grade general-purpose greeter function:
function greet(person, date) {
  console.log(`Hello ${person}, today is ${date}!`);
}
 
greet("Brendan");

At this point we will run tsc hello.ts . This time we will get an error in the command line:

Expected 2 arguments, but got 1.

TypeScript tells us that one less parameter was passed to the greet function.

Although we write standard JavaScript, TypeScript can still help us find errors in the code, cool~.

Documents are still output when an error is reported (Emitting with Errors)

In the example just now, there is one detail you may not have noticed, that is, if we open the compiled file, we will find that the file still has changes. Is this strange? tsc clearly reported an error, why do I need to compile the file again? This brings us to a core point of TypeScript: Most of the time, you have to know your code better than TypeScript.

For example, if you are migrating your code to TypeScript, this will generate a lot of type checking errors, and you have to deal with all the errors for the type checker. At this time, you have to think about it, obviously the previous code can Normally, why does TypeScript prevent the code from running normally?

So TypeScript will not hinder you. Of course, if you want TypeScript to be stricter, you can use the noEmitOnError compilation option, try to change your hello.ts file, and then run tsc :

tsc --noEmitOnError hello.ts

You will find that hello.ts will not be updated.

Explicit Types

Until now, we haven't told TypeScript person and date are. Let's edit the code and tell TypeScript that person is a string type, and date is a Date object. We use date of toDateString() method.

function greet(person: string, date: Date) {
  console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}

What we have done is to give person and date added type annotations (Annotations of the type) , described greet function can support what the values passed. So you can understand the signature (Signature) : greet support passing a string type of person and a Date type of date .

After adding type annotations, TypeScript can prompt us, for example, when greet is called by mistake:

function greet(person: string, date: Date) {
  console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
 
greet("Maddison", Date());
// Argument of type 'string' is not assignable to parameter of type 'Date'.

TypeScript prompts that there is an error in the second parameter. Why is this?

This is because calling Date() in JavaScript will return a string . Using new Date() will produce a value of type Date

We quickly fix this problem:

function greet(person: string, date: Date) {
  console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
 
greet("Maddison", new Date());

Remember, we don’t always need to write type annotations. Most of the time, TypeScript can automatically infer the type:

let msg = "hello there!";
// let msg: string

Although we did not tell TypeScript that msg is string , it still inferred the type. This is a feature. If the type system can correctly infer the type, it is best not to add type annotations manually.

Erased Types

What will TypeScript compile into the code in the previous example? Let's take a look:

"use strict";
function greet(person, date) {
    console.log("Hello " + person + ", today is " + date.toDateString() + "!");
}
greet("Maddison", new Date());

Pay attention to two things:

  1. Our person and date parameters no longer have type annotations
  2. The template string, that is ` is converted to use the + number 061d45c7fc0799

Let's look at the first point first. Type annotations are not part of JavaScript. So there is no browser or runtime environment that can directly run TypeScript code. This is why TypeScript needs a compiler. It needs to convert TypeScript code into JavaScript code before you can run it. So most of the code unique to TypeScript will be erased. In this example, all type annotations like ours will be erased.

谨记:类型注解并不会更改程序运行时的行为

Downleveling

Let's focus on the second point again. The original code is:

`Hello ${person}, today is ${date.toDateString()}!`;

Was compiled into:

"Hello " + person + ", today is " + date.toDateString() + "!";

Why do you want to do this?

This is because template strings are features in ECMAScript2015 (also known as ECMAScript 6, ES2015, ES6, etc.). TypeScript can compile new versions of codes into old versions, such as ECMAScript3 or ECMAScript5. This process of converting a higher version of ECMAScript syntax to a lower version is called downleveling .

TypeScript is converted to ES3 default, a very old version of ECMAScript. We can also use the target option to convert to some newer versions. For example, executing --target es2015 will convert to ECMAScript 2015, which means that the converted code can be run in any place that supports ECMAScript 2015.

Execute tsc --target es2015 hello.ts , let us look at the code compiled into ES2015:

function greet(person, date) {
  console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", new Date());
Although the default target is the ES3 version, most browsers already support ES2015, so most developers can safely specify ES2015 or a newer version, unless you have to be compatible with a problematic browser.

Strict mode (Strictness)

Different users use TypeScript to focus on different things. Some users will look for a more relaxed experience, which can help check part of the code in their programs, and they can also enjoy the tool features of TypeScript. This is the default development experience of TypeScript. The type is optional. It is inferred that it will be compatible with most types. There is no mandatory check for the value null / undefined Just like tsc will still output files when compiling errors, these default options will not hinder your development. If you are migrating JavaScript code, you can use this method from the very beginning.

In sharp contrast, there are many users who want TypeScript to check the code as much as possible, which is why this language provides strict mode settings. But unlike the form of a toggle switch (either check or not), the form provided by TypeScript is more like a dial, the more you turn it, the more content TypeScript will check. This requires a little extra work, but it is worth it. It can bring more comprehensive inspections and more accurate tool functions. If possible, new projects should always enable these strict settings.

TypeScript has several switches for strict mode settings. Unless otherwise specified, the examples in the document are written in strict mode. The strict configuration item in 161d45c7fc09fe "strict": true can be turned on at the same time or set separately. Among these settings, the most important thing you need to know is noImplicitAny and strictNullChecks .

noImplicitAny

At some point, TypeScript does not infer the type for us, at this time it will fall back to the broadest type: any . This is not the worst thing, after all, any nothing like writing JavaScript.

However, the frequent use of any defeats our purpose of using TypeScript. The more types you use in your program, the more help you get with verification and tools, which also means that you will encounter fewer bugs when writing code. After enabling the noImplicitAny configuration item, when the type is implicitly inferred to be any , an error will be thrown.

strictNullChecks

By default, null and undefined can be assigned to other types. This allows us to write some more code. But forgetting to deal with null and undefined also caused a lot of bugs. Some people even call it million-worth error ! strictNullChecks option will let us deal with null and undefined more clearly, and will also save us from worrying about whether we have forgotten to deal with null and undefined .

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.


冴羽
9.3k 声望6.3k 粉丝