How Typescript differs from JavaScript, and the benefits of using Typescript in projects

1. The difference between

TypeScript requires data to have an explicit type (even if no type is defined, there is type inference); JavaScript does not.

TypeScript provides compile-time static type checking through type annotations, which artificially type a variable.

2. The advantages

JavaScript code can work with TypeScript without modification, making it easy for projects to transition to TypeScript, and using compilers to convert TypeScript code to JavaScript.

Most errors can be found at compile time.

Second, basic types

In addition to inheriting JavaScript types (Booleans, numbers, strings, arrays, Null, Undefined, Object), some new types have been added

1. Any type

The type of a variable is dynamic, can be arbitrary, is not checked by the type checker, and can pass through the compilation phase

let dynamic: any = 4;
dynamic = "daphnisli";
Copy the code

2. Void has no type

A variable has no type, and a function can be void if it has no return value

function notReturned() :void {
    console.log("daphnisli");
}
Copy the code

Declaring a void variable is of little use, since it can only be assigned undefined and NULL:

let notReturned: void = undefined;
Copy the code

3. The Tuple Tuple

The tuple type allows you to represent an array with a known number and type of elements. The elements need not be of the same type, but the order of the types is important. For example, let’s define a pair of tuples of type string and number.

let x: [string.number];
x = ['hello'.10]; // OK
x = [10.'hello']; // Error
Copy the code

4.Never

The never type represents the types of values that never exist. For example, the never type is the return type of function expressions or arrow function expressions that always throw an exception or have no return value at all;

function error(message: string) :never {
    throw new Error(message);
}
Copy the code

5. The enumeration

A numeric enumeration is defined below, with Up initialized to 1. The rest of the members will automatically grow from 1. You can also do without initialization, so UP is 0

enum Enumerate {
    Up = 1,
    Down,
    Left,
    Right
}
Copy the code

In a string enumeration, each member must be initialized with a string, or with another string enumeration member.

enum Enumerate {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",}Copy the code

6. Type assertion (type conversion)

There are times when I know more about a variable than TypeScript does. Type assertion is a way of telling the compiler, “I know the type of this variable.” It has no run-time impact, only at compile time.

Angle brackets

let str: any = "daphnisli";
let strLength: number = (<string>str).length;
Copy the code

React supports only the AS syntax

let str: any = "this is a string";
let strLength: number = (str as string).length;
Copy the code

Three, interfaces,

An interface is a description of a variable’s type. The object parameters we pass in actually contain many attributes, but the compiler only checks if the required attributes exist and their types match.

interface LabelledValue {
  ordinary: string // Common attributesoptional? :string	// Optional attribute
  readonly readOnly: number // Read-only attribute
}

Copy the code

The type checker does not check the order of attributes, as long as the corresponding attributes exist and the type is correct.

Four, functions,

For type checking of function types, the parameter names of the function do not need to match the names defined in the interface. The parameters of the function are checked one by one and the corresponding parameter types are required to be the same.

Every function argument in TypeScript is required. The number of arguments passed to a function must be the same as the number of arguments expected by the function. (Inconsistency will cause an error)

1. Optional parameters and default parameters

In TypeScript you can use it next to parameter names, right? Realize the function of optional parameters

function buildName(firstName: string, lastName? :string) {
    if (lastName)
        return firstName + "" + lastName;
    else
        return firstName;
}
let result1 = buildName("Bob");  // works correctly now
let result2 = buildName("Bob"."Adams"."Sr.");  // error, too many parameters
let result3 = buildName("Bob"."Adams");  // ah, just right
Copy the code

In TypeScript, you can provide a default value for a parameter, which has a default initial value when the parameter is not passed or when the value passed is undefined.

Arguments with default initialization are of optional type and, like optional arguments, can be omitted when calling a function.

function buildName(firstName: string, lastName = "Smith") {
    return firstName + "" + lastName;
}
let result1 = buildName("Bob");                  // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob".undefined);       // still works, also returns "Bob Smith"
let result3 = buildName("Bob"."Adams"."Sr.");  // error, too many parameters
let result4 = buildName("Bob"."Adams");         // ah, just right
Copy the code

Note: The optional arguments must be placed after the required arguments. The default arguments can be placed before the required arguments, but in order to ensure the order of the arguments, you must pass undefined to get the default value

2. Residual arguments (same arguments)

Required, default, and optional parameters have one thing in common: they represent a parameter. Sometimes, you want to manipulate more than one parameter at a time, or you don’t know how many parameters will be passed in. In JavaScript, you can use arguments to access all incoming arguments. In TypeScript, you can collect all parameters into a single variable:

function buildName(firstName: string. restOfName:string[]) {
  return firstName + "" + restOfName.join("");
}
let employeeName = buildName("Joseph"."Samuel"."Lucas"."MacKinzie");
//Joseph Samuel Lucas MacKinzie
Copy the code

Fifth, generics

What it does: Creates reusable components that can support multiple types of data

1. First introduction to generics

For example, the generic function returns the arg passed in, if you don’t use generics

function generic(arg: any) :any {
	// Perform some operations ~ ~ ~
    return arg;
}
Copy the code

Using any causes the function to accept arG arguments of any type, which results in an error: the type passed in should be the same as the type returned, but using any results in not knowing what type of value is returned.

So you need a way to make the type of the return value the same as the type of the parameter passed in. That is generics (generics stand for any type)

function generic<T> (arg: T) :T {
    returnarg; } Once a generic function is defined, it can be used in two ways. The first is to pass in all the arguments, including the type arguments:let output = generic<string> ("myString"); This is explicitly specifying that T isstringThe second takes advantage of the type corollary -- the compiler automatically helps us determine the type of T based on the arguments passed in:let output = generic("myString"); Note that we do not need to use Angle brackets (<>) to pass in the type explicitly; The compiler can look at the value of myString and set T to its type. Type inference helps us keep our code lean and readable. If the compiler cannot automatically infer the type, it can only pass in the type of T explicitly as above, which is possible in some complicated cases. ##### is more recommended for the first useCopy the code

T helps us capture the type passed in by the user (e.g., string) when T is used again as the return type. Now you know that the parameter type is the same as the return value type.

2. Use generics

For another example, if you want to print out the length of arG at the same time. It’s likely to do this:

function genericLength<T> (arg: T) :T {
    console.log(arg.length); 
    return arg;
}
Copy the code

The editor will get an error because arG is arbitrary, it doesn’t necessarily have a length property, in case T is number, it doesn’t have a length property, but arrays do.

So to avoid the above error, we can specify arg after arg, which is the array type of type T

function genericLength<T> (arg: Array<T>) :Array<T> {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}
Copy the code

Doing so allows us to use the generic variable T as part of the type T, rather than the whole type, adding flexibility.

What if we want to use an interface to describe what we want to do with an array type of type T?

You can use the extends keyword for constraints

interface Lengthwise {
    length: number;
}
function genericLength<T extends Lengthwise> (arg: T) :T {
    console.log(arg.length); 
    return arg;
}
Copy the code

Note: This generic function is now constrained, so it no longer applies to any type:

genericLength(3);  // Error: number does not have length attributeWe need to pass in a value that matches the constraint type and must contain the required attributes: genericLength({genericLength({genericLength({genericLength))length: 10.value: 3});
Copy the code

6. Type inference

1. The basic

In TypeScript, type inference helps provide types where they are not explicitly specified. Here’s an example

let x = 3;
Copy the code

The type of variable X is inferred to be a number. This inference occurs when initializing variables, setting default parameter values, and determining the return value of a function.

2. Best generic type

When you need to infer a type from several expressions, the types of those expressions are used to infer the most appropriate generic type. Take the following example

let x = [0.1.null];
Copy the code

To infer the type of X, you must consider the types of all elements. There are two options: number and NULL. The compute common type algorithm considers all candidate types and gives a type that is compatible with all candidate types.

Because the final generic type is derived from the candidate type, sometimes the candidate types share the same generic type, but no one type can be used as a type for all candidate types. Take the following example

let zoo = [new Cat(), new Dog(), new Mouse()];
Copy the code

Here, we want zoo to be inferred as Animal[], but there are no objects in this array that are of Animal type, so we cannot infer this result. To correct this, we need to specify the type explicitly when candidate types are not available:

let zoo: Animal[] = [new Cat(), new Dog(), new Mouse()];
Copy the code

If you do not specify the type and the best generic type is not found, the result of type inference for joint array types, (Cat | Dog | Mouse) [].

3. Context type

TypeScript type inference can also go in the opposite direction. This is called “categorizing by context.” Context-specific categorization occurs when the type of the expression is related to its location. Take the following example

window.onmousedown = function(mouseEvent) {
    console.log(mouseEvent.button);  //<- Error
};
Copy the code

This example results in a type error. The TypeScript type checker uses the type of the window. onmousedown function to infer the type of the right-hand function expression. Therefore, you can infer the type of the mouseEvent parameter. If the function expression is not in the context type, the mouseEvent parameter needs to be of type ANY so that no errors are reported.

If the context type expression contains explicit type information, the context type is ignored. Rewrite the above example:

window.onmousedown = function(mouseEvent: any) {
    console.log(mouseEvent.button);  
};
Copy the code

This function expression is annotated with an explicit parameter type, and the context type is ignored. This way, no errors are reported, because the context type is not used here.

Contextual categorization can be used in many situations. Usually contains function arguments, the right side of assignment expressions, type assertions, object member and array literals, and return value statements. Context types are also considered candidates for the best generic type. Such as:

function createZoo() :Animal[] {
    return [new Cat(), new Dog(), new Mouse()];
}
Copy the code

In this example, there are four candidates for the best generic type: Animal, Cat, Dog, and Mouse. Of course, Animal would be considered the best generic type.

Type compatibility

1. Start

The basic rule of TypeScript’s structured type system is that if X is compatible with Y, then Y has at least the same properties as X. Such as:

interface Named {
    name: string;
}

let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: 'Alice'.location: 'Seattle' };
x = y;
Copy the code

Here we check to see if y can be assigned to x, and the compiler checks each attribute in X to see if it can find the corresponding attribute in Y. In this case, y must contain a string member whose name is name. Y satisfies this condition, so the assignment is correct.

Use the same rules for checking function arguments:

function greet(n: Named) {
    console.log('Hello, ' + n.name);
}
greet(y); // OK
Copy the code

Note that y has an extra location attribute, but this does not raise an error (because the number of arguments is the same, but the Y object has an extra attribute). Only members of the target type (in this case Named) are checked for compatibility. The comparison is performed recursively, checking each member and its children.

2. Compare two functions

let x = (a: number) = > 0;
let y = (b: number, s: string) = > 0;

y = x; // OK
x = y; // Error
Copy the code

To see if x can be assigned to Y, first look at their argument list. Each parameter of x must have a corresponding type of parameter in y. Note that it doesn’t matter if the parameters have the same name, just their type. Here, each argument to x has a corresponding argument in y, so assignment is allowed.

The second assignment error, because y has a required second argument, but x does not, so assignment is not allowed.

Let’s see how to handle return value types by creating two functions that differ only in their return value types:

let x = () = > ({name: 'Alice'});
let y = () = > ({name: 'Alice'.location: 'Seattle'});

x = y; // OK
y = x; // Error
Copy the code

Here, the type system enforces the return value type of the source function to be a subtype of the return value type of the target function. So y can be assigned to x

8. Advanced types

1. Cross types

In the following example, Cat & Dog is a collection of both Cat and Dog types

// Assign all parameters to result
function extend<T.U> (first: T, second: U) :T & U {
    letresult = <T & U>{}; for (let id in first) { (<any>result)[id] = (<any>first)[id]; } for (let id in second) { if (! Result.hasownproperty (id) {// Check whether result contains id attribute (<any>result)[id] = (<any>second)[id]; } } return result; } var jim = extend(new Cat(), new Dog());Copy the code

The extend function assigns all the attributes of the passed argument to result and prints it, where the type of ressult is the set of the types of the two arguments

2. Union type

Union types indicate that a value can be one of several types. We use a vertical bar (|) separated for each type, so the number | string representing a value can be a number or string, the incoming parameters just meet one of them.

function padLeft(value: string, padding: string | number ) {
    // ...
}

Copy the code

If a value is a union type, then we can access only the members that are common to all types of that union type. (Because an error is reported in case the accessed property does not exist in this type, it is best to access the common property.)

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet() :Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors
Copy the code

In this example, Bird has a fly member. We are not sure a Bird if there is a fly way | Fish types of variables. If the variable is of type Fish at run time, the call to pet.fly() is an error.

3. Type protection and type differentiation

How do you tell which one it is? Type assertions can be used

The following example uses type assertions to avoid errors

let pet = getSmallPet();

if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}
Copy the code

User-defined type protection can be cumbersome if type assertion is used more than once. We can encapsulate a function to determine which type it is

function isFish(pet: Fish | Bird) :pet is Fish {
    return(<Fish>pet).swim ! = =undefined;
}
// The 'swim' and 'fly' calls are ok now
if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}
Copy the code

Pet is Fish is a type predicate. The predicate is of the form propsName is Type, and propsName must be a parameter name from the current function.

4. Typeof or Instanceof type protection (note the scope of use of Typeof and Instanceof)

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join("") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'. `);
}
Copy the code

You can also avoid errors by using typeof or instanceof to determine the typeof the parameter before using it

Ix. Declaration of merger

1. Merge interfaces

The mechanism for merging is to put the members of both parties into an interface with the same name.

interface Box {
    height: number;
    width: number;
}
interface Box {
    scale: number;
}
let box: Box = {height: 5.width: 6.scale: 10};
Copy the code

There are two types of members:

Nonfunction member

  • Non-function members of an interface should be unique.
  • If they are not unique, they must be of the same type.
  • If non-function members have the same name but their types are different, the compiler will report an error.

Members of the function

  • Each function declaration with the same name is treated as an overload of that function.
  • When interface A is merged with subsequent interface A, the subsequent interface has higher priority.

Here’s an example

interface Cloner {
    clone(animal: Animal): Animal;
}
interface Cloner {
    clone(animal: Sheep): Sheep;
}
interface Cloner {
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
}
Copy the code

These three interfaces are combined into one declaration:

interface Cloner {
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
    clone(animal: Sheep): Sheep;
    clone(animal: Animal): Animal;
}
Copy the code

When calling the Clone function, the first declaration is used first

One exception to this rule is when a special function signature occurs. If one of the parameters in the signature is a single string literal, it will be promoted to the top of the overload list.

For example, the following interfaces are merged together:

interface Document {
    createElement(tagName: any): Element;
}
interface Document {
    createElement(tagName: "div"): HTMLDivElement;
    createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
    createElement(tagName: string): HTMLElement;
    createElement(tagName: "canvas"): HTMLCanvasElement;
}
Copy the code

The merged Document will look like this:

interface Document {
    createElement(tagName: "canvas"): HTMLCanvasElement;
    createElement(tagName: "div"): HTMLDivElement;
    createElement(tagName: "span"): HTMLSpanElement;
    createElement(tagName: string): HTMLElement;
    createElement(tagName: any): Element;
}
Copy the code

This merge puts the arguments of the string literal first and the other arguments after.

That’s all. Thank you.