Knowledge to prepare

  • Read the React and TypeScript official documentation

  • You are advised to enable “noImplicitAny”: true in tsconfig.json

  • Put up two TypeScript knowledge brain maps

Established a full-stack large front-end technology exchange group, please add wechat: Mokinzhao

Component introduction

  • Function component
// ✅ better is recommended
const WrapComponent: React.FC<ExtendedProps> = (props) = > {
 // return ...
};
// Use it directly
export default WrapComponent;

/ / or
export default function (props: React.PropsWithChildren<SpinProps>) {
 // return ...
}

Copy the code
  • Class components
type IEProps { Cp? : React.ComponentClass<{ id? : number }>; } type IEState {id: number; }
// ✅ better is recommended
class ClassCpWithModifier extends React.Component<Readonly<IEProps>, Readonly<IEState>> {
 private gid: number = 1;
 public state: Readonly<IEState> = { id: 1 };
 render() { return this.state.id = 2; } // ts(2540)
}

Copy the code
  • A type that can be used for both
React.ComponentType<P> = React.ComponentClass<P> | React.FunctionComponent<P>;

Copy the code

Element

  • onClick and onChange
  // click to use react. MouseEvent with a dom generic type
  // HTMLInputElement represents the input tag. Another common one is HTMLDivElement
  const onClick = (e: React.MouseEvent<HTMLInputElement>) = >{}; ✅ better// onChange uses a react. ChangeEvent plus a DOM generic type
  / / are generally not HTMLInputElement HTMLSelectElement could also be used
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) = >{}; ✅ betterreturn<ProForm<DataType> /> {' DataType 'set the type of the render line, Params is the submission type of the parameter. ValueType indicates the customized ValueType. <ProTable<DataType, Params, ValueType> /> <input onClick={onClick} onChange={onChange} /> </>);Copy the code
  • Forms and onSubmit
port * as React from 'react'

type changeFn = (e: React.FormEvent<HTMLInputElement>) = > void

const App: React.FC = () = > {

  const [state, setState] = React.useState(' ')

  const onChange: changeFn = e= >{setState(e.currenttarget.value)} ✅ betterconst onSubmit = (e: React.SyntheticEvent) = > {
    e.preventDefault()
    const target = e.target as typeof e.target & {
      password: { value: string }
    } // Type extension
    const password = target.password.value
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" value={state} onChange={onChange} />
    </form>)}Copy the code

Hooks used

  • useState
// It can be used directly when given an initial value

import { useState } from 'react';
// ...
const [val, toggle] = useState(false);
// val is inferred to be a Boolean type
// Toggle can only handle Boolean types


// There is no initial value (undefined) or initial null

type AppProps = { message: string };
const App = () = > {
    const [data] = useState<AppProps | null> (null); ✅ better// const [data] = useState<AppProps | undefined>();
    return <div>{data? .message}</div>;
};

Copy the code
  • useEffect
function DelayedEffect(props: { timerMs: number }) {
    const { timerMs } = props;

    useEffect(() = > {
        const timer = setTimeout(() = > {
            /* do stuff */
        }, timerMs);
        
        / / is optional
        return () = > clearTimeout(timer);
    }, [timerMs]);
    / / return void ✅ ensure function or a return void | undefined cleaning function
    return null;
}

// Async request:

/ / ✅ better
useEffect(() = >{(async() = > {const { data } = await ajax(params);
        // todo}) (); }, [params]);// Or then can also be used
useEffect(() = > {
    ajax(params).then(({ data }) = > {
        // todo
    });
}, [params])

Copy the code
  • useRef
function TextInputWithFocusButton() {
    // Initialize to null, but tell TS that it wants HTMLInputElement type
    // inputEl can only be used with input elements
    const inputEl = React.useRef<HTMLInputElement>(null);
    const onButtonClick = () = > {
        // TS will check the inputEl type and initialize null to have no focus attribute on current
        // You need to customize the judgment!
        if (inputEl && inputEl.current) {
            inputEl.current.focus();
        }
        / / ✅ bestinputEl.current? .focus(); };return (
        <>
            <input ref={inputEl} type="text" />
            <button onClick={onButtonClick}>Focus the input</button>
        </>
    );
}

Copy the code
  • useReducer

When using useReducer, use the Discriminated Unions to accurately identify and narrow the payload type of a specific type. Generally, the reducer return type needs to be defined; otherwise, TS is automatically derived.

const initialState = { count: 0 };

// ❌ bad, you may pass in an undefined type or an incorrect word, and you need to be compatible with different types of payload
// type ACTIONTYPE = { type: string; payload? : number | string };

/ / ✅ good
type ACTIONTYPE =
    | { type: 'increment'; payload: number }
    | { type: 'decrement'; payload: string }
    | { type: 'initial' };

function reducer(state: typeof initialState, action: ACTIONTYPE) {
    switch (action.type) {
        case 'increment':
            return { count: state.count + action.payload };
        case 'decrement':
            return { count: state.count - Number(action.payload) };
        case 'initial':
            return { count: initialState.count };
        default:
            throw new Error();
    }
}

function Counter() {
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
        <>
            Count: {state.count}
            <button onClick={()= > dispatch({ type: 'decrement', payload: '5' })}>-</button>
            <button onClick={()= > dispatch({ type: 'increment', payload: 5 })}>+</button>
        </>
    );
}

Copy the code
  • useContext

UseContext is used in combination with useReducer to manage global data flows.

interface AppContextInterface {
    state: typeof initialState;
    dispatch: React.Dispatch<ACTIONTYPE>;
}

const AppCtx = React.createContext<AppContextInterface>({
    state: initialState,
    dispatch: (action) = > action,
});
const App = (): React.ReactNode => {
    const [state, dispatch] = useReducer(reducer, initialState);

    return (
        <AppCtx.Provider value={{ state.dispatch}} >
            <Counter />
        </AppCtx.Provider>
    );
};

/ / consumer context
function Counter() {
    const { state, dispatch } = React.useContext(AppCtx);
    return (
        <>
            Count: {state.count}
            <button onClick={()= > dispatch({ type: 'decrement', payload: '5' })}>-</button>
            <button onClick={()= > dispatch({ type: 'increment', payload: 5 })}>+</button>
        </>
    );
}
Copy the code
  • Custom Hooks

The beauty of Hooks is not just that they reduce the power of lines of code, but that they separate logic from THE UI. Do pure logic layer reuse. Example: When you customize Hooks, return an array with elements of definite types, not union types. Const-assertions can be used.

// Example: When you customize Hooks, return an array with elements of definite types, not union types. Const-assertions can be used.

export function useLoading() {
    const [isLoading, setState] = React.useState(false);
    const load = (aPromise: Promise<any>) = > {
        setState(true);
        return aPromise.finally(() = > setState(false));
    };
    return [isLoading, load] as const; / / infer [Boolean, typeof load], rather than the joint type (Boolean | typeof load) []
}

// You can also assert a tuple type.

export function useLoading() {
    const [isLoading, setState] = React.useState(false);
    const load = (aPromise: Promise<any>) = > {
        setState(true);
        return aPromise.finally(() = > setState(false));
    };
    return [isLoading, load] as [
        boolean.(aPromise: Promise<any>) = > Promise<any>]; }// If you need more than one of these, you can use generics to define a helper function and use TS autoinference.

function tuplify<T extends any[] > (. elements: T) {
    return elements;
}

function useArray() {
    const numberValue = useRef(3).current;
    const functionValue = useRef(() = > {}).current;
    return [numberValue, functionValue]; // type is (number | (() => void))[]
}

function useTuple() {
    const numberValue = useRef(3).current;
    const functionValue = useRef(() = > {
    }).current;
    return tuplify(numberValue, functionValue); // type is [number, () => void]
}


Copy the code

other

When to use generics

When you have a function, an interface, or a class that needs to be applied to many types, when you need an ID function that can take any value as an argument, the return value just returns the argument as it is, and it can only take one argument, it’s easy to throw out a line in the JS era

const id = arg= > arg

Copy the code

Since it can take any value, that is to say, our function’s input and return values should be of any type. If we do not use generics, we have to repeat the definition

type idBoolean = (arg: boolean) = > boolean

type idNumber = (arg: number) = > number

type idString = (arg: string) = > string
Copy the code

If we use generics, we just need to


function id<T> (arg: T) :T {
  return arg
}

/ / or

const id1: <T>(arg: T) = > T = arg= > {
  return arg
}

Copy the code
  • Partial is used in a number of places, such as the common utility generic Partial.

The function is to make the attributes of the type optional. Note that this is shallow Partial

type Partial<T> = { [P inkeyof T]? : T[P] }Copy the code

If we need deep Partial we can do it with generic recursion

type DeepPartial<T> = T extends Function

  ? T

  : T extends object

  ? { [P inkeyof T]? : DeepPartial<T[P]> } : T type PartialedWindow = DeepPartial<Window>Copy the code

When to select Type and Interface

  • Use interfaces when defining public apis, such as editing a library, so that users can easily inherit the interface
  • When defining component properties (Props) and states (State), it is recommended to use Type because type is more restrictive
  • The type type cannot be edited twice, and the interface can be extended at any time

Request to packaging

interface RequestMethodInUmi<R = false> {
  <T = any>(
    url: string.options: RequestOptionsWithResponse & { skipErrorHandler? :boolean },
  ): Promise<RequestResponse<T>>; <T = any>( url: string, options: RequestOptionsWithoutResponse & { skipErrorHandler? : boolean }, ): Promise<T>; <T = any>( url: string, options? : RequestOptionsInit & { skipErrorHandler? : boolean }, ): R extends true ? Promise<RequestResponse<T>> : Promise<T>; } const request: RequestMethodInUmi = (url: any, options: any) => { const requestMethod = getRequestMethod(); return requestMethod(url, options); }; declare namespace API { type ResponseInfoStructure = { success: boolean; // if request is success data? : Record<string, never>; // response data message? : string; errorCode? : string; // code for errorType errorMessage? : string; // message display to user }; } export async function getFakeCaptcha( params: { // query phone? : string; }, options? : { [key: string]: any }, ) { return request<API.ResponseInfoStructure>('/api/login/captcha', { method: 'GET', params: {... params, }, ... (options || {}), }:); }Copy the code
  • Recommended front-end and back-end integration plug-ins
  1. Swagger typescript – API github.com/acacode/swa…
  2. Yapi – to – typescript github.com/fjc0k/yapi-…
  3. GraphQL Code Generator www.graphql-code-generator.com

The commonly used skill

  • The object type is Record

    instead of {} and object
    ,>
type ObjectTypes = {
    objBetter: Record<string, unknown>; // ✅ better instead of obj: object
    
    // For obj2: {}; There are three cases:
    obj2Better1: Record<string, unknown>; // ✅ better
    obj2Better2: unknown; / / ✅ any value
    obj2Better3: Record<string.never>; // ✅ empty object
    
    /** Record */
    dict1: {
        [key: string]: MyTypeHere;
    };
    dict2: Record<string, MyTypeHere>; // Equivalent to dict1
};

/ / benefits:

//1. When you write home, type h.
//2. "home" is spelled as "hoem".
//3. Narrow the receiving boundary.

Copy the code
  • It is not recommended to give Function type directly. It is best to have explicit parameter type, number and return value type
type FunctionTypes = {
    onSomething: Function; // ❌ bad, not recommended. Any callable function
    onClick: () = > void; // ✅ better, specify a function with no arguments and no return value
    onChange: (id: number) = > void; // ✅ better, a function whose argument has no return value
    onClick(event: React.MouseEvent<HTMLButtonElement>): void; / / ✅ better
};
Copy the code
  • The React Prop type
export declare interface AppProps {
    children1: JSX.Element; // ❌ bad, the array type is not considered
    children2: JSX.Element | JSX.Element[]; // ❌ does not consider character types
    children3: React.ReactChildren; // ❌ name bluff, tool type, use with caution
    children4: React.ReactChild[]; // better, but null is not considered
    children: React.ReactNode; // ✅ best, best accept all children types
    functionChildren: (name: string) = > React.ReactNode; // ✅ returns to the React nodestyle? : React.CSSProperties;// React styleonChange? : React.FormEventHandler<HTMLInputElement>;// Form event! The generic parameter is the type of 'event.target'
}
Copy the code
  • Use lookup types to access component property types
// Great
import Counter from './d-tips1'

type PropsNew = React.ComponentProps<typeof Counter> & {

  age: number

}

const App: React.FC<PropsNew> = props= > {

  return <Counter {. props} / >

}
export default App

Copy the code
  • Promise type

type IResponse<T> = {
  message: string
  result: T
  success: boolean
}

async function getResponse() :Promise<IResponse<number[] > >{
  return {
    message: 'Obtain success'.result: [1.2.3].success: true,
  }
}

getResponse().then(response= > {
  console.log(response.result)
})
Copy the code
  • Typeof /instanceof/in/is: Type guards are used for type discrimination
//typeof
function doSome(x: number | string) {
  if (typeof x === 'string') {
    // In this block, TypeScript knows that 'x' must be of type 'string'
    console.log(x.subtr(1)); // Error: the 'subtr' method does not exist on 'string'
    console.log(x.substr(1)); // ok
  }

  x.substr(1); // Error: 'x' cannot be guaranteed to be 'string'
}

//instanceof

class Foo {
  foo = 123;
  common = '123';
}
class Bar {
  bar = 123;
  common = '123';
}
function doStuff(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.foo); // ok
    console.log(arg.bar); // Error
  }
  if (arg instanceof Bar) {
    console.log(arg.foo); // Error
    console.log(arg.bar); // ok
  }
}
doStuff(new Foo());
doStuff(new Bar());

//in

interface A {
  x: number;
}

interface B {
  y: string;
}

function doStuff(q: A | B) {
  if ('x' in q) {
    // q: A
  } else {
    // q: B}}//is

function isString(test: any) :test is string{
    return typeof test === 'string';
}

function example(foo: number | string){
    if(isString(foo)){
        console.log('it is a string' + foo);
        console.log(foo.length); // string function
    }
}
example('hello world');
// is is the keyword "type predicate" to narrow the type of the argument. When using test is string, we know that isString(foo) === true is a string argument. Boolean does not have this ability The meaning of the is keyword.

Copy the code
  • Index/map/condition/assertion type

// Use the extends keyword to determine the subtype relationship between two types
  type isSubTyping<Child, Par> = Child extends Par ? true : false;
  type isAssertable<T, S> = T extends S ? true :  S extends T ? true : false;
  type isNumAssertable = isAssertable<1.number>; // true
  type isStrAssertable = isAssertable<string.'string'>; // true
  type isNotAssertable = isAssertable<1.boolean>; // false

// Type inference in conditional types
{
  type ElementTypeOfObj<T> = T extends { name: infer E; id: infer I } ? [E, I] : never;
  type isArray = ElementTypeOfObj<{ name: 'name'; id: 1; age: 30} >.// ['name', 1]
  type isNever = ElementTypeOfObj<number>; // never
}
// keyof: Obtains the keyof an object
  type MixedObjectKeys = keyof MixedObject; // string | number
  type animalKeys = keyof animal; // 'type' | 'age'
  type numberIndexKeys = keyof numberIndex; // "type" | "age" | "nickname"

// O[K]: attribute search
class Images {
    public src: string = 'https://www.google.com.hk/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png'
    public alt: string = 'Google'
    public width: number = 500
}
type propsNames = keyof Images
type propsType = Images[propsNames]

// [K in O] : indicates the mapping type
  type SpecifiedKeys = 'id' | 'name';
  type TargetType = {
    [key in SpecifiedKeys]: any;
  }; // { id: any; name: any; }
  type TargetGeneric<O extends string | number | symbol> = {
    [key in O]: any;
  }
  type TargetInstance = TargetGeneric<SpecifiedKeys>; // { id: any; name: any; }

/ *! : non-empty assertion - */ is not recommended
const data={
    a:' '
    b: {c:' '} } data! .a! .c/* as as: double assertion */

function handler(event: Event) {
  const element = (event as any) as HTMLElement; // ok
}

/* as const const const */

// type '"hello"'
let x = "hello" as const
// type 'readonly [10, 20]'
let y = [10.20] as const
// type '{ readonly text: "hello" }'
let z = { text: "hello" } as const
/ / advantages:
Object literal property, get readonly property, become read-only property
Array literals become readonly tuples
//3. Literal types cannot be extended (e.g. from hello to string)
Copy the code

Common Tool Types

  • The Partial object or interface property becomes optional
type partial<T> = { [K inkeyof T]? : T[K] }Copy the code
  • The Required object or interface property becomes mandatory
type Required<T> = {
  [P inkeyof T]-? : T[P]; };Copy the code
  • Pick extracts the specified property as a new type
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};
Copy the code
  • Omit specifies attributes as new types
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Copy the code
  • Readonly Set all properties to read-only
type MyReadonly<T> = {
    readonly [K in keyof T]: T[K]
}
Copy the code
  • Exclude Removes the specified type from the union type
type Exclude<T, U> = T extends U ? never : T;
Copy the code
  • Extract Extracts the specified type from the union type
type Extract<T, U> = T extends U ? T : never;
Copy the code
  • NonNullable Removes the null or undefined type from the union type
type NonNullable<T> = T extends null | undefined ? never : T;
Copy the code
  • record
type MyExclude<T, K> = T extends K ? never : T;
Copy the code

Reference documentation

::: Tip React+TypeScript Cheatsheets ⭐️⭐️⭐… : : :

::: Tip React + TypeScript practices ⭐️⭐️ ️… : : :