TS (TypeScript) is a superset of JavaScript types. It is a strongly typed programming language that can be compiled into pure JavaScript execution.

Ts is used to refer to TypeScript for ease of writing.

Ts has been used for some time. In order to learn TS more systematically, I carefully read the official documents and made some records and summaries. I hope this article can also help you to learn TS, lead you to get started.

Why use TS?

To start with my point: using TS is not necessarily good, you should make that decision based on your business needs. However, I think TS is a better choice than JS for a project with long-term maintenance or multiple participants.

In terms of error reduction, legibility, and development efficiency, I’ve summarized the following reasons, and if these reasons don’t convince you, you can give up TS.

Throw errors ahead of time

The ts type checking mechanism allows us to detect type errors at runtime before the editor throws them at code time.

Scenario 1: Avoid incorrect calls. []. Push (‘string’) returns the length of the Array and is of type number, so result.join(‘,’) will return an error.

let result = [].push('string');
console.log(result.join(', '));
// the attribute "join" does not exist on type "number" ~~~~.
Copy the code

Scenario 2: Avoid basic logic errors. At first glance, the following code looks fine, but if you look at it carefully, you will see that the else if condition does not execute, because value cannot be either ‘a’ or ‘b’, so this condition will never be true. So the else if condition is actually a redundant piece of code, which won’t have any impact on our program, but if we really don’t need it, it might be better to delete it.

const value = Math.random() < 0.5 ? "a" : "b";
if(value ! = ="a") {
  // ...
} else if (value === "b") {
  / / ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ this condition will always return "false", because "" type a" "and" "b" "there is no overlap
  // The code in this condition will not be executed
}
Copy the code

Easy to use functions

Scenario 1: Improve the readability of functions. There is no return value for the greet function (ts) using the greet type annotation (ts).

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

Scenario 2: Control the input and output of functions. The editor throws errors whenever a function actually passes fewer or more arguments than it needs to pass, or when it assigns a return value to another type of value. For example, we use the function we wrote above as follows:

greet("Brendan");
//~~~~~~~~~~~~~~ should have two arguments, but gets one.

greet("Brendan".'Sunday'.'test');
// ~~~~~ should have two arguments, but gets three.

let result: string = ' ';
result = greet("Brendan".' ');
//~~~~ cannot assign type "void" to type "string".
Copy the code

More efficient development

Anyone who has ever used TS knows that it is difficult to write type annotations in TS, so is it impossible to say that development is more efficient? It depends on what you mean by productive development, not that you’re busy every day or getting things done quickly.

Scenario 1: Reduce the cost of reading. If we don’t constrain a variable’s type, it can change to another type at any time, and I don’t know it’s a type until I’ve read all the code on it. This way, when we are maintaining a project, we actually spend a lot of time reading the previous code. Suppose you have the following code in js:

let result = ' '
if(...). { result =Number(result)
}else if(...). {/ /...
  result = str.split(' ')
  / /...
}else{
  / /...
  result = arr.join(', ')}Copy the code

Result starts as a string, and when it meets certain conditions it can become a number or Array, so I have to be very careful when using methods on string or Array. If the type of the variable is constrained in TS, then it will only be of this type and there will be no such concerns.

It is widely estimated that developers spend 70% of their code maintenance time reading it to understand it. It was a real eye-opener. It reached 70 percent. No wonder the average programmer writes about 10 lines of code a day. We spend seven hours a day reading code to understand how those 10 lines work!

Scenario 2: Friendlier editor prompts. Take this code for example:

interface IPerson {
  name: string.age: number.gender: 'male'|'woman'
}

const user1: IPerson = {
  name: 'Tom'.age: 30.gender: 'male'
}

const user2: IPerson = {
  name: 'Army'.age: 24.gender: 'woman'
}
Copy the code

We define an entity interface IPerson, and the objects user1 and user2 are both of type IPerson. When we set variables, the editor will give us attribute hints based on the attributes defined by the IPerson interface:

Gender is defined as the ‘male’ | ‘female’ literal type, have the following tips:

Type annotations

String, number, Boolean

String, Number, and Boolean are the three most common types, and their type annotations are the same as if you were retrieving strings using Typeof and are easy to understand.

const str: string = 'Hello World';
const num: number = 23;
const isTrue: boolean = false;
Copy the code

An Array of Array

Array types are related to the types of the elements in the array. For example, arrays that are all numbers can be represented in two ways:

const nums: number[] = [1.2.3];
/ / or
const nums: Array<number> = [1.2.3];
Copy the code

The same thing happens when the elements in an array are of other types.

tuples

We said that each element in an array is of the same type, so when the element type is different and the element type is fixed at each position, we call that a tuple type.

// Assigning an initialization value to a tuple requires supplying all items specified in the tuple type
let tom: [string.number] = ['Tom'.26];

It is also possible to assign only one item of the tuple, but to the specified type
let tom: [string.number];
tom[0] = 'Tom'; //ok
tom[0] = 12; //error
//~~~~ cannot assign type "number" to type "string"

// Can also be used? Said the optional
let tom: [string.number.string? ] = ['Tom'.26];
tom[2] = 'male'
Copy the code

any

Any indicates that the value can be of any type, and the following expressions are valid.

let obj: any = { x: 0 };
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
Copy the code

You can use any when you don’t want to do type checking, but that defeats the point of type checking and is recommended to avoid using any in your projects whenever possible.

Object type

Object type annotations, we usually list all the attributes of the object, and specify the type of each attribute.

const person:{ name: string; age? :number} = { name: 'Jonathan' };
Copy the code

The type annotation of the object can be used between each attribute. Or, separate. Each property of the object is optional. The name of the optional property is followed by? Represents, for example, that the age attribute of person in the code above is optional, i.e. a person is legal without the age attribute.

In JS, if you use an object property that does not exist, the property value is assumed to be undefined instead of a runtime error. So when you want to use an optional property of an object, you must check whether the property is undefined.

function printName(obj: { first: string, last? :string }) {
  // If the value of 'obj.last' is not provided, obj.last is undefined, which causes an error
  console.log(obj.last.toUpperCase());
  // check whether it is undefined.
  if(obj.last ! = =undefined) {
    // OK
    console.log(obj.last.toUpperCase());
  }
  // you can use the chain to write the method to judge, such writing is OK
  console.log(obj.last? .toUpperCase()); }Copy the code

The joint type

The ts typing mechanism allows us to create new types based on existing types. A union type is one of these types, which is a combination of two or more types called union members.

For example, a function argument may be of type number or string. If either type is passed, an error will be reported:

function printId(id: number | string) {
  console.log("Your ID is: " + id);
}

// OK
printId(101);

// OK
printId("202");

// Error
printId({ myID: 22342 });
// ~~~~~~~~~~~~~~ type "{myID: number; "String}" cannot be assigned to the parameter type | number "parameters.
// The type "{myID: number; } "assigned to type" number ".
Copy the code

When you declare union types for variables, TS checks to see if they are valid for each union member. Such as:

function printId(id: number | string) {
  console.log(id.toUpperCase());  
  / / ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ type "string | number" "toUpperCase" does not exist on the attribute.
  // Attribute "toUpperCase" does not exist on type "number".
}
Copy the code

The solution is to use Typeof to improve your code, make conditional judgments, and perform separate actions for different id types

function printId(id: number | string) {
  if (typeof id === "string") {
    // id is of type 'string'
    console.log(id.toUpperCase());
  } else {
    // id is type 'number'
    console.log(id); }}Copy the code

Type the alias

The object types and union types mentioned above are assigned to variables directly, but if the same type is used in more than one place, we can give the type a separate name, using its alias where it is used, rather than writing it everywhere.

Alias the object type:

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);  
}
const point: Point = { x: 100.y: 100 };
printCoord(point);
Copy the code

Alias the union type:

type ID = number | string;
Copy the code

You can alias any type, but an alias is just another name for that type; they’re essentially the same type.

interface

Another way to name object types is the interface, as the type alias mentioned above has a similar effect. In the example above, we can also change Point from type to interface declaration, which has the same effect:

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

Interface and type:

From the above example, we can see that interface and type are very similar in that they can describe an object or function. Most of the time you can choose to use type or interface as you like. There are two key differences:

  • scalability:interfaceuseextendsKeyword to extend properties;typethrough&To extend the properties.
// 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;
Copy the code
  • Consolidated statement:interfaceCan be repeated declaration, the new will merge with the old;typeCannot repeat the declaration, it will report an error
// 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: identifier "Window" is repeated.
Copy the code

In addition, the following things can be done by type but not by interface:

  • You can rename primitive types, union types, and so on
// Base type alias
type Name = string
// Union type
type ID = string | number
/ / yuan group
type List = [string.number]
Copy the code
  • usetypeofGet instance type
const div = document.createElement('div');
type DivElement = typeof div;
Copy the code

Summary: In general, interface is used to describe “data structures” and type is used to alias types.

Literal type

In addition to the string and number types, we can also define the specified content as a string or a number.

Var and let variables can be reassigned. Const variables cannot be reassigned. This affects ts’s ability to create literal types.

let changingStr = "Hello World";
changingStr = "Olá Mundo";
// 'changingStr' can be reassigned to any string, so it is of type string

const constantStr = "Hello World";
// 'constantStr' cannot be reassigned to "Hello World", so ts will infer that it is of type "Hello World"
Copy the code

This may not seem very useful, but you can see it when combined with union types. Let’s take a look at these examples:

Example 1: Restrict function arguments.

function printText(s: string, alignment: "left" | "right" | "center") {
  // ...
}

printText("Hello, world"."left");
printText("day, mate"."centre");
/ / ~ ~ ~ ~ ~ ~ ~ the parameters of the type "the" centre "" cannot be assigned to type" "left" | "right" | "center" "parameter.
Copy the code

Example 2: Restrict a function to return a result.

function compare(a: string, b: string| | 0 1) : - 1{
  return a === b ? 0 : a > b ? 1 : -1;
}
Copy the code

Example 3: Used with other non-literal types.

interface Options {
  width: number;
}

function configure(x: Options | "auto") {
  // ...
}

configure({ width: 100 });
configure("auto");
configure("automatic");
/ / ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ the parameters of the type "the" automatic "" cannot be assigned to type" Options | "auto" "parameter
Copy the code

Accordingly, you can put the Boolean type is understood as a true | false literal type of an alias.

Null, and undefined

StrictNullChecks is a configuration item. If strictNullChecks is disabled, null and undefined can be used as attributes of any type. Therefore, it is generally not recommended to turn this configuration item off.

StrictNullChecks checks both null and undefined as separate types. StrictNullChecks checks both null and undefined as separate types.

function doSomething(x: string | null) {
  if (x === null) {
    // do nothing
  } else {
    console.log("Hello, "+ x.toUpperCase()); }}Copy the code

There’s a special symbol in TS! Type checks for NULL and undefined are removed from types, called non-null assertions. The following code asserts that x cannot be null, so TS will assume that this is fine.

function liveDangerously(x? :number | null) {
  // No error
  console.log(x! .toFixed()); }Copy the code

Note: Like other types of assertions, non-null assertions are removed at compile time, so when you use them make sure that the value cannot be null or undefined.

The enumeration

Enumerations are an extension of js types made by TS. Unlike other types or assertions, enumerations are real objects at runtime.

Digital enumeration

We often encounter situations where a state has multiple values. We pass the number type to the back end, but it is less intuitive in code what state 0 represents. From a semantic perspective, it is easier to understand using the enumeration status.new. The following is an enumeration of numbers. If New is given an initial value, the members after New are automatically incremented by 1. If none of the members are given an initial value, the first member defaults to 0 and the following members are automatically incremented by 1. Of course you can assign values to each member.

enum Status {
  'New' = 0.'Success'.'Disabled'.'Delete',}Copy the code

String enumeration

In a string enumeration, each member must be initialized with either a string literal or another string enumeration member. If you are debugging and have to read the runtime value of a numeric enumeration, the value is often difficult to read – it does not express useful information. String enumerations allow you to provide a run-time value that is meaningful and readable, independent of the enumerator’s name.

// Hump is easier to read than all caps
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT",}Copy the code

Constant enumeration

To avoid overhead in additional generated code and additional indirect access to enumeration members, you can use constant enumerations, defined as const. Constant enumerations are removed at compile time, as follows

const enum Directions { 
    Up, Down, Left, Right 
} 
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]

// Generated after compilation
var directions = [0 /* Up */.1 /* Down */.2 /* Left */.3 /* Right */];
Copy the code

The generic

Generics is the property of defining functions, interfaces, or classes without specifying a specific type in advance, but specifying the type at the time of use.

Generics are one of the most commonly used types in TS, because we know that in projects, data types tend to be complex and reusable, so we need generics with more flexible functionality.

As we can see from the following code, value can be of any type and the createArray function must return an array with the same element type as value. The purpose of generics here is to help us control the function to return an array of type value, establishing the universality of the function.

function createArray<T> (length: number, value: T) :Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
createArray(3.'x'); // ['x', 'x', 'x']
createArray(2, { name: 'test' });
Copy the code

We can also use generics to define the type of the request parameters when we have a request page interface:

interface IQuery<T> {
  page: number
  size: numberdata? : T }const queryInfo: IQuery<{ name: string} > = {page: 1.size: 20.data: { name: 'test'}}const queryInfo1: IQuery<{ type: number} > = {page: 1.size: 20.data: { type: 1}}Copy the code

There’s more to generics than that, of course, and a separate article on generics will be written later.

BigInt and Symbol

In ts, the type annotation of BigInt corresponds to BigInt. Ts will infer that Symbol corresponds to a unique type. For example, in the following code, FirstName is of type ‘typeof firstName’.

// bigint
const oneHundred: bigint = BigInt(100);

// Symbol
const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {
  / / ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ this condition will always return "false", because of the type "typeof firstName"
  // No overlap with "typeof secondName"
  // Can't ever happen
}
Copy the code

Type inference

When you declare a variable as const, var, or let you can declare the type directly, but most of the time we don’t have to declare the type, because the type inference mechanism of TS will infer the type from the initial value that you give it.

When TS cannot infer the type, the default type is any. We can through configuration of ts compiler option is set in the project have implied expressions and statements of any type times wrong, in tsconfig. Json file configuration:

{
  // Compile options
  "compilerOptions": {
    "noImplicitAny": false./ /...
  }
  / /...
}
Copy the code

Anonymous functions

Anonymous functions and named functions are represented differently, as in the following example:

// Names does not display a specified type
const names = ["Alice"."Bob"."Eve"];

// forEach is an anonymous function
names.forEach((s) = > {
  console.log(s.toUppercase());
  // ~~~~~~~~~~~~ attribute "toUppercase" does not exist on type "string". Do you mean "toUpperCase"?
});
Copy the code

Here s does not show the declared type, but since TS automatically infer that names are of type String [], s is naturally inferred to be of type string, so TS checks for the toUppercase method on the string.

Literal type inference

When you declare and initialize an object, TS defaults to the fact that all properties of the object can be changed. In the following example, req.method is inferred to be of type string, not “GET”, so ts will prompt an error when calling the handleRequest method.

function handleRequest(url:string, method: "GET" | "POST"){
  / /...
}
const req = { url: "https://example.com".method: "GET" };

handleRequest(req.url, req.method);
/ / ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ the parameters of type "string" cannot be assigned to type "" GET" | "POST" "parameter
Copy the code

Solutions:

  • 1. Declare a type as “GET” using type assertion, either way
const req = { url: "https://example.com".method: "GET" as "GET" };
/ / or
handleRequest(req.url, req.method as "GET");
Copy the code
  • 2. Useas constConverts the entire object to a literal type
const req = { url: "https://example.com".method: "GET" } as const;
handleRequest(req.url, req.method);
Copy the code

Types of assertions

Type assertions can be used to tell TS the type of a variable when it cannot tell the type, but you know exactly what type it is. There are two types of type assertion, the as keyword and the <> form (which cannot be used in.tsx files), which are equivalent.

For example, if you use document.getElementById to fetch a Cavas element, ts only knows that it is an HTMLElement, but you know that it is an HTMLCanvasElement, you can use an assertion to tell TS:

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
/ / or
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
Copy the code

Use of assertions: There are several cases where assertions are used more often

  • Assert a union type as one of the types
function upperCaseID(id: string|number){
  return (id as string).toUpperCase()
}
Copy the code
  • Assert a parent class as a more concrete subclass
interface ApiError extends Error {
  code: number;
}
interface HttpError extends Error {
  statusCode: number;
}

function isApiError(error: Error) {
  if (typeof (error as ApiError).code === 'number') {
    return true;
  }
  return false;
}
Copy the code
  • Assert any type as any
(window as any).name = 'Tom';
Copy the code
  • Assert any as a concrete type
const temp: any = 'Hello World';
console.log((temp as string).toLowerCase());
Copy the code

In order for A to be asserted as B, A only needs to be compatible with B or B compatible with A. This is also for safety purposes when type assertions are made, since unfounded assertions are very dangerous. Other impossible situations are blocked, such as:

const x = "hello" as number;
/ / ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ type "string" to the "number" of transformation may be wrong, because the two types
// Not enough overlap. If this is intentional, first convert the expression to "unknown".
Copy the code

If you feel this is too restrictive and must cast (not recommended), you can do this with two assertions, first of any or unknown type and then of number, as in:

const x = ("hello" as any) as number;
Copy the code

reference

everyday-types