Several stack technology sharing front end: TS, see where you escape~

Posted by acke1976 on Tue, 07 Dec 2021 21:39:46 +0100

Write in front

The difficulty of this paper is medium to low. Most of the points involved are how to reasonably apply ts in the project, and a small part will involve some principles. It has a wide audience. It can be eaten with or without TS foundation

After reading this article, you may gain:

1. If you are not familiar with TS, this article can help you complete the study of ts application, accompanied by many Demo examples to guide business applications.

2. If you are familiar with TS, this article can be used as a review article to review your knowledge, hoping to trigger your new discovery and thinking at some points.

3. For IState and IProps of class components, compare some writing methods and thinking of Hook components.

TIPS: super easy to use online TS editor (many configuration items can be configured manually)

Portal: https://www.typescriptlang.org/

What is TS

In general, TypeScript is a superset of JavaScript. It has optional types and can be compiled into pure JavaScript. (I have always regarded TypeScript as the Lint of JavaScript)

So the question is, why should TS be designed to be static? Or in other words, why do we need to add type specifications to JavaScript?

Classic question and answer session - because it can solve some unresolved pain points of JS: 1. JS is a dynamically typed language, which also means that we don't know the type of variables before instantiation, but using TS can avoid classic low-level errors before running.

Example: Uncaught TypeError: 'xxx' is not a function ⚠️ Error in dictionary level:

JS is like this. It tells me that there is an error only when an error occurs at runtime, but when TS intervenes:

good heavens! Throw the problem directly in the editor stage, nice!

2. Lazy Carnival! The specification is convenient and not error prone. For VS Code, the most it can do is to indicate whether this attribute exists or not, but it can not accurately indicate what type this attribute is, but TS can deduce / reverse deduce through type (in other words, if you do not explicitly write a type, you will use type inference to infer the type you are using), This perfectly optimizes the code completion:

1) The first Q & A -- think: ask: so what JS pain points can we think of in business development? Answer, summary, supplement:

  • Type restrictions on function parameters;
  • Limit the types of arrays and objects to avoid definition errors. For example, array definition errors may occur when data deconstruction is complex or more. A = {}, if (a.length) {/ / xxxxx}
  • let functionA = 'jiawen' / / actually let functionA: string = 'jiawen'

3. Make our application code easier to read and maintain. If the definition is perfect, you can roughly understand the function of parameters through types. I believe that through the above simple bug demo, you have a preliminary re understanding of TS. the next chapter will formally introduce how to make good use of TS in the process of business development.

How to use TS

How to use in business TS/How to use it well TS?This problem is actually related to " How to make good use of one in business API " It's the same. First of all, you should know what this thing is doing, what the parameters are, what the rules are, and what extensions can be accepted......wait. In short, roll it! What extensions......wait. In short, roll it!

1. Induction of common types of TS

Through a comprehensive summary of common TS errors in the business, I hope Demos will benefit you

1) string number boolean of primitives

The reason why the author disassembles the basic types is that the nouns primitives / metalinguals / tuples appear frequently in both Chinese and English documents. The author understands the Vernacular: I hope to use literal values instead of built-in object types in the definition of type constraints. Official documents:

let a: string = 'jiawen';let flag: boolean = false;let num: number = 150interface IState: {  flag: boolean;  name: string;  num: number;}

2) Tuple

// Tuple type represents an array with known element number and type. The type of each element does not have to be the same, but the type of the corresponding position needs to be the same.


let x: [string, number];

x = ['jiawen', 18];   // ok

x = [18, 'jiawen'];    // Erro

console.log(x[0]);    // jiawen

3)undefined null

let special: string = undefined

// It is worth mentioning that undefined/null is a subclass of all basic types,

// Therefore, they can be assigned to other defined types arbitrarily, which is why the above code does not report an error

4) object and {}

// Object represents the normal Javascript object type, not the basic data type

const offDuty = (value: object) => {

  console.log("value is ",  value);

}


offDuty({ prop: 0}) // ok

offDuty(null) offDuty(undefined) // Error

offDuty(18) offDuty('offDuty') offDuty(false) // Error



//  {} represents any type that is not null / undefined

const offDuty = (value: {}) => {

  console.log("value is ", value);

}


offDuty({ prop: 0}) // ok

offDuty(null) offDuty(undefined) // Error

offDuty(18) offDuty('offDuty') offDuty(false) // ok

offDuty({ toString(){ return 333 } }) // ok


//  {} is almost the same as Object, except that Object will verify the built-in toString / hasownproperty of Object

const offDuty = (value: Object) => {

  console.log("value is ",  value);

}


offDuty({ prop: 0}) // ok

offDuty(null) offDuty(undefined) // Error

offDuty(18) offDuty('offDuty') offDuty(false) // ok

offDuty({ toString(){ return 333 } }) // Error


If an object type is required, but there is no requirement for attributes, it is recommended to use object 

{} and Object The scope of representation is too large. It is recommended not to use it as much as possible

5)object of params

// We can usually use point object functions (specify parameter object types) in business


const offDuty = (value: { x: number; y: string }) => {

  console.log("x is ", value.x);

  console.log("y is ", value.y);

}


// Business must involve "optional attributes"; Let's briefly introduce the convenient and fast "optional attributes"


const offDuty = (value: { x: number; y?: string }) => {

  console.log("Required attribute x ", value.x);

  console.log("optional attribute  y ", value.y);

  console.log("optional attribute  y Method of ", value.y.toLocaleLowerCase());

}

offDuty({ x: 123, y: 'jiawen' })

offDuty({ x: 123 }) 


// Question: is there a problem with the above code?


answer:


// Offduty ({X: 123}) will cause the result to report an error value. Y.tolocalelovercase()

// Cannot read property 'toLocaleLowerCase' of undefined


Option 1: Manual type check

const offDuty = (value: { x: number; y?: string }) => {

  if (value.y !== undefined) {

      console.log("May not exist ", value.y.toUpperCase());

  }

}

Scenario 2: use optional attributes (recommend)

const offDuty = (value: { x: number; y?: string }) => {

  console.log("May not exist ", value.y?.toLocaleLowerCase());

}

6) unknown and any

// unknown can represent any type, but it also tells TS that developers cannot determine the type, so they need to be careful when doing any operation


let Jiaven: unknown


Jiaven.toFixed(1) // Error


if (typeof Jiaven=== 'number') {

  Jiaven.toFixed(1) // OK

}


When we use any When you type, any Will escape the type check, and any Variables of type can perform any operation without error during compilation


anyscript === javascript


be careful: any It will increase the risk of errors during operation and should not be used unless absolutely necessary;


If you encounter a scenario that wants to represent [don't know what type], it is recommended to give priority unknown

7) union Union type

union It is also called union type. It is composed of two or more other types. It represents the value that may be any one. It is used between types ' | 'separate


type dayOff = string | number | boolean


Implicit derivation of union types may lead to errors. Please refer to the language guide for related problems code and tips - <TS Implicit derivation of


.It should be noted that an error will be reported when accessing non common attributes, and no error will be reported when accessing common attributes.Last most intuitive demo


function dayOff (value: string | number): number {

    return value.length;

}

// number does not have length, and an error will be reported. Solution: typeof value = = 'string'


function dayOff (value: string | number): number {

    return value.toString();

}

// Both number and string have toString(), and no error will be reported

8)never

// Never is a subtype of other types, including null and undefined, representing values that never appear.


// What role does never play in actual development? Here, the author takes you Yuxi's classic explanation as the first example


The first example, when you have a union type:


interface Foo {

  type: 'foo'

}


interface Bar {

  type: 'bar'

}


type All = Foo | Bar


stay switch Intermediate judgment type,TS It can be narrowed (discriminated union): 


function handleValue(val: All) {

  switch (val.type) {

    case 'foo':

      // Here val is narrowed to Foo

      break

    case 'bar':

      // val, this is Bar

      break

    default:

      // val is never here

      const exhaustiveCheck: never = val

      break

  }

}


Attention in default Inside, we narrowed it down to never of val Assign to an explicit declaration never Variable.



If all the logic is correct, it should be able to compile here. But if one day your colleague changes All Type of:


    type All = Foo | Bar | Baz


But he forgot to be there handleValue In it is added for Baz Processing logic,

At this time default branch inside val Will be narrowed to Baz,Result in failure to assign to never,A compilation error occurred.

So in this way, you can ensure handleValue Always exhausted (exhaust) All All Possible types of
The return value of the second usage is never The function of can be the case of throwing an exception

function error(message: string): never {

    throw new Error(message);

}


The return value of the third usage is never The function of can be an end point that cannot be executed

function loop(): never {

    while (true) {}

}

9)Void

interface IProps {

  onOK: () => void

}

void and undefined The function is highly similar, but void Indicates that you don't care about the return value of the function or that the method has no return value

10)enum

The author believes that ts Medium enum Is a very interesting enumeration type, and its underlying is number Implementation of


1.General enumeration

enum Color {

  Red, 

  Green, 

  Blue

};

let c: Color = Color.Blue;

console.log(c); // 2


2.String Enum 

enum Color {

  Red = 'red', 

  Green = 'not red', 

};


3.Heterogeneous enumeration / Sometimes called mixed enumeration

enum Color {

  Red = 'red', 

  Num = 2, 

};
<First pit>


enum Color {

  A,         // 0

  B,         // 1

  C = 20,    // 20

  D,         // 21

  E = 100,   // 100

  F,         // 101

}


If the initialization has partial assignment, the value of the subsequent member is the value of the previous member plus 1
<Second pit> This pit is the extension of the first pit. If you are not careful, you will be fooled!


const getValue = () => {

  return 23

}


enum List {

  A = getValue(),

  B = 24,  // You must initialize the value here, or the compilation will not pass

  C

}

console.log(List.A) // 23

console.log(List.B) // 24

console.log(List.C) // 25


If the value of a property is calculated, the member following it must initialize the value.

Otherwise it will Enum member must have initializer.

11) Generics

The generic type I understand is very Vernacular: first, do not specify the specific type, and get the specific type through the passed parameter type. Let's start with the following filter demo to explore why generics are necessary

Basic styles for generics

function fun<T>(args: T): T {
  return args
}

If you haven't touched it, will you feel a little confused? No problem! We go directly from a business perspective.

1.Initial requirement: filter arrays of numeric types

declare function filter(
  array: number[], 
  fn: (item: unknown) => boolean
) : number[];

2.The product has changed its requirements: it also filters some strings string[] 

Speed up, then use the overloading of functions, Add a statement, It's a little stupid, but it's easy to understand

declare function filter(
  array: string[],
  fn: (item: unknown) => boolean
): string[];

declare function filter(
  array: number[],
  fn: (item: unknown) => boolean
): number[];

3.Here comes the product again! Filter this time boolean[],object[] ..........

At this time, if you still choose overloading, the workload will be greatly increased and the code will become more and more cumbersome. At this time, generics will appear,
In terms of implementation, it is more like a method to define types through your parameters. The transformation is as follows:

declare function filter<T>(
  array: T[],
  fn: (item: unknown) => boolean
): T[];

When we understand generics as a method implementation, we naturally associate it with: methods have multiple parameters and default values, and generics can also be used.

type Foo<T, U = string> = { // Multi parameter, default value
  foo: Array<T> // Can pass
  bar: U
}

type A = Foo<number> // type A = { foo: number[]; bar: string; }
type B = Foo<number, number> // type B = { foo: number[]; bar: number; }

Since it is a "function", there will also be "restrictions". Some slightly common constraints are listed below.

1. extends: limit T Must be at least one XXX Type of

type dayOff<T extends HTMLElement = HTMLElement> = {
   where: T,
   name: string
}
2. Readonly<T>: Construct a structure with all attributes as readonly,This means that properties of the constructed type cannot be reassigned.

interface Eat {
  food: string;
}

const todo: Readonly<Eat> = {
  food: "meat beef milk",
};

todo.food = "no food"; // Cannot assign to 'title' because it is a read-only property.
3. Pick<T,K>: from T Pick some from the list K attribute

interface Todo {
  name: string;
  job: string;
  work: boolean;


type TodoPreview = Pick<Todo, "name" | "work">;

const todo: TodoPreview = {
  name: "jiawen",
  work: true,
};
todo;
4. Omit<T, K>: Combined T and K And ignore the K To construct the type.

interface Todo {
  name: string;
  job: string;
  work: boolean;
}

type TodoPreview = Omit<Todo, "work">;

const todo: TodoPreview = {
  name: "jiawen",
  job: 'job',
};
5.Record: Constraint definition key type is Keys,Value type is Values Object type of the.

enum Num {
  A = 10001,
  B = 10002,
  C = 10003
}

const NumMap: Record<Num, string> = { 
  [Num.A]: 'this is A',
  [Num.B]: 'this is B'
}
// Missing attribute '10003' in type '{10001: string; 10002: string;}',
// However, this attribute is required in the type "Record < errorcodes, string >", so we can also conduct comprehensive inspection through Record

keyof Keyword can be used to get all of an object type key type
type User = {
  id: string;
  name: string;
};

type UserKeys = keyof User;  // "id" | "name"

The transformation is as follows

type Record<K extends keyof any, T> = {
  [P in K]: T;
};
At this time T by any;
There are some not commonly used but easy to understand:

6. Extract<T, U>  from T,U Extract the same type from

7. Partial<T>    All properties are optional

type User = {
  id?: string,
  gender: 'male' | 'female'
}

type PartialUser =  Partial<User>  // { id?: string, gender?: 'male' | 'female'}
  
type Partial<T> = { [U in keyof T]?: T[U] }

8. Required<T>   All properties must be << === >> And Partial contrary

type User = {
  id?: string,
  sex: 'male' | 'female'
}

type RequiredUser = Required<User> // { readonly id: string, readonly gender: 'male' | 'female'}

function showUserProfile (user: RequiredUser) {
  console.log(user.id) // Don't you need to add it at this time? Yes
  console.log(user.sex)
}
type Required<T> = { [U in keyof T]-?: T[U] };   -? : Represents removal?

Some notes for TS

1. type and interface of TS

1) interface can only declare object types and supports declaration merging (extensible).

interface User {  id: string}interface User {  name: string}const user = {} as Userconsole.log(user.id);console.log(user.name);

2) Type (type alias) does not support declaration merge -- l type

type User = {
  id: string,
}

if (true) {
  type User = {
    name: string,
  }

  const user = {} as User;
  console.log(user.name);
  console.log(user.id) // Property 'id' does not exist on type 'User'.
}

3) Summary of similarities and differences between type and interface:

a. Generally speaking, type is more general, and the right side can be any type, including expression operation, mapping, etc;

b. For those that can be defined by interface, type can also be used;

c. The extension methods are also different. The interface can be extended with the extends keyword or used to implement an interface;

d. Can be used to describe an object or function;

e. Type can declare basic type alias, union type and tuple type, but interface cannot;

f. However, if you are developing a package or module that allows others to expand, use interface. If you need to define basic data types or type operations, use type;

g. The interface can be defined multiple times and will be regarded as a consolidated declaration, but the type does not support it;

h. The export methods are different. The interface supports simultaneous declaration and default export, while the typetype must be declared before export; r/>

2. Script mode and module mode of TS

Typescript has two modes. The logic of distinction is that the file content package does not contain import or export keywords.

1) Script mode (script), a file corresponds to an html script tag.

2) Module mode: a file corresponds to a module of Typescript.

In script mode, all variable definitions and type declarations are global. If multiple files define the same variable, an error will be reported, and the interface with the same name will be merged; In module mode, all variable definitions and type declarations are valid within the module.

There are also differences between the two modes when writing type declarations. For example, in the script mode, declare var GlobalStore can write declarations for global objects directly.

example:

In script mode, declare var GlobalStore can write declarations for global objects directly.

GlobalStore.foo = "foo";
GlobalStore.bar = "bar"; // Error

declare var GlobalStore: {
  foo: string;
};

In module mode, declare global is required to write a declaration for a global object

GlobalStore.foo = "foo";
GlobalStore.bar = "bar";

declare global {
  var GlobalStore: {
    foo: string;
    bar: string;
  };
}

export {}; // export keyword changes the mode of the file

3. Index signature for TS

The index signature can be used to define the types of attributes and values in the object. For example, define a React component, allowing props to pass any props with key as string and value as number

interface Props {
  [key: string]: number
}

<Component count={1} /> // OK
<Component count={true} /> // Error
<Component count={'1'} /> // Error

4. Type of TS

Typescript allows you to use a type just as an object takes a property value

type User = {
  userId: string
  friendList: {
    fristName: string
    lastName: string
  }[]
}

type UserIdType = User['userId'] // string
type FriendList = User['friendList'] // { fristName: string; lastName: string; }[]
type Friend = FriendList[number] // { fristName: string; lastName: string; }

In the above example, we use the function of type typing to calculate several other types from the User type. FriendList[number] number here is a keyword used to get the type of array subitems. You can also use literal numbers in tuples to get the type of array elements.

type group = [number, string]
type First =  group[0] // number
type Second = group[1] // string

5. Assertion of TS

1) Type assertion is not a type conversion. It is not allowed to assert into a type that does not exist in a union type.

function getLength(value: string | number): number {
    if (value.length) {
        return value.length;
    } else {
        return value.toString().length;
    }

    // This problem has been mentioned in the object of parmas and will not be repeated

    After modification:

    if ((<string>value).length) {
        return (<string>value).length;
    } else {
        return something.toString().length;
    }
}
Two ways to write assertions

1. <type>value:  <string>value

2. perhaps value as string

Special attention!!! It is not allowed to assert into a type that does not exist in a union type

function toBoolean(something: string | number): boolean {
    return <boolean>something;
}

2) Non null assertion

TypeScript also has a special syntax for removing null and undefined from types without any explicit checking.

Writing after any expression is actually a type assertion, indicating that the value is not null or undefined

function liveDangerously(x?: number | undefined | null) {
  // Recommended writing
  console.log(x!.toFixed());
}

How to use TS in Hook components

1,usestate

useState has the ability of type derivation if the initial value is not null/undefined, and infers the type according to the incoming initial value; If the initial value is null/undefined, you need to pass the type definition to constrain. In general, it is recommended to pass in the type (through the first generic parameter of useState).

// Here ts you can infer the type of value and constrain the setValue function call
const [value, setValue] = useState(0);

interface MyObject {
  name: string;
  age?: number;
}

// Here, you need to pass MyObject to constrain value and setValue
// Therefore, we generally recommend incoming types
const [value, setValue] = useState<MyObject>(null);

2)useEffect useLayoutEffect

No return value, no type passing and constraints required

3)useMemo useCallback

  • useMemo does not need to pass the type, and the type can be inferred from the return value of the function.
  • useCallback does not need to pass the type, and the type can be inferred from the return value of the function.

However, note that the input parameter of the function needs to define the type, otherwise it will be inferred as any!

const value = 10;

const result = useMemo(() => value * 2, [value]); // It is inferred that result is of type number

const multiplier = 2;
// Infer (value: number) = > number
// Note that the function input parameter value needs to define the type
const multiply = useCallback((value: number) => value * multiplier, [multiplier]);

4)useRef

When useRef passes a non null initial value, the type can be inferred. Similarly, the type can be defined by passing in the first generic parameter to constrain the type of ref.current.

1. If the value is null
const MyInput = () => {
  const inputRef = useRef<HTMLInputElement>(null); // Here, the constraint inputRef is an html element
  return <input ref={inputRef} />
}

2. If not null
const myNumberRef = useRef(0);  // It is automatically inferred that myNumberRef.current is of type number
myNumberRef.current += 1;

5)useContext

useContext generally infers the return value according to the value of the passed in Context. Generally, the delivery type does not need to be displayed.

type Theme = 'light' | 'dark';// We passed the type in createContext const ThemeContext = createContext < theme > ('dark '); Const app = () = > (< ThemeContext. Provider value = "dark" > < mycomponent / > < / ThemeContext. Provider >) const mycomponent = () = > {/ / usecontext infers the type from the ThemeContext. There is no need to display and pass const theme = usecontext (ThemeContext); return < div > the theme is {theme} < / div >

Some thinking

In this paper, the author thinks about the basic application of TS and ts in Hook, but this part is lengthy about how TSC converts TS code into JS code. A separate article can be published later (2) About the bottom implementation of TS generics, this part is more complex, and the author needs to precipitate. You are welcome to leave a message directly or add it in the article!!!