Pre – work

  1. Read the TS section in the React Doc
  2. Read the React section on the TypeScript Playground

    Introducing React: Always use namespace imports

    import * as React from 'react'; import { useState } from 'react'; Import * as ReactDom from 'react-dom'; import * as ReactDom from 'react-dom';

    Import React from ‘React’ is not recommended. 👉 Why? This introduction is called Default Export. The reason it is not recommended is that React exists only as a namespace. We don’t use React as if it were a value. Since React 16.13.0, the export of React has been changed to export {XXX,… } in the form of (commit). Default Export is still available because the React build product is the CommonJS specification and Webpack and other build tools are compatible.

Component development

1. Use the Function Component declaration as much as possible, i.e. React.fc:

export interface Props { /** The user's name */ name: string; /** Should the name be rendered in bold */ priority? : boolean } const PrintName: React.FC<Props> = (props) => { return ( <div> <p style={{ fontWeight: props.priority ? "bold" : "normal" }}>{props.name}</p> </div> ) }

I usually use vscode snippets to quickly generate it during development:

"Functional, Folder Name": {
    "prefix": "ff",
    "body": [
      "import * as React from 'react';",
      "",
      "const { useRef, useState, useEffect, useMemo } = React;",
      "",
      "",
      "interface ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props {",
      "",
      "}",
      "",
            "const defaultProps: ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props = {};",
            "",
      "const ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}: React.FC<${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props> = (props: React.PropsWithChildren<${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/}Props> = defaultProps) => {",
      "  const { } = props;",
      "",
      "  return (",
      "",
      "  );",
      "};",
      "",
      "export default ${TM_DIRECTORY/.*[\\\\\\\\\\\\/](.*)$/$1/};",
      ""
    ],
    "description": "Generate a functional component template"
  },

Hook associated

It is recommended to install vscode plugins: act Hooks Snippets to quickly write Hooks to improve development efficiency.

2. UstateState <T> : When the initial state value is null, it is recommended to write the full generic type, otherwise the type can be automatically inferred.

If the initial value of some state is null, the type should be explicitly declared:

const [user, setUser] = React.useState<User | null>(null)

Note: If the initial value is undefined, you do not need to add undefined to the generic type.

3. Both useMemo() and useCallback() infer types implicitly, and it is recommended not to pass generics

Note: Do not use UseCallback too often, as it will add overhead. Only if:

  • Packing inReact.memo()(or shouldComponentUpdate) components accept a callback prop;
  • When functions are used as dependencies of other hooksuseEffect(... , the callback).

If you return a hook as an array, you need to manually add a const assertion:

function useLoading() { const [isLoading, setLoading] = React.useState(false); const load = (aPromise: Promise<any>) => { setLoading(true) return aPromise.then(() => setLoading(false)); } / / actual needs: [Boolean, typeof load] type / / not automatically derived: (Boolean | typeof load) [] return [isLoading, load] as const; }

Or you can define the return type directly:

export function useLoading(): [
  boolean,
  (aPromise: Promise<any>) => Promise<any>
] {
  const [isLoading, setState] = React.useState(false)
  const load = (aPromise: Promise<any>) => {
    setState(true)
    return aPromise.then(() => setState(false))
  }
  return [isLoading, load]
}

other

5. Use default parameter values instead of default properties

interface GreetProps { age? : number } const defaultProps: GreetProps = { age: 21 }; const Greet = (props: GreetProps = defaultProps) => { /* ... * /}

– Function Component defaultProps will eventually be deprecated and is not recommended. If you still want to use defaultProps, the following methods are recommended:

interface IProps { name: string } const defaultProps = { age: 25, }; Type GreetProps = IProps & typeof defaultProps; const Greet = (props: GreetProps) => <div></div> Greet.defaultProps = defaultProps; // Use const TestComponent = (props: React.ComponentProps<typeof Greet>) => { return <h1 /> } const el = <TestComponent name="foo" />

6. It is recommended that we use Interface to define components (props). Type is also acceptable, but not mandatory

Type cannot be edited twice, whereas Interface can be extended at any time.

7. Use ComponentProps to get the component parameter type that is not exported, and use ReturnType to get the return value type

Get the type of component parameter:

Props type type ButtonProps = react.componentprops <typeof Button> // props (props type alertButtonprops = Omit<ButtonProps, 'onClick'>) // Remove onClick const alertButton React.FC<AlertButtonProps> = props => ( <Button onClick={() => alert('hello')} {... props} /> )

Get the return value type:

Function foo() {return {baz: 1}} type FooReturn = returnType <typeof foo> // {baz: number}

8. Use/when commenting on type or interface* / for better type hints

/* ✅ */ /** * @param a 1 * @param b 2 */ type Test = {a: string; b: number; }; const testObj: Test = { a: '123', b: 234, };

When hover toTestTime type hints are friendlier:

9. Component Props TS type specification

type AppProps = { /** string */ message: string; /** number */ count: number; /** boolean */ disabled: boolean; /** Names: string[]; / * * string literals * / status: 'waiting' | 'success'; / / obj3: {id: string; title: string; }; /** objArr: {id: string; title: string; } []; /** Dictionary */ dict: Record<string, myTypehere >; /** any Function that will not be called at all */ onSomething: Function; /** / onClick: () => void; */ change: (id: number) => void; /** onClick(e: React.MouseEvent< htmlButtonElement >): void; /** Optional? : OptionalType; children: React.ReactNode; // best, support all types (jsx. Element, jsx. Element[], string) renderChildren: (name: string) => React. style? : React.CSSProperties; onChange? : React.FormEventHandler<HTMLInputElement>; // form event};

10. Event processing in the component

Common types of EVENTL:

React.SyntheticEvent<T = Element> React.ClipboardEvent<T = Element> React.DragEvent<T = Element> React.FocusEvent<T = Element> React.FormEvent<T = Element> React.ChangeEvent<T = Element> React.KeyboardEvent<T = Element> React.MouseEvent<T  = Element> React.TouchEvent<T = Element> React.PointerEvent<T = Element> React.UIEvent<T = Element> React.WheelEvent<T = Element> React.AnimationEvent<T = Element> React.TransitionEvent<T = Element>

Define the event callback function:

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

If you don’t care much about the type of the event, you can use React.SyntheticEvent directly. If the target form has a custom named input that you want to access, you can use a type extension:

const App: React.FC = () => { const 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}> <div> <label> Password: <input type="password" name="password" /> </label> </div> <div> <input type="submit" value="Log in" /> </div> </form> );  };

Try to use optional Channing

12. Try to use React.ComponentProps< Typeof Component> to reduce unnecessary props exports

13. Do not use function declarations in type or interface

/** ✅ */ interface iCounter {start: (value: number) => string} /** ** / interface iCounter1 {start(value: number): string }

14. When local components are combined with multiple components for inter-component state communication, it is not necessary to use mobx or it is recommended to use together with UserEduer () and UseContext () if it is not particularly complex. Best practice of frequent inter-component communication:

Store.ts

import * as React from 'react'; export interface State { state1: boolean; state2: boolean; } export const initState: State = { state1: false, state2: true, }; export type Action = 'action1' | 'action2'; export const StoreContext = React.createContext<{ state: State; dispatch: React.Dispatch<Action>; }>({ state: initState, dispatch: value => { /** noop */ } }); export const reducer: React. <State, Action> = (State, Action) => {// React. <State, Action> = (State, Action) => { return { ... state, state1: ! state.state1 }; case 'action2': return { ... state, state2: ! state.state2 }; default: return state; }};

WithProvider.tsx

import * as React from 'react'; import { StoreContext, reducer, initState } from './store'; const { useReducer } = React; const WithProvider: React.FC<Record<string, unknown>> = (props: Reaction. propsWithChildren <Record<string, unknown>>) => {// Injecting state as root state to child const [state, dispatch] = useReducer(reducer, initState); const { children } = props; return <StoreContext.Provider value={{ state, dispatch }}>{children}</StoreContext.Provider>; }; export default WithProvider;

The parent component:

import * as React from 'react';
import WithProvider from './withProvider';
import Component1 from './components/Component1';
import Component2 from './components/Component2';

const { useRef, useState, useEffect, useMemo } = React;

interface RankProps {}

const defaultProps: RankProps = {};

const Rank: React.FC<RankProps> = (props: React.PropsWithChildren<RankProps> = defaultProps) => {
  const {} = props;

  return (
    <WithProvider>
      <Component1 />
      <Component2 />
    </WithProvider>
  );
};


export default Rank;

The child component only needs to import storeContext and then usContext () to get the state and dispatch

import * as React from 'react'; import { StoreContext } from '.. /.. /store'; const { useContext } = React; interface Component1Props {} const defaultProps: Component1Props = {}; const Component1: React.FC<Component1Props> = (props: React.PropsWithChildren<Component1Props> = defaultProps) => { const { state, dispatch } = useContext(StoreContext); const {} = props; return ( <> state1: {state.state1 ? 'true' : 'false'} <button type="button" onClick={(): void => { dispatch('action1'); }} > changeState1 with Action1 </button> </> ); }; export default React.memo(Component1); // It is recommended that the context should be memorized to improve performance

Store.ts and WithProvider. TSX can be configured as vscode snippets and can be used when needed.

reference

[1] React + TypeScript practices [2] React Hooks best practices

Feel free to discuss in the comments or issue, point out what doesn’t work, and add other best practices