preface

The official TypeScript documentation has been updated for a long time, but the Chinese documentation I could find was still in older versions. Therefore, some new and revised chapters are translated and sorted out.

This article is adapted from “The Basics” in The TypeScript Handbook.

This paper does not strictly follow the translation of the original text, and some of the content has also been explained and supplemented.

The body of the

Each value in JavaScript behaves differently when performing different operations. This sounds a bit abstract, so let’s take an example. Suppose we had a variable named message and imagine what we could do:

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

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

We don’t even know the value of message, nor do we know the result of this code. Every action depends first on what value we have.

  • messageIs it callable?
  • messageThere’s a group calledtoLowerCaseIs the property of?
  • If you have,toLowerCaseIs it callable?
  • If these values could all be called, what would they return?

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

Let’s assume that message is defined like this:

const message = "Hello World!";
Copy the code

As you can probably guess, if we try to run message.tolowerCase (), we get the character in lower case.

What about the second piece of code? If you’re familiar with JavaScript, you probably know the following error:

TypeError: message is not a function
Copy the code

It would be nice if we could avoid such errors.

When we run code, JavaScript calculates the type of the value at runtime and then decides what to do. The type of a value also includes the behavior and capabilities of the value. TypeError also implicitly tells us, in this case, that the string Hello World cannot be called as a function.

For some values, such as string and number, we can use the Typeof operator to confirm their type. For other functions, however, there is no corresponding way to determine their type. For example, consider this function:

function fn(x) {
  return x.flip();
}
Copy the code

As you can see from reading the code, the function only executes when passed to an object with a callable flip property. But JavaScript doesn’t show this information when the code executes. In JavaScript, the only way to know what happens to fn when it is passed a particular value is to call it and see what happens. This behavior makes it harder to predict code execution before it’s even run, which means it’s harder to know what’s going to happen to your code when you write it.

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

The alternative is to use a static type system that predicts what code will be needed before it runs.

Static type-checking

Recall the TypeError generated by calling string as a function. Most people don’t like to get an error when running code. These are 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 re-run the code, we can see the error immediately. We can locate the problem very quickly, but not always. For example, if we don’t do enough testing, we won’t see the possibility that something could go wrong. Or if we were lucky enough to see the error, we might have to do a big refactoring and add a lot of different code to figure it out.

Ideally, we should have a tool that helps us find errors before the code even runs. This is what static type checkers like TypeScript do. Static types Systems describe the structure and behavior of values. A TypeScript type checker takes advantage of this information and tells us when something might be wrong:

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

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

In this case, TypeScript throws an error message before it runs.

Non-exception failure

So far, we’ve been talking about runtime errors, runtime errors, where JavaScript tells us something at runtime that it thinks doesn’t make sense. These things happen because the ECMAScript specification explicitly states how these exceptions should behave.

For example, the specification states that an error should be thrown when calling something that is not callable. This might sound obvious, so you might think that if you get a property of an object that doesn’t exist, you should also throw an error, but JavaScript doesn’t. It doesn’t return an error and returns undefined.

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

A static type needs to flag which code is an error, even if the actual working JavaScript doesn’t immediately report an error. In TypeScript, the following code generates an error stating that location does not exist:

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

Although sometimes this means making trade-offs in your presentation, the goal is to find legitimate mistakes in our project. TypeScript can now catch many legitimate errors.

For example, 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();
Copy the code

Function not called:

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

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
}
Copy the code

Types for Tooling

TypeScript not only catches us when we make mistakes, it also prevents us from making them.

The type checker, because it has type information, can check that, for example, a variable’s attributes are correctly fetched. Because of this information, it can also list the attributes you might want to use as you type.

This means that TypeScript is also very useful for writing code, and the core type checker provides error information as well as code completion. That’s where TypeScript comes in as a tool.

TypeScript is powerful in addition to providing completion and error information as you type. It also supports “quick fix” functionality, which automatically fixes bugs and reconstructs well-organized code. It also supports navigation, such as jumping to where a variable is defined, or finding all references to a given variable.

All of these features are built on top of type checkers and are cross-platform. Chances are your favorite editor already supports TypeScript.

tscThe TypeScript compiler (TSC)

So far we’ve only talked about type checkers, but we haven’t used them yet. Now let’s take a look at our new friend the TSC – TypeScript compiler. First, we can install it via NPM:

npm install -g typescript
Copy the code

This installs the TypeScript compiler globally, but if you want to install TSC in a local node_modules, you can also use NPX or similar tools.

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

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

Note that there is no extra embellishment here, the Hello World project is just as good as if you were writing it in JavaScript. Now you can run the TSC command to perform type checking:

tsc hello.ts
Copy the code

Now that we’ve run TSC, you’ll notice that nothing has happened. Exactly, because there are no type errors, there is no output from the command line.

But if we check again, we’ll see that we’ve got a new file. This is the output of the hello.ts file. TSC will compile the ts file into a pure JavaScript file. Let’s look at the compiled output file:

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

In this case, because TypeScript doesn’t compile anything, it looks the same as what we wrote. The compiler outputs as clean code as possible, just like a normal developer would. It’s not easy, but TypeScript insists on it by keeping indentation, cross-line code, comments, etc.

What if we insist on producing 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");
Copy the code

Let’s run TSC Hello.ts again. This time we will get an error on the command line:

Expected 2 arguments, but got 1.
Copy the code

TypeScript tells us to pass one less argument to the greet function.

Even though we’re writing standard JavaScript, TypeScript still helps us find errors in our code. Cool.

Emitting with Errors (Emitting with Errors)

One detail you may not have noticed in the example above is that if we open the compiled output file, we will find that the file is still changed. Isn’t that a little strange? TSC has already reported an error, why should it compile the file again? This gets to the heart of TypeScript’s point: most of the time, you know your code better than TypeScript does.

For example, if you are migrating your code to TypeScript, there are a lot of type-checking errors, and you have to deal with them for the type-checker. Why should TypeScript prevent your code from working when it already works?

So TypeScript doesn’t hold you back. Of course, if you want TypeScript to be harsher, you can use the noEmitOnError compilation option, try modifying your hello.ts file, and run TSC:

tsc --noEmitOnError hello.ts
Copy the code

You’ll notice that hello. Ts doesn’t get updated.

Explicit Types

So far, we haven’t told TypeScript what type person and date are. Let’s edit the code to tell TypeScript that Person is a string and date is a date object. We also use the toDateString() method of date.

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

What we’ve done is add Type annotations to Person and Date to describe what values the greet function can support. The greet signature supports passing in a person of type string and a Date of type Date.

By adding type annotations, TypeScript can tell us, for example, if greet is called incorrectly:

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'.
Copy the code

TypeScript tells you that the second argument has an error. Why?

This is because calling Date() in JavaScript returns a string. Use new Date() to produce a value of type Date.

Let’s quickly fix this:

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

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

let msg = "hello there!";
// let msg: string
Copy the code

Although we don’t tell TypeScript that MSG is a string value, it still infers the type. This is a feature, and if the type system can correctly infer the type, it is best not to manually add type annotations.

Erased Types

How does TypeScript compile 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());
Copy the code

Notice two things:

  1. ourpersondateParameters no longer have type annotations
  2. Template string, that is, the wrapped string is converted to use+No connection

Let’s look at the first point. Type annotations are not part of JavaScript. So no browser or runtime environment can run TypeScript code directly. That’s why TypeScript needs a compiler that converts TypeScript code to JavaScript code before you can run it. So most typescript-specific code is erased, and in this case, our type annotations are erased altogether.

Remember: Type annotations do not change the behavior of your program at runtimeCopy the code

Downleveling

Let’s focus on the second point. The original code was:

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

Compiled to:

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

Why do you do that?

This is because template strings are a feature of ECMAScript2015 (also known as ECMAScript 6,ES2015, ES6, etc.). TypeScript compilers from newer versions of code into older versions of code. For example, ECMAScript3 or ECMAScript5. This process of turning a high version of ECMAScript syntax into a low version is called downleveling.

TypeScript defaults to ES3, a very old version of ECMAScript. We can also convert to newer versions using the Target option, such as execute — Target ES2015 will be converted to ECMAScript 2015, which means the converted code can run anywhere ECMAScript 2015 is supported.

TSC –target es2015 hello.ts

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

Although the default target is ES3, most browsers already support ES2015, so most developers can safely specify ES2015 or later unless you want to be compatible with a problem browser.

Strictness

Different users care about different things with TypeScript. Some users will be looking for a looser experience, both to help examine some of the code in their programs and to enjoy TypeScript’s tooling capabilities. This is the default TypeScript development experience. Types are optional, inference is compatible with most types, and there is no mandatory checking for null/ undefined values. Just like TSC will still output files when compiling errors, these default options should not hinder your development. If you are migrating JavaScript code, use this approach from the start.

In contrast, many users expect TypeScript to check their code as much as possible, which is why the language provides strict schema Settings. But instead of toggling, which is either checked or not checked, TypeScript provides a form that is more like a dial, and the more you turn it, the more TypeScript checks. This requires a little extra work, but it’s worth it, and it leads to more thorough checks and more accurate tool functionality. If possible, new projects should always turn these strict Settings on.

TypeScript has several strict schema setting switches. Unless otherwise noted, examples in the documentation are written in strict mode. The strict configuration item in the CLI, or “strict”: true in tsconfig.json, can be enabled at the same time or set separately. Of these Settings, the most important things you need to know are noImplicitAny and strictNullChecks.

noImplicitAny

At some point, TypeScript doesn’t infer types for us, so it falls back to the broadest type: any. This wouldn’t be the worst thing, since falling back to any would be like writing JavaScript.

However, using any too often defeats the purpose of using TypeScript. The more types you use, the more help you’ll get with validation and tools, which means fewer bugs when writing code. When the noImplicitAny configuration item is enabled, an error is thrown when the type is implicitly inferred to be any.

strictNullChecks

By default, values like null and undefined can be assigned to other types. This will allow us to write a little bit more code. But forgetting to handle null and undefined has led to a number of bugs, some might even call it a million-dollar error! The strictNullChecks option lets us handle null and undefined more explicitly, and also frees us from worrying about forgetting to handle null and undefined.

The TypeScript series

  1. TypeScript type narrowing
  2. The function of TypeScript
  3. TypeScript object type
  4. The generic of TypeScript
  5. TypeScript’s Keyof operator
  6. TypeScript’s Typeof operator
  7. TypeScript index access types
  8. TypeScript conditional types

Wechat: “MQyqingfeng”, add me Into Hu Yu’s only readership group.

If there is any mistake or not precise place, please be sure to give correction, thank you very much. If you like or are inspired by it, welcome star and encourage the author.