What is TypeScript


TypeScript is a free and open source programming language developed by Microsoft. It is a superset of JavaScript and essentially adds optional static typing and class-based object-oriented programming to the language.


In short, TypeScript is a superset of JavaScript with optional types that can be compiled to pure JavaScript; technically TypeScript is JavaScript with static types.

 TypeScript Pros and Cons


  • Enhance code maintainability, especially in large projects.

  • Friendly error hints in the editor, and type-checking at the compile stage to find most errors
  •  Support for the latest JavaScript features

  • The surrounding ecological prosperity, vue3 has been fully supported typescript

  •  Requires some learning costs

  • Compatibility with some plugin libraries is not perfect, e.g. using typescript in a vue2 project was not so smooth.

  • Increase the cost of upfront development, after all, you need to write more code (but easier to maintain later)

 installation environment

 Install typescript


First of all, we can create a new empty folder for learning ts, for example, I created a new folder under the folder helloworld.ts

npm install -g  typescript 


If you don’t remember whether you have typescript installed or not, you can use the following command to verify:

tsc -v 

 If the version appears, the installation has been successful

Version 4.6.3

 Generate tsconfig.json configuration file

tsc --init


After executing the command, we can see that a tsconfig.json file has been generated, which contains some configuration information, so let’s leave it at that for now.

 In our helloworld.ts file, write whatever you want.



The console executes the tsc helloworld.ts command, and a helloworld.js file with the same name is generated in the directory with the following code



The tsc command reveals that our typescript code was converted to familiar js code

 Let’s move on.

node helloworld.js

 You can see the output

 Install ts-node


So through our above a pass operation, we know that running the tsc command can be compiled to generate a js file, but if each change we have to manually go to the compilation, and then through the node command to view the results is not too much trouble.

 And ts-node is here to solve this problem.

npm i -g ts-node

 With this plugin, we can run the .ts file directly

 Let’s try it.

ts-node helloworld.ts

 You can see that our printout has output

 All of our subsequent examples can be verified with this command


Now we can move on to typescript.

 TypeScript Basic Types

 Boolean Type

const flag: boolean = true;

 Number Type

const count: number = 10;

 String Type

  let name: string = "";

 Enum Type


Enumeration type is used to define a collection of values, using enumeration we can define some constants with names. Using enumerations you can clearly express the intent or create a set of differentiated use cases. For example, Monday through Sunday, orientation up and down, left and right, etc.

  •  normal enumeration


The initial value defaults to 0, and the rest of the members will automatically grow in order, which can be thought of as array subscripts.

enum Color {
  RED,
  PINK,
  BLUE,
}

const red: Color = Color.RED;
console.log(red); // 0
  •  Setting the initial value
enum Color {
  RED = 2,
  PINK,
  BLUE,
}
const pink: Color = Color.PINK;
console.log(pink); // 3
  •  string enumeration


const pink: Color = Color.PINK;
console.log(pink);
  •  constant enumeration


The difference between a constant enumeration and a normal enumeration is that the entire enumeration is deleted at the compilation stage when the enumeration is modified with the const keyword. We can see how it looks after compilation.

const enum Color {
  RED,
  PINK,
  BLUE,
}

const color: Color[] = [Color.RED, Color.PINK, Color.BLUE];
console.log(color); //[0, 1, 2]

var color = [0 /* RED */, 1 /* PINK */, 2 /* BLUE */];

 Array type

 There are two ways to define an array type.

  const arr: number[] = [1,2,3];
  const arr2: Array<number> = [1,2,3];

 Tuple type


The above array type approach can only define arrays that are all of the same type internally. Arrays with different internal types can be defined using the tuple type


Tuple (Tuple) represents a known number and type of arrays, it can be understood that he is a special kind of arrays

  const tuple: [number, string] = [1, "zhangmazi"];


Note that the tuple type can only represent an array with a known number and type of elements, the length is specified, and out-of-bounds access will prompt an error. For example, an array may have multiple types in it, the number and type of which are uncertain, then just any[].

 undefined and null


By default null and undefined are subtypes of all types. That means you can assign null and undefined to other types.

  let a: undefined = undefined;
  let b: null = null;

  let str: string = 'zhangmazi';
  str = null
  str = undefined; 


If you specify “strictNullChecks”:true in tsconfig.json, i.e., if strict mode is enabled, null and undefined can only be assigned to void and their respective types. (Thanks to the comments section for pointing this out.) null and undefined can only be assigned to their own types.

// 启用 --strictNullChecks
let x: number;
x = 1; 
x = undefined;    
x = null;    


But undefined can assign a value to a void

let c:void = undefined 
let d:void = null 

 any Type


any skips the type checker’s checking of the value, and any value can be assigned to the any type

  let value: any = 1;
  value = "zhangmazi"; 
  value = []; 
  value = {};

 void Type


void means invalid, usually only used in functions, tell others that the function has no return value.

  function sayHello(): void {
    console.log("hello");
  }

 never Type


The never type represents the type of values that never exist. For example, the never type is the type of the return value of a function expression or arrow function expression that always throws an exception or never has a return value at all

 Two cases where the value will never exist:


  • 1 If a function throws an exception while executing, then the function never has a return value (because throwing an exception directly interrupts the program, which makes the program go no further than the return value, i.e., it has an unreachable endpoint, and there is never a return).

  • 2 Code that executes an infinite loop in a function (a dead loop) so that the program never runs to the point where the function returns a value, and there is never a return.

function error(msg: string): never { 
  throw new Error(msg); 
}


function loopForever(): never { 
  while (true) {};
}

 Unknown Type


UNKNOWN is the same as ANY and all types can be assigned to UNKNOWN:.

  let value: unknown = 1;
  value = "zhangmazi"; 
  value = false; 

 The big difference between UNKNOWN and ANY is:


A value of any type can be assigned to any, and a value of type any can be assigned to any. unknown A value of any type can be assigned to it, but it can only be assigned to unknown and any.

 object type


object, Object and {} types


  • The object object type is used to represent all non-primitive types, i.e., we can’t assign primitive types such as number, string, boolean, symbol, etc., to an object. null and undefined types can’t be assigned to an object in strict mode either.
let object: object;
object = 1; 
object = "a"; 
object = true; 
object = null; 
object = undefined; 
object = {}; 
  • Object


Big Object represents all types that have toString, hasOwnProperty methods, so all primitive and non-primitive types can be assigned to Object (null and undefined are not allowed in strict mode).

let bigObject: Object;
object = 1; 
object = "a"
object = true; 
object = null; 
ObjectCase = undefined; 
ObjectCase = {}; // ok
  • {}


{} A null object type, like a large Object, represents a collection of primitive and non-primitive types.


In TypeScript, we define a class using the Class keyword.

class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  sayHi(): void {
    console.log(`Hi, ${this.name}`);
  }
}

const flag1: number[] = [1, 2, 3];
const flag2: Array<number> = [1, 2, 3];

 function declaration

function add(x: number, y: number): number {
  return x + y;
}

 function expression (math.)

const add = function(x: number, y: number): number {
  return x + y;
}

 Interface Definition Functions

interface Add {
  (x: number, y: number): number;
}

 Optional parameters

function add(x: number, y?: number): number {
  return y ? x + y : x;
}

 default parameter

function add(x: number, y: number = 0): number {
  return x + y;
}

 Remaining parameters

function add(...numbers: number[]): number {
  let sum = 0;
  for (let i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }
  return sum;
}

 function overloading


Function overloading or method overloading is an ability to create multiple methods using the same name and different number or types of arguments.

function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
  return x + y;
}


In the above example, we provide multiple function type definitions for the same function, thus enabling function overloading

 Points to note.


Function overloading is really implemented in the function body of the last function of the same name defined before the last function body definition are function type definitions can not write specific function implementation methods can only define the type of

 genre inference


If a type is not explicitly specified, then TypeScript will infer a type according to the rules of type inference.

let x = 1;
x = true; 

 The code above is equivalent to

let x: number = 1;
x = true


As you can see from the above example, when we don’t specify an explicit type for x, typescript will infer that x is of type number.


And if it is defined without assignment, it will be inferred to be of type any without being type-checked at all, regardless of whether it is assigned afterwards or not:

let x;
x = 1
x = true

 type assertion


In some cases, we may know the type of a variable better than typescript, so we may want to manually specify the type of a value

 Type assertions are made in two ways

  •  angle brackets < >
let str: any = "to be or not to be";
let strLength: number = (<string>str).length;
  •  as Writing
let str: any = "to be or not to be";
let strLength: number = (str as string).length;

 nonempty assertion


In context when the type checker is unable to assert a type, the affix expression operator ! can be used to assert that the object of the operation is of a non-null and non-undefined type, i.e. the value of x! will not be null or undefined

  let user: string | null | undefined;
  console.log(user!.toUpperCase());
  console.log(user.toUpperCase());

 Determining Assignment Assertions

let value:number
console.log(value); // Variable 'value' is used before being assigned.

 If we define a variable and use it without assigning a value to it, we get an error


By letting x!: number; determine the assignment assertion, the TypeScript compiler knows that the property will be explicitly assigned.

let value!:number
console.log(value); 

 Type of association


Union types are separated by | , indicating that the value can be one of several types

let status:string|number
status='to be or not to be'
status=1

 type alias


A type alias is used to give a new name to a type. It just gives a new name, it does not create a new type. Type aliases are often used for union types.

type count = number | number[];
function hello(value: count) {}

 Cross-cutting type


A cross type is the opposite of a union type and is represented by the & operator; a cross type is when two types must be present

interface IpersonA{
  name: string,
  age: number
}
interface IpersonB {
  name: string,
  gender: string
}

let person: IpersonA & IpersonB = { 
    name: "",
    age: 18,
    gender: ""
};


person is the type of IpersonA and IpersonB.


Note: Cross-typing takes the concatenation of multiple types, but if the key is the same but of a different type, then the key is of type never

interface IpersonA {
    name: string
}

interface IpersonB {
    name: number
}

function testAndFn(params: IpersonA & IpersonB) {
    console.log(params)
}

testAndFn({name: ""}) // error TS2322: Type 'string' is not assignable to type 'never'.

 Type Guard


Type protection is an expression for executable runtime checks to ensure that the type is within certain limits. In other words, type protection ensures that a string is a string, even though its value can also be a numeric value. Type protection is not entirely different from feature detection; the main idea is to try to detect properties, methods, or prototypes to determine how to handle values.


In other words: type guards are runtime checks to make sure that a value is within the range of the desired type

 There are currently four main ways to implement type protection:

  •  1. in keyword
interface InObj1 {
    a: number,
    x: string
}
interface InObj2 {
    a: number,
    y: string
}
function isIn(arg: InObj1 | InObj2) {

    if ('x' in arg) console.log('x')

    if ('y' in arg) console.log('y')
}
isIn({a:1, x:'xxx'});
isIn({a:1, y:'yyy'});
  •  2、typeof keyword
function isTypeof( val: string | number) {
  if (typeof val === "number") return 'number'
  if (typeof val === "string") return 'string'
  return ''
}
function creatDate(date: Date | string){
    console.log(date)
    if(date instanceof Date){
        date.getDate()
    }else {
        return new Date(date)
    }
}
  •  4. Type predicates for custom type protection
function isNumber(num: any): num is number {
    return typeof num === 'number';
}
function isString(str: any): str is string{
    return typeof str=== 'string';
}


We use interfaces to define the types of objects. An interface is an abstraction (description) of an object’s state (properties) and behavior (methods)

 The simple understanding is: provide a convention for our code

 We use the keyword interface to declare an interface

interface Person {
    name: string;
    age: number;
}
let tom: Person = {
    name: 'Tom',
    age: 25
};


We define an interface Person, and then we define a variable tom, whose type is Person, so that we constrain the shape of tom to be consistent with the interface Person.


Interfaces are generally capitalized. (Of course, a lot of people also use the I capitalization to indicate that it’s an interface.)

 Setup Interface Optional | Read Only

interface Person {
  readonly name: string;
  age?: number;
}

  • The optional attribute, our most common use case, is that we are not sure if this parameter will be passed, or exists.


  • The read-only attribute is used to restrict the modification of an object’s value to only when it has just been created. In addition, TypeScript provides the ReadonlyArray type, which is similar to Array, except that it removes all mutable methods, thus ensuring that an array can never be modified after it is created.

 indexed signature


Sometimes we want an interface to allow arbitrary attributes in addition to mandatory and optional attributes, and we can use indexed signatures to fulfill this requirement.


Note that once an arbitrary property is defined, the types of both the deterministic and optional properties must be a subset of its type

interface Person {
  name: string;
  age?: number;
  [prop: string]: any
}

const p1:Person = { name: "" };
const p2:Person = { name: "", age: 28 };
const p3:Person = { name: "", sex: 1 }


We provide for indexing by a value of type string, which indexes to a value of type any.

 Differences between interfaces and type aliases


In fact, in most cases, using interface types and type aliases is equivalent, but in some specific scenarios there is a big difference between the two.


One of the core principles of TypeScript is type checking the structures that values have. And the role of the interface is to name these types and define the data model for your code or third-party code.


type (type alias) gives a new name to a type. type is sometimes similar to interface, but works on primitive values (basic types), union types, tuples, and anything else you need to write by hand. Making an alias doesn’t create a new type – it creates a new name to refer to that type. Aliasing basic types is usually not useful, although it can be used as a form of documentation.


Both interfaces and type aliases can be used to describe the type of an object or function, just with a different syntax

type MyTYpe = {
  name: string;
  say(): void;
}

interface MyInterface {
  name: string;
  say(): void;
}

 Both allow for expansion


  • interface is extended with extends
interface MyInterface {
  name: string;
  say(): void;
}

interface MyInterface2 extends MyInterface {
  sex: string;
}

let person:MyInterface2 = {
  name:'',
  sex:'',
  say(): void {
    console.log("hello!");
  }
}
  •  type Use & to implement extensions
type MyType = {
  name:string;
  say(): void;
}
type MyType2 = MyType & {
  sex:string;
}
let value: MyType2 = {
  name:'',
  sex:'',
  say(): void {
    console.log("hello!");
  }
}


  • type can declare basic datatype aliases/joint types/tuples, etc., whereas interface cannot.

type UserName = string;
type UserName = string | number;

type Animal = Pig | Dog | Cat;
type List = [string, boolean, number];
  •  interface can merge declarations, but type cannot.
interface Person {
  name: string
}
interface Person {
  age: number
}


A generalization is a feature that does not prespecify a specific type when defining a function, interface, or class, but rather specifies the type when it is used.


For example, let’s say we have a requirement now that we want to implement a function where the arguments to the function can be any value, and the return value is to return the arguments as they are, and the type of the arguments is string, and the function return type is string?

 It’s easy for you to write down:

function getValue(arg:string):string  {
  return arg;
}


Now that the requirements have changed and you need to return a value of type number, you’d say that the union type would do the trick:

function getValue(arg:string | number):string | number  {
  return arg;
}


But then there’s the problem that if we need to return a boolean, an array of strings or even an arbitrary type, do we write as many union types as there are?

 Yes, we’ll just use any!

function getValue(arg:any):any  {
  return arg;
}


Although any is good, and often any does solve a lot of problems, it doesn’t meet our needs, and the pass and return are both of type any, which doesn’t unify the pass and return.


As a programmer with a tawdry most demanding program, can we have any other solutions?

 It’s time to bring out our generalizations!

 Basic use


A generalization is a feature that does not prespecify a specific type when defining a function, interface, or class, but rather specifies the type at the time of use

 The above requirement, if we use generalization to solve it:

function getValue<T>(arg:T):T  {
  return arg;
}


The syntax of a generic is to write the type parameter inside the angle brackets <> , usually T is used to indicate the first type variable name, in fact, it can be replaced by any valid name, for example, if we use NIUBI , it will also compile correctly.


A generic type is like a placeholder for a variable, and when using it we can pass in the defined type like a parameter, and output it as-is

 We have two ways to use it:

    1.  Define the type to be used, for example:
getValue<string>('');
    1.  Utilize typescript’s type inference, for example:
getValue('') 

 Multiple parameters


There is not really only one type variable that can be defined; we can introduce as many type variables as we wish to define. For example, we introduce a new type variable U

function getValue<T, U>(arg:[T,U]):[T,U] {
  return arg;
}


const str = getValue(['', 18]);


typescript gives us the ability to automatically infer the type of input and return.

 generalized constraint


When using a generic variable inside a function, you can’t manipulate its properties or methods at will because you don’t know which type it is beforehand:

function getLength<T>(arg:T):T  {
  console.log(arg.length);
}


Since the generic T does not necessarily contain the attribute length, I want the getLength function to only allow passing in variables that contain the length attribute, how do I do that?


At this point, we can use the extends keyword to constrain the generalization

interface Lengthwise {
  length: number;
}

function getLength<T extends Lengthwise>(arg:T):T  {
  console.log(arg.length); 
  return arg;
}
const str = getLength('')
const arr = getLength([1,2,3])
const obj = getLength({ length: 5 })


As you can see here, it doesn’t matter if you’re a str, arr, or obj, as long as you have the length attribute, you can

 For more information, see Easy TS Floods.

 generic interface

 Specify the generic type when defining the interface

interface KeyValue<T,U> {
  key: T;
  value: U;
}

const person1:KeyValue<string,number> = {
  key: '',
  value: 18
}
const person2:KeyValue<number,string> = {
  key: 20,
  value: ''
}

泛型类

class Test<T> {
  value: T;
  add: (x: T, y: T) => T;
}

let myTest = new Test<number>();
myTest.value = 0;
myTest.add = function (x, y) {
  return x + y;
};

 Generic type aliases

type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];

 Default types for generic parameters


We can specify a default type for the type parameter in a generic. This default type works when the type parameter is not specified directly in the code when using a generic, and cannot be inferred from the actual value parameter. It’s kind of like the default parameter of a function in js.

function createArray<T = string>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

 Generic Tool Types

  • typeof

 Keywords can push out types from implementations, in addition to doing type protection.


let p1 = {
  name: "",
  age: 18,
  gender: "male",
};
type People = typeof p1;
function getName(p: People): string {
  return p.name;
}
getName(p1);
  • keyof

 can be used to get all the key values in an object’s interface

interface Person {
  name: string;
  age: number;
  gender: "male" | "female";
}

type PersonKey = keyof Person; //type PersonKey = 'name'|'age'|'gender';

function getValueByKey(p: Person, key: PersonKey) {
  return p[key];
}
let val = getValueByKey({ name: "", age: 18, gender: "male" }, "name");
console.log(val); 
  • in

 Used to iterate through enumerated types:

type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }
  • infer


In a conditional type statement, you can declare a type variable with infer and use it.

type ReturnType<T> = T extends (
  ...args: any[]
) => infer R ? R : any;


infer R is to declare a variable to hold the type of the return value passed into the function signature, in short, it is used to get the type of the function’s return value for later use.

  • extends


Sometimes we define generalizations that we don’t want to be too flexible or that we want to inherit from certain classes, etc. You can add generalization constraints with the extends keyword.

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}


This generalized function is now defined with constraints, so it no longer applies to arbitrary types:

loggingIdentity(3);  // Error, number doesn't have a .length property


When we pass in a value of a legal type, i.e. a value that contains the length attribute:

loggingIdentity({length: 10, name: ''}); 
  •  index access operator


Index access is possible using the [] operator:

interface Person {
  name: string;
  age: number;
}

type x = Person["name"]; // x is string

 Type of built-in tools

  1. Required

 Make attributes of types mandatory

interface Person {
    name?: string,
    age?: number,
    hobby?: string[]
}

const user: Required<Person> = {
    name: "",
    age: 18,
    hobby: ["code"]
}
  1. Partial


Converts all properties to optional, as opposed to Required.

interface Person {
    name: string,
    age: number,
}

const shuge:Person = {
  name:''
} // error  Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.


From the above, we know that if we have to pass it and we have to pass it less, we will get an error.

 We use Partial to make it optional.

type User = Partial<Person>

const shuge: User={
  name:''

  1. Exclude


Exclude<T, U> The function of this function is to remove the type of a type that belongs to another type, and the remaining attributes form a new type.

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
  1. Extract


In contrast to Exclude, Extract<T,U> extracts U from T.

type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void

 Applies to: Concatenation type

  1. Readonly


Converts all property values of an array or object to read-only, which means that these properties cannot be reassigned.

interface Person {
  name: string;
  age: number;
  gender?: "male" | "female";
}

let p: Readonly<Person> = {
  name: "hello",
  age: 10,
  gender: "male",
};
p.age = 11; // error  Cannot assign to 'age' because it is a read-only property.
  1. Record


The role of Record <K extends keyof any, T> is to convert the values of all attributes in K to T types.

type Property = 'key1'|'key2'
type Person = Record<Property, string>;

const p: Person = {
  key1: "hello",
  key2: "",
};
  1. Pick

 Pick out some attributes from a certain type

type Person = {
  name: string;
  age:number;
  gender:string
}

type P1 = Pick<Person, "name" | "age">; // { name: string; age: number; }

const user:P1={
  name:'',
  age:18
}
  1. Omit


In contrast to Pick, Omit<T,K> removes all other attributes from T excluding K.

interface Person {
  name: string,
  age: number,
  gender: string
}
type P1 = Omit<Person, "age" | "gender">
const user:P1  = {
  name: ''
}
  1. NonNullable


Remove null from the type and undefined

type P1 = NonNullable<string | number | undefined>; // string | number
type P2 = NonNullable<string[] | null | undefined>; // string[]
  1. ReturnType

 Used to get the return value type of a function

type Func = (value: string) => string;
const test: ReturnType<Func> = "1";
  1. Parameters

 The type of the tuple used to obtain the types of the tuples comprising the function’s argument types.

type P1 = Parameters<(a: number, b: string) => void>; // [number, string]
  1. InstanceType

 Returns the type of instance of constructor type T

class C {
  x = 0;
  y = 0;
}

type D = InstanceType<typeof C>;  // C

tsconfig.json


In the environment installation section at the beginning of the article, remember that we have generated a tsconfig.json file, so what exactly is the use of this file?


tsconfig.json is the configuration file for the TypeScript project.


tsconfig.json contains the configuration for TypeScript compilation. By changing the compilation configuration, we can make TypeScript compile ES6, ES5, and node code.

 important field

  •  files – Sets the name of the files to be compiled;

  • include – Sets the files to be compiled, supports path pattern matching;

  • exclude – Sets files that do not need to be compiled, supports path pattern matching;

  • compilerOptions – Sets options related to the compilation process.

By lzz

Leave a Reply

Your email address will not be published. Required fields are marked *