Pre – work

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

React: Always use namespace import.

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

Import React from ‘React’ is not recommended. 👉 Why?

This introduction is called default export. The reason it’s not recommended is that React exists only as a namespace, and we don’t use React as a value. React 16.13.0 export {XXX,… } in the form of (commit). The reason why default export can still be used is that the React build product is compliant with Commonjs and webPack and other build tools.

Component development

1. Use Function Component declarations as often 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>)}Copy the code

I usually use vscode snippets for quick generation at development time:

"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"
  },
Copy the code

Hook associated

Install the vscode plugin: React Hooks to quickly write Hooks.

2. UseState: It is recommended to write the full generic when the initial state value is empty, otherwise the type can be inferred automatically.

Cause: Some states with null initial values need to explicitly declare the type:

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

Note: you do not need to add undefined to the generic if the initial value is undefined.

3. UseMemo () and useCallback() implicitly infer types and recommend not passing generics

Note: Do not use useCallback too often, as it also adds overhead. Only if:

  • Packing inReact.memo()The component in (shouldComponentUpdate) accepts a callback prop;
  • When functions are used as dependencies for other hooksuseEffect(... , the callback).

4. Add a const assertion manually to a custom hook if it returns an array:

function useLoading() {
  const [isLoading, setLoading] = React.useState(false);
  const load = (aPromise: Promise<any>) = > {
    setLoading(true)
    return aPromise.then(() = > setLoading(false));
  }
  // Actual: [Boolean, typeof load] type
  / / instead of automatic deduction: (Boolean | typeof load) []
  return [isLoading, load] as const;
}
Copy the code

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]
}
Copy the code

other

5. Use default parameter values instead of default properties

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

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

interface IProps {
  name: string
}
const defaultProps = {
  age: 25};// Type definition
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" />
Copy the code

6. You are advised to use Interface to define component props (TS), and type is optional

The difference between type and interface: The type type cannot be edited twice, while the interface can be extended at any time.

7. Use ComponentProps to obtain the component parameter types that are not exported, and use ReturnType to obtain the returned value types

Get component parameter types:

// Get the parameter type
import { Button } from 'library' // But props type is not exported
type ButtonProps = React.ComponentProps<typeof Button> / / get props
type AlertButtonProps = Omit<ButtonProps, 'onClick'> / / remove the onClick
const AlertButton: React.FC<AlertButtonProps> = props= > (
  <Button onClick={()= >alert('hello')} {... props} />
)
Copy the code

Get the return value type:

// Get the return value type
function foo() {
  return { baz: 1}}type FooReturn = ReturnType<typeof foo> // { baz: number }
Copy the code

8. Use /** */ when commenting on type or interface to get a better indication of the type

/ * ✅ * /
/ * * *@param A note 1 *@param B Note 2 */
type Test = {
  a: string;
  b: number;
};
const testObj: Test = {
  a: '123'.b: 234};Copy the code

Type hints are friendlier when hover to Test:

9. Type specification of component Props TS

type AppProps = {
  /** string */
  message: string;
  /** number */
  count: number;
  /** boolean */
  disabled: boolean;
  /** Array of primitive types */
  names: string[];
  /** String literal */
  status: 'waiting' | 'success';
  /** object: Lists all attributes of the object */
  obj3: {
    id: string;
    title: string;
  };
  /** item is an array of objects */
  objArr: {
    id: string;
    title: string; } [];/ * / * * dictionary
  dict: Record<string, MyTypeHere>;
  /** Any function that will not be called at all */
  onSomething: Function;
  /** Function with no arguments & return value */
  onClick: () = > void;
  /** Function that takes arguments */
  onChange: (id: number) = > void;
  /** functions that carry click events, not e: any */
  onClick(e: React.MouseEvent<HTMLButtonElement>): void;
  /** Optional attribute */optional? : OptionalType; children: React.ReactNode;// Best, support all types (jsx.element, jsx.element [], string)
  renderChildren: (name: string) = >React.ReactNode; style? : React.CSSProperties; onChange? : React.FormEventHandler<HTMLInputElement>;// Form events
};
Copy the code

10. Component event handling

Common Eventl types:

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>Copy the code

Define the event callback function:

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

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

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>
  );
};
Copy the code

11. Use optional Channing whenever possible

12. Try to use React.ComponentProps to reduce the export of unnecessary props

13. Do not use function declarations in type or interface

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

❌ / * * * /
interface ICounter1 {
  start(value: number) :string
}
Copy the code

14. When local components are combined with multiple components for inter-component state communication, mobx is not needed if it is not particularly complex, or it is recommended to use it together with useReducer() and useContext().

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.Reducer<State, Action> = (state, action) = > {
 // One of the benefits of reducer is that you can get the previous state
  switch (action) {
    case 'action1':
      return { ...state, state1: !state.state1 };
    case 'action2':
      return { ...state, state2: !state.state2 };
    default:
      returnstate; }};Copy the code

WithProvider.tsx

import * as React from 'react';
import { StoreContext, reducer, initState } from './store';

const { useReducer } = React;

const WithProvider: React.FC<Record<string, unknown>> = (props: React.PropsWithChildren<Record<string, unknown>>) = > {
  // Inject state into child components as the state of the root node
  const [state, dispatch] = useReducer(reducer, initState);
  const { children } = props;

  return <StoreContext.Provider value={{ state.dispatch}} >{children}</StoreContext.Provider>;
};

export default WithProvider;
Copy the code

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;

Copy the code

The child component simply needs to import StoreContext and useContext() to get 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 to use the memo if context is available

Copy the code

Store.ts and WithProvider.tsx can be configured to vsCode snippets and used directly when needed.

reference

[1] React + TypeScript practice

[2] Intensive Reading of React Hooks Best Practices

Feel free to discuss in the comments or issues, pointing out irrationalities and adding other best practices

This article was originally published on personal blog: moltemort. Top/post/typesc…