How to introduce React

import * as React from 'react'

import * as ReactDOM from 'react-dom'
Copy the code

This method of reference has proved to be the most reliable and is recommended.

Another way to quote:

import React from 'react'

import ReactDOM from 'react-dom'
Copy the code

You need to add additional configuration: “allowSyntheticDefaultImports” : true

How functional components are declared

Several ways to declare

React.FunctionComponent react. FC:

// Great
type AppProps = {
  message: string
}

const App: React.FC<AppProps> = ({ message, children }) => (
  <div>
    {message}
    {children}
  </div>
)

Copy the code

The difference between using a React.FC declaration function component and a normal declaration and PropsWithChildren is:

  • React.FCReturn types are explicitly defined; otherwise, they are derived implicitly
  • React.FCFor static attributes:displayName,propTypes,defaultPropsProvides type checking and automatic completion
  • React.FCchildrenProvides implicit types(ReactElement | null), but currently, there are some issues in the types provided.

For example, when you use React.FC, you get a type error:

const App: React.FC = props => props.children
const App: React.FC = () => [1, 2, 3]
const App: React.FC = () => 'hello'
Copy the code

Solutions:

const App: React.FC<{}> = props => props.children as any
const App: React.FC<{}> = () => [1, 2, 3] as any
const App: React.FC<{}> = () => 'hello' as any
// 或者
const App: React.FC<{}> = props => (props.children as unknown) as JSX.Element
const App: React.FC<{}> = () => ([1, 2, 3] as unknown) as JSX.Element
const App: React.FC<{}> = () => ('hello' as unknown) as JSX.Element
Copy the code

In most cases, the React.FC declaration mode is the simplest and most effective. If type incompatibility occurs, you are advised to use the following two methods:

Second: use PropsWithChildren, which saves you from frequently defining the children type and automatically sets the children type to ReactNode:

type AppProps = React.PropsWithChildren<{ message: string }>
const App = ({ message, children }: AppProps) => (
  <div>
    {message}
    {children}
  </div>
)
Copy the code

Third: Direct statement:

type AppProps = { message: string children? : React.ReactNode } const App = ({ message, children }: AppProps) => ( <div> {message} {children} </div> )Copy the code

Hooks

useState

In most cases, TS will automatically deduce the type of state for you:

Const [val, toggle] = react. useState(false) // obj is automatically derived to type: {name: String} const [obj] = react. useState({name: 'sj'}) // arr is automatically derived to type: string[] const [arr] = React.useState(['One', 'Two'])Copy the code

Using derived types as interfaces/types:

Export default function App() {// user is automatically derived as type: {name: string} const [user] = react. useState({name: 'sj', age: 32 }) const showUser = React.useCallback((obj: Typeof User) => {return 'My name is ${obj.name}, My age is ${obj.age}'}, []) return <div className="App"> user: {showUser(user)}</div> }Copy the code

However, some states with an initial value of null need to explicitly declare the type:

type User = {
  name: string
  age: number
}
const [user, setUser] = React.useState<User | null>(null)
Copy the code

useRef

When the initial value is null, there are two ways to create it:

const ref1 = React.useRef<HTMLInputElement>(null)
const ref2 = React.useRef<HTMLInputElement | null>(null)
Copy the code

The difference between the two is:

  • The first wayref1.currentIs read-only(read - only)And can be passed to the built-inrefProperty, bound to DOM elements;
  • The second wayref2.currentIs mutable (similar to a member variable of a declared class)
const ref = React.useRef(0)
React.useEffect(() => {
  ref.current += 1
}, [])
Copy the code

When using both methods, you need to check the type:

const onButtonClick = () => { ref1.current? .focus() ref2.current? .focus() }Copy the code

In some cases, you can omit type checking by adding! Assert, not recommended:

// Bad function MyComponent() { const ref1 = React.useRef<HTMLDivElement>(null!) React.useEffect(() => {react. useEffect() => { Ref1.current. Focus doSomethingWith(ref1.current. Focus ())}) return <div ref={ref1}> etc </div>}Copy the code

useEffect

UseEffect Note that the return value of the callback function can only be function or undefined

Function App() {react. useEffect() => {// do something... }, []). / / the return value is a function the React useEffect (() = > {/ / do something... return () => {} }, []) }Copy the code

useMemo / useCallback

Both useMemo and useCallback can infer their types directly from the values they return

The useCallback argument must be typed. Otherwise, ts does not report an error

// react. useMemo(() => value * 2, [value]) // react. useMemo(() => value * 2, [value])) number) => number const multiply = React.useCallback((value: number) => value * multiplier, [ multiplier, ])Copy the code

Passing generics is also supported. UseMemo generics specify return value types and useCallback generics specify parameter types

// You can also explicitly specify the return value type, Const result = react. useMemo<string>(() => 2, []) // Parameters of type () => number cannot be assigned to parameters of type () => string. const handleChange = React.useCallback< React.ChangeEventHandler<HTMLInputElement> >(evt => { console.log(evt.target.value) }, [])Copy the code

Custom Hooks

We need to manually add a const assertion for each item in the array. We need to add a const assertion for each item in the array.

function useLoading() { const [isLoading, setState] = React.useState(false) const load = (aPromise: Promise<any>) => {setState(true) return apromise.then (() => setState(false))} [Boolean, typeof load] type / / not automatically derived: (Boolean | typeof load) [] return as const} [isLoading, load]Copy the code

If you have problems with const assertions, you can also 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

If you have a large number of custom hooks to deal with, here’s a handy utility method to handle the tuple return value:

function tuplify<T extends any[]>(... elements: T) { return elements } function useLoading() { const [isLoading, setState] = React.useState(false) const load = (aPromise: Promise<any>) => { setState(true) return aPromise.then(() => setState(false)) } // (boolean | typeof load)[] return [isLoading, load] } function useTupleLoading() { const [isLoading, setState] = React.useState(false) const load = (aPromise: Promise<any>) => { setState(true) return aPromise.then(() => setState(false)) } // [boolean, typeof load] return tuplify(isLoading, load) }Copy the code

The default property defaultProps

Most articles do not recommend using defaultProps

Recommended method: Use default parameter values instead of default properties:

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

DefaultProps type

TypeScript3.0+ is a great improvement in the type inference of default properties. Although there are still some boundary cases, it is not recommended to use TypeScript3.0+.

type IProps = { name: string } const defaultProps = { age: GreetProps = IProps & typeof defaultProps const Greet = (props: GreetProps) => <div></div> GreetProps. DefaultProps = (props: React.ComponentProps<typeof Greet>) => { return <h1 /> } const el = <TestComponent name="foo" />Copy the code

Types or Interfaces

In daily react development, interface and Type are used in similar scenarios

Implements and extends are static operations that do not allow one implementation or the other, so joint types are not supported:

class Point { x: number = 2 y: number = 3 } interface IShape { area(): number } type Perimeter = { perimeter(): number } type RectangleShape = (IShape | Perimeter) & Point class Rectangle implements RectangleShape { // Class can only implement object types with statically known members or an intersection of object types. x = 2 y = 3 area() { return this.x + this.y } } interface ShapeOrPerimeter extends RectangleShape {} // Interfaces can only extend object types or the intersection of object types that use statically known membersCopy the code

useTypeorInterface?

There are several common rules:

  • 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 State (State), it is recommended to use type because type is more restrictive. Interface and type are two different concepts in TS. However, in most React cases, Interface and type can achieve the same function. The main difference between type and interface is that type cannot be edited twice, while interface can be extended at any time
Interface Animal {name: string} // Add a new attribute: color interface Animal {color: String} / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / type Animal = {name: string} / / type type does not support attribute extensions / / Error: Duplicate identifier 'Animal' type Animal = { color: string }Copy the code

Gets an unexported Type

In some scenarios, when importing a third-party library for a component that does not export the component parameter type or return value type, you can use ComponentProps/ ReturnType to obtain the required component type.

Import {Button} from 'library' import {Button} from 'library' import {Button} props type type ButtonProps = React.ComponentProps<typeof Button> // Omit operation type < <ButtonProps, 'onClick'> React.FC<AlertButtonProps> = props => ( <Button onClick={() => alert('hello')} {... props} /> )Copy the code
Function foo() {return {baz: 1}} type FooReturn = ReturnType<typeof foo>Copy the code

Props

Usually we use Type to define Props. In order to improve maintainability and code readability, we want to add clear comments during daily development.

Now there is such a type

type OtherProps = {
  name: string
  color: string
}
Copy the code

In the process of use, the hover corresponding type will be shown as follows

// type OtherProps = {
//   name: string;
//   color: string;
// }
const OtherHeading: React.FC<OtherProps> = ({ name, color }) => (
  <h1>My Website Heading</h1>
)
Copy the code

Add relatively detailed comments to make it clearer to use. Note that comments need to use /**/, and // cannot be recognized by vscode

// Great
/**
 * @param color color
 * @param children children
 * @param onClick onClick
 */

type Props = {
  /** color */
  color?: string
  /** children */
  children: React.ReactNode
  /** onClick */
  onClick: () => void
}

// type Props
// @param color — color
// @param children — children
// @param onClick — onClick
const Button: React.FC<Props> = ({ children, color = 'tomato', onClick }) => {
  return (
    <button style={{ backgroundColor: color }} onClick={onClick}>
      {children}
    </button>
  )
}
Copy the code

The common type is Props TS

Base attribute type

type AppProps = { message: string count: number disabled: boolean /** array of a type! */ names: string[] /** string literals to specify exact string values, with a union type to join them together */ status: 'waiting' | 'success' / * * at any need to use the attributes of the object (not recommended, but is useful as a placeholder) * / obj: object function and ` / * * object ` nearly the same, and ` object ` exactly * / obj2: */ obj3: {id: string title: string} /** array of objects! (common) */ objArr: {id: string title: string}[] /** Arbitrary number of attributes of the dictionary, with the same type */ dict1: {[key: string]: Dict2: Record<string, MyTypeHere> /** arbitrary completely not called function */ onSomething: Function /** Function with no arguments */ onClick: () => void /** Function with arguments */ onChange: OnClick (event: react. MouseEvent<HTMLButtonElement>): void /** Optional? : OptionalType }Copy the code

The React property type is commonly used

Export Declare Interface AppBetterProps {children: react. ReactNode // Great functionChildren: (name: string) => React.ReactNode style? : react. CSSProperties // Pass the style object onChange? : React.FormEventHandler<HTMLInputElement> } export declare interface AppProps { children1: JSX Element / / poor, does not support array children2: JSX. Element | JSX. Element [] / / general, does not support the string children3: ReactChild: children4: react.reactChild [] // children4: react.reactChild React.reactnode // best, support all types recommended using functionChildren: (name: string) => React.ReactNode // recommended function as a child render prop type style? : react. CSSProperties // Pass the style object onChange? : React.FormEventHandler<HTMLInputElement> // Form event, generic parameter is type of event. Target}Copy the code

Forms and Events

onChange

The change event, which has two methods for defining parameter types.

The first method uses inferred method signatures (e.g. React.FormEvent

: void)

import * 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)
  }
  return (
    <div>
      <input type="text" value={state} onChange={onChange} />
    </div>
  )
}
Copy the code

The second method enforces the use of the delegate type provided by @types/React, either way.

import * as React from 'react'
const App: React.FC = () => {
  const [state, setState] = React.useState('')
  const onChange: React.ChangeEventHandler<HTMLInputElement> = e => {
    setState(e.currentTarget.value)
  }
  return (
    <div>
      <input type="text" value={state} onChange={onChange} />
    </div>
  )
}
Copy the code

onSubmit

If you don’t care too much about the type of event, you can just use react.SyntheticEvent, or use type extension if the target form has custom named input that you want to access

import * as React from 'react' 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

Operators

A common operator used for type determination

  • Typeof and instanceof: Used for type discrimination

  • Keyof: obtains the keyof an object

  • O[K]: Attribute search

    • or – or readonly or ? : addition, subtraction, read-only, and optional modifiers
  • x ? Y: Z: Conditional types for generic types, type aliases, and function parameter types

  • ! : empty assertion of a nullable type

  • As: Type assertion

  • Is: Type protection for function return types

Tips

Use lookup types to access component property types

Reduce unnecessary exports of Type by looking up types, and if you need to provide complex types, extract them into files exported as public apis.

Now we have a Counter component that takes the required name:

// counter.tsx
import * as React from 'react'
export type Props = {
  name: string
}
const Counter: React.FC<Props> = props => {
  return <></>
}
export default Counter
Copy the code

There are two ways to get the parameter type of Counter from other components that reference it

The first is through the Typeof operator (recommended)

// 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 AppCopy the code

The second is by exporting in the original component

import Counter, { Props } from './d-tips1' type PropsNew = Props & { age: number } const App: React.FC<PropsNew> = props => { return ( <> <Counter {... props} /> </> ) } export default AppCopy the code

Do not use function declarations in type or interface

For consistency, all members of the type/interface are defined by the same syntax.

StrictFunctionTypes Enforce stricter type checking when comparing function types, but strict checking does not take effect under the first declaration.

✅ interface ICounter {start: (value: number) => string} ❌ interface ICounter1 {start(value: number): String} 🌰 interface Dog extends Animal {wow: () => void} interface Comparer<T> {compare: (a: T, b: T) => number } declare let animalComparer: Comparer<Animal> declare let dogComparer: Comparer<Dog> animalComparer = dogComparer // Error dogComparer = animalComparer // Ok interface Comparer1<T> { compare(a: T, b: T): number } declare let animalComparer1: Comparer1<Animal> declare let dogComparer1: Comparer1<Dog> animalComparer1 = dogComparer // Ok dogComparer1 = animalComparer // OkCopy the code

The event processing

We often use event event objects in event handlers when registering events. For example, when using mouse events, we use clientX, clientY to get pointer coordinates.

It might be tempting to set event to type ANY, but that would lose the point of statically checking our code.

Function handleEvent(event: any) {console.log(event.clienty)}Copy the code

Imagine registering a Touch event and mistakenly getting the value of its clientY property from the event object in the event handler. In this case, we’ve set the event to any, so TypeScript doesn’t tell us when we compile. There is a problem when we access it via event.clientY, because the event object for the Touch event does not have clientY.

Writing a type declaration for an event object through an interface is a waste of time. Fortunately, the React declaration file provides a type declaration for the event object.

Event Indicates the type of the Event object

  • ClipboardEvent<T = Element> ClipboardEvent object

  • DragEvent<T =Element> Drags the event object

  • ChangeEvent<T = Element> ChangeEvent object

  • KeyboardEvent<T = Element> KeyboardEvent object

  • MouseEvent<T = Element> MouseEvent object

  • TouchEvent<T = Element> Touches the event object

  • WheelEvent<T = Element> Wheel time object

  • AnimationEvent<T = Element> AnimationEvent object

  • TransitionEvent<T = Element> TransitionEvent object

Event handler type

When we define event handlers, is there a more convenient way to define their function types? The answer is to use the EventHandler type aliases provided with the React declaration file to define the type of event handlers by using the EventHandler type aliases for different events

type EventHandler<E extends React.SyntheticEvent<any>> = {
  bivarianceHack(event: E): void
}['bivarianceHack']
type ReactEventHandler<T = Element> = EventHandler<React.SyntheticEvent<T>>
type ClipboardEventHandler<T = Element> = EventHandler<React.ClipboardEvent<T>>
type DragEventHandler<T = Element> = EventHandler<React.DragEvent<T>>
type FocusEventHandler<T = Element> = EventHandler<React.FocusEvent<T>>
type FormEventHandler<T = Element> = EventHandler<React.FormEvent<T>>
type ChangeEventHandler<T = Element> = EventHandler<React.ChangeEvent<T>>
type KeyboardEventHandler<T = Element> = EventHandler<React.KeyboardEvent<T>>
type MouseEventHandler<T = Element> = EventHandler<React.MouseEvent<T>>
type TouchEventHandler<T = Element> = EventHandler<React.TouchEvent<T>>
type PointerEventHandler<T = Element> = EventHandler<React.PointerEvent<T>>
type UIEventHandler<T = Element> = EventHandler<React.UIEvent<T>>
type WheelEventHandler<T = Element> = EventHandler<React.WheelEvent<T>>
type AnimationEventHandler<T = Element> = EventHandler<React.AnimationEvent<T>>
type TransitionEventHandler<T = Element> = EventHandler<
  React.TransitionEvent<T>
>
Copy the code

A bivarianceHack is a type definition for an event handler that receives an event object of the type of the received generic variable E and returns void

The reason why bivarianceHack is used instead of (Event: E): void has to do with functional compatibility under strictfunctionTypes. (event: E): void, if the argument is a derived type, it cannot be passed to a function whose argument is a base class.

class Animal {
  private x: undefined
}
class Dog extends Animal {
  private d: undefined
}
type EventHandler<E extends Animal> = (event: E) => void
let z: EventHandler<Animal> = (o: Dog) => {} // fails under strictFunctionTyes
type BivariantEventHandler<E extends Animal> = {
  bivarianceHack(event: E): void
}['bivarianceHack']
let y: BivariantEventHandler<Animal> = (o: Dog) => {}
Copy the code

Promise type

We often use async functions for asynchronous operations, which return a Promise object when called. We can add callbacks using the then method. Promise

is a generic type, and the T generic variable is used to determine the parameter type of the first callback received when the THEN method is used.

type IResponse<T> = { message: string result: T success: boolean } async function getResponse(): Promise<IResponse<number[]>> {return {message: 'get success ', result: [1, 2, 3], success: true, } } getResponse().then(response => { console.log(response.result) })Copy the code

First declare the generic interface of IResponse to define the type of Response, and determine the type of result through the T generic variable. We then declare an asynchronous function getResponse and define the type of the return value of the function as Promise

>. Finally, calling the getResponse method returns a promise, called through then. The then method receives the first callback function response of type {message: string, result: Number [], success: Boolean}.

Components of generic parameters

The following component’s name attribute specifies the parameter format. If you want to derive the actual type from the type of the passed parameter instead of specifying it, use generics.

const TestB = ({ name, name2 }: { name: string; name2? : string }) => { return ( <div className="test-b"> TestB--{name} {name2} </div> ) }Copy the code

If you need to pass in an external parameter type, simply ->

type Props<T> = { name: T name2? : T } const TestC: <T>(props: Props<T>) => React.ReactElement = ({ name, name2 }) => { return ( <div className="test-b"> TestB--{name} {name2} </div> ) } const TestD = () => { return ( <div> <TestC<string> name="123" /> </div> ) }Copy the code

When to use generics

When your function, interface, or class:

  • When you need to apply to many types, for example 🌰

When we need an ID function, the function argument can be any value, the return value is to return the argument as is, and it can only accept one argument, in the JS era we can easily throw out a line

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} const id1: <T>(arg: T) => T = arg => {return arg}Copy the code
  • Need to be used in many places, such as the common utility genericsPartial.

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

type Partial<T> = { [P in keyof 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 in keyof T]? : DeepPartial<T[P]> } : T type PartialedWindow = DeepPartial<Window>Copy the code