React+Typescript best practices

preface

By the way, TypeScript is a typed superset of JavaScript that can be compiled into pure JavaScript. For example, create an index.ts file, create a function, CD it to that file, execute TSC index.ts, and compile to js. That is, of course, if typescript is installed globally. There are plenty of reasons on the web for typescript’s benefits that I won’t go over here, but from my development experience:

  • Typescript is a statically typed language that exposes many unintended problems during development, resulting in fewer bugs and less abusive testing;

  • Also, Typescript projects are highly maintainable because many types are defined during development;

  • Most importantly, Typescript has a great ability to infer types and interfaces, so you can hit the back of the car as long as you’ve defined the types and interfaces before you develop them. Of course WHAT I’m declaring here is the process of developing and writing Typescript, because I’ve seen a lot of Typescript written for Typescript’s sake. They implement features first and then fix TS.

Let’s take a look at Typescript from a real project.

Start the

  • If your project originally runs JS+React and plans to migrate to Typescript, I highly recommend that you do so by reading my other article on the AnDesignPro project migrating TS and formatting it with Eslint+Prettier code

  • If you’re building a project from scratch, execute the following command to generate a React+Typescript project. The formatting tool for esLint +prettier code is then configured, as shown in the previous article.

create-react-app my-app –template typescript

Best practices

Component definition

Component definitions can be divided into function components and class components.

  • Functional components, by far the most popular way to define components, are written in two ways.
import React from 'react';

// function declaration
function Heading({}:IProps) :React.ReactNode {
  return <h1>My Website Heading</h1>;
}

// Function extension
const OtherHeading: React.FC<IProps> = () = > <h1>My Website Heading</h1>;
Copy the code

The function returns a react. ReactNode type.

The second way to write it is to use a function expression that returns a function instead of a value or expression, so specify that the function returns a value of type react. FC.

  • Class components
interface StateProps { }

interface IProps { }

class Page extends React.Component<IProps.StateProps> {
    constructor(pops){}componentDidMount(){}render() {
        return (
            <div>
                
            </div>); }}Copy the code

Props

In TypeScript, we can use the interface or type keyword to declare props as generic.

  • First, the function component is written
import React from 'react';
import { connect } from "dva";
import { ThunkDispatch } from "redux-thunk";
import { AnyAction } from "redux";
// Global State type for the project
import { State } from '@/types';

// Derive the type extracted from redux from mapStateToProps, provided that the type has been declared in state
type MapStateToProps = Readonly<ReturnType<typeof mapStateToProps>>

// Derive the type of asynchronous methods from mapDispatchToProps
type MapDispatchToProps = Readonly<ReturnType<typeof mapDispatchToProps>>

// Deduce from the default the type that the parent passes to the child
type DefaultProps = Readonly<typeof defaultProps>

// make up IProps
type IProps =DefaultProps & MapStateToProps & MapDispatchToProps

const defaultProps = {
    name: 'FrontDream'.age: 18
}

const Content = ({
    //Deconstruct your props name, age}: IProps) = > {

    return (
        <div>

        </div>)}const mapStateToProps = (state: State) = > {
    return {
        items: state.Info.items,
    }
}

const mapDispatchToProps = (dispatch: ThunkDispatch<any.any, AnyAction>) = > ({
    fetchData: () = > {
        dispatch({
            type: "receiptInfo/fetch",})}})// Set component defaults
Content.defaultProps = defaultProps

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Content)

Copy the code
  • Then introduceClass componentsIProps
import React from 'react';
import { connect } from "dva";
import { ThunkDispatch } from "redux-thunk";
import { AnyAction } from "redux";
// Global State type for the project
import { State } from '@/types';

// The default value passed by the parent component to that component
const defaultProps = {
    name: 'FrontDream'.age: 18
}

const initState = {
    content: ' '
}

// Derive the type extracted from redux from mapStateToProps, provided that the type has been declared in state
type MapStateToProps = Readonly<ReturnType<typeof mapStateToProps>>

// Derive the type of asynchronous methods from mapDispatchToProps
type MapDispatchToProps = Readonly<ReturnType<typeof mapDispatchToProps>>

// Deduce from the default the type that the parent passes to the child
type DefaultProps = Readonly<typeof defaultProps>

// make up IProps
type IProps = DefaultProps & MapStateToProps & MapDispatchToProps

// Deduce the component's state type from initState
type StateProps = Readonly<typeof initState>

class Content extends React.Component<IProps.StateProps> {
    static readonly defaultProps: DefaultProps = defaultProps
    
    readonly state: StateProps = initState

    componentDidMount(){}render() {
        return (
            <div>

            </div>); }}const mapStateToProps = (state: State) = > {
    return {
        items: state.Info.items,
    }
}

const mapDispatchToProps = (dispatch: ThunkDispatch<any.any, AnyAction>) = > ({
    fetchData: () = > {
        dispatch({
            type: "receiptInfo/fetch",})}})export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Content)

Copy the code

The IProps can be divided into several subsets, which can be classified into the following four categories based on actual application scenarios:

  • DefaultProps, a property passed directly from the parent component to the current component;
type DefaultProps = Readonly<typeof defaultProps> // Deduce from the default the type that the parent passes to the child
Copy the code
  • MapStateToPropsThrough theconnectreduxIn thestoreAcquired properties;
type MapStateToProps = Readonly<ReturnType<typeof mapStateToProps>>  // Derive the type extracted from redux from mapStateToProps, provided that the type has been declared in state
Copy the code
  • MapDispatchToPropsThrough theconnectmapDispatchToPropsGet asynchronous function;
type MapDispatchToProps = Readonly<ReturnType<typeof mapDispatchToProps>>  // Derive the type of asynchronous methods from mapDispatchToProps
Copy the code
  • RoutePropsThrough thereact-router-domIn therouteAttributes of route delivery

Multiple props can be connected using an ampersand, as in the example above.

It is important to note that the parameters passed by route are not taken into account in the above parameters. It is a bit more complicated to consider when you need to consider them.

import React from 'react';
import { connect } from "dva";
import { ThunkDispatch } from "redux-thunk";
import { AnyAction, compose } from "redux";
import {withRouter, WithRouterProps} from 'react-router'
// Global State type for the project
import { State } from '@/types';

// The default value passed by the parent component to that component
const defaultProps = {
    name: 'FrontDream'.age: 18
}

const initState = {
    content: ' '
}

// Derive the type extracted from redux from mapStateToProps, provided that the type has been declared in state
type MapStateToProps = Readonly<ReturnType<typeof mapStateToProps>>

// Derive the type of asynchronous methods from mapDispatchToProps
type MapDispatchToProps = Readonly<ReturnType<typeof mapDispatchToProps>>

// Deduce from the default the type that the parent passes to the child
type DefaultProps = Readonly<typeof defaultProps>

// make up IProps
type IProps = DefaultProps & MapStateToProps & MapDispatchToProps & WithRouterProps

// Deduce the component's state type from initState
type StateProps = Readonly<typeof initState>

class Content extends React.Component<IProps.StateProps> {
    static readonly defaultProps: DefaultProps = defaultProps

    readonly state: StateProps = initState

    componentDidMount(){}render() {
        return (
            <div>

            </div>); }}const mapStateToProps = (state: State) = > {
    return {
        items: state.Info.items,
    }
}

const mapDispatchToProps = (dispatch: ThunkDispatch<any.any, AnyAction>) = > ({
    fetchData: () = > {
        dispatch({
            type: "receiptInfo/fetch",})}})export default compose<React.ComponentClass<DefaultProps>>(
    withRouter,
    connect(
        mapStateToProps,
        mapDispatchToProps
    )(Content)
)
Copy the code

Compare that carefully.

Now, a lot of people are wondering why this isn’t like the way I usually want to write things, like this:

// Examples copied from other people's best practices, I wonder, can such practices be called best practices?
typeProps = { color? :string; name? :string
  children: React.ReactNode;
  onClick: ()  => void;
}
Copy the code

I dare say this is called dumb Typescript, not smart Typescript! . Why do you say that? Let me tell you what the problem is.

  1. This is written without setting the componentdefaultPropsAnd there is no setting initialinitstate. The most important thing in a program is fault tolerance. When an error occurs, the system can not crash, and there is enough robustness. This is a good system, to the componentspropsandstateSetting the default and initial values improves the robustness and fault tolerance of the system.
  2. Following the foolproof notation above, when the system needs to add new features, or new requirements, new ones are addedpropsorstateThere are two things that need to be modified, one isstateorprops, one isstateorpropsThe correspondinginterface. In other words, I want to change one feature, but I need to change two things. On the other hand, the derivation aboveTSWrite it, passReturnTypeandtypeofCombined with the derivation of the type, you can only write in one place, and the type is more accurate!

What do you think? Go for it?

Get the props done, and the rest of the development is done.

Use in Hooks

  1. useState
  • String type

    With useState, TypeScript can automatically infer the type, so just give an empty string

    const [name, setName] = useState(' ');
Copy the code
  • object

    You need to initialize state with a null value, which can be passed using generics.

    type User = {
        name: string;
        age: number;
    };
    const [user, setUser] = useState<User | null> (null);
    / / or
    const [user, setUser] = useState({} as User);
Copy the code
  • An array of
    type User = {
        name: string;
        age: number;
    };
    const [user, setUser] = useState<Array<User>>([]);
    / / or
    const [user, setUser] = useState([] as User[]);
Copy the code

2.useEffect

There is no return value and no need to pass the type.

3.useLayoutEffect

There is no return value and no need to pass the type.

4.useRef

When useRef passes a non-empty initial value, it can infer the type, passing the first generic parameter to define the type of ref.current. The following is an example:

// Store div dom
const divRef = React.useRef<HTMLDivElement | null> (null);

// Store button DOM
const buttonRef = React.useRef<HTMLButtonElement | null> (null);

// Store the BR DOM
const brRef = React.useRef<HTMLBRElement | null> (null);

// Store a DOM
const linkRef = React.useRef<HTMLLinkElement | null> (null);

Copy the code

Other tags are similar

  1. useCallback

    There is no need to pass a type, but note that the function’s input parameter needs to define a type, otherwise it is inferred to be any.

    const part = 2;
    
    const res = useCallback((value: number) = > value * part, [part]);
Copy the code

6.useMemo

UseMemo does not need to pass in the type and can infer the type from the return value of the function.

const res = React.useMemo(() = > {
  complexComputed(a, b)
}, [a, b])
Copy the code

7.useContext

CreateContext defines the types that need to be passed down through generics. Take ali’s AntDesign form as an example:

import React from 'react';
import { WrappedFormUtils } from 'antd/es/form/Form';

export interface Cubicle extends Partial<any> { }

export interfaceIFormContext { form? : WrappedFormUtils;type? :string; changeCubicles? (data: Cubicle[]):void;
}

const FormContext = React.createContext<IFormContext>({});
Copy the code

When used, it can be directly:

    const { form, type } = useContext(FormContext);
Copy the code

8.userReducer

The useReducer accepts two parameters, Reducer and initialState. The reducer type can be inferred only by type constraints on the input state and action of the Reducer function passed in to the useReducer. When using useReducer or Redux, you need to use the combined type.

Take the following example for reference

import React, { useReducer } from 'react';

const initialState = { count: 0 };

/ * * joint type, use | link * /
type ACTIONTYPE =
| { type: 'increment'; payload: number }
| { type: 'decrement'; payload: string }
| { type: 'reset'; payload: number };

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 'reset':
            return { count: action.payload };
        default:
            throw new Error();
    }
}

const Counter: React.FC = () = > {
    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>
            <button onClick={()= > dispatch({ type: 'reset', payload: 0 })}>reset</button>
        </>
    );
};

export default Counter;
Copy the code

The event processing

  1. Form eventsonChangeThe event
import React from 'react';

const Input: React.FC = () = > {
    const [value, setValue] = React.useState(' ');
    
    const onChange = (e: React.ChangeEvent<HTMLInputElement>) = > {
        setValue(e.target.value);
    };

    return <input value={value} onChange={onChange} />;
};

export default Input;

Copy the code

Forms are often used to collect information about internal state. They are primarily used for login and registration pages, so submitted information can be collected through forms and sent to the server for processing.

React.FormEvent Is usually used for element event types:

class App extends React.Component<> {
    clickHandler = (e: React.FormEvent<HTMLButtonElement>) = > {
        // ...
    }

    changeHandler = (e: React.FormEvent<HTMLInputElement>) = > {
        // ...
    }

    render() {
        return (
            <div>
                <button onClick={this.clickHandler}>Click</button>
                <input type="text" onChange={this.changeHandler} />
            </div>)}}Copy the code

Both clickHandler and changeHandler event arguments e have type react. FormEvent.

We can omit the arguments to the react. FormEvent input handler and use the return value of the input handler instead. This is done by using: React.ChangeEventHandler:

class App extends React.Component<> {
    clickHandler: React.FormEvent<HTMLButtonElement> = (e) = > {
        // ...
    }

    changeHandler: React.FormEvent<HTMLInputElement> = (e) = > {
        // ...
    }

    render() {
        return (
            <div>
                <button onClick={this.clickHandler}>Click</button>
                <input type="text" onChange={this.changeHandler} />
            </div>)}}Copy the code
  1. aandbuttonClick event of
import React from 'react';

const Button: React.FC = () = > {
  const handleClick = (event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>) = >{};return (
    <div>
      <button onClick={handleClick}>click</button>
      <a href="" onClick={handleClick}>
        link
      </a>
    </div>
  );
};

export default Button;

Copy the code

3.TextArea

This refers to the React.ChangeEvent of the text area element, which is an instance of HTMLTextAreaElement.

The generic T is HTMLTextAreaElement. Events that the React. ChangeEvent.

4.Select

Select the same as textarea, but the element type is HTMLSelectElement and the event is React.ChangeEvent.

  1. Form

Similarly, the type is HTMLFormElement and the event is react.changeEvent.

  1. Video, Audio

Similarly, the type is HTMLVideoElement and HTMLAudioElement, and the event is react.ChangeEvent.

7.React.SyntheticEvent

This type is when you don’t care about type:

class App extends React.Component<> {
    submitHandler = (e: React.SyntheticEvent) = > {
        // ...
    }

    render() {
        return (
            <form onSubmit={this.submitHandler}>.</form>)}}Copy the code
  1. Other events
DragEvent<T = Element> DrageEvent <T = Element> Change KeyboardEvent<T = Element> keyboard event object MouseEvent<T = Element> MouseEvent object TouchEvent<T = Element> TouchEvent object WheelEvent<T = Element> WheelEvent object AnimationEvent<T = Element> AnimationEvent object TransitionEvent<T = Element> TransitionEvent objectCopy the code

Axios request

Axios requests are made primarily by defining a global response type and leaving generics to allow users to define their own types for each interface, as you can see in the following example:

// Global background return type
interface ResponseData<T = any> {
  code: number
  result: T
  message: string
}

// Each interface has its own return type
interface User {
  name: string
  age: number
}

function getUser<T> () {
  return axios<ResponseData<T>>('/extend/user')
    .then(res= > res.data)
    .catch(err= > console.error(err))
}


async function test() {
  const user = await getUser<User>()
  if (user) {
    console.log(user.result.name)
  }
}

test()
Copy the code

Of course, if your asynchronous request tool is not Axios then it can do the same thing and also define the global response type and the response type of the business interface and pass it in through generics.

❤️ Love triple punch

1. Please click “Watching” to support me when you see here. Your “watching” is the motivation for my creation.

2. Pay attention to the public number front-end dreamers, “learn front-end together”!

3. Add wechat [QDW1370336125] to pull you into the technical exchange group to learn together.