❗️ Preparation:

  • Familiar with the React
  • Get familiar with TypeScript (2ality’s Guide, Chibicode’s Tutorial for beginners)

  • Read the React official document TS

  • Read up on TypeScript Playground React

This document references the latest TypeScript version

How to introduce React

import * as React from 'react'

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

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

Here’s 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 a functional component is declared

Several ways to declare

The react.functionComponent, short form: react.fc:



// Great

type AppProps = {

  message: string

}



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

  <div>

    {message}

    {children}

  </div>

)
Copy the code

The difference between declaring function components using Reacte. FC and normal declarations and PropsWithChildren is:

  • React.fc defines the return type explicitly; the other methods are implicitly derived
  • React.FC provides type checking and autocompletion for static properties: displayName, propTypes, and defaultProps

  • The React. FC provides children with implicit type (ReactElement | null), but for now, to provide the type of some issue (problem)

For example, the following use of React.FC will report 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

Solution:



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



/ / or



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 general, use the React.FC way declaration is the simplest and effective, recommended use; If a type incompatibility problem occurs, use either of the following methods:

Second: use PropsWithChildren. This will save you the need to frequently define the type of children and automatically set the type of children 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<T>

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



// 'val' is derived as a Boolean type, and toggle takes a Boolean type parameter

const [val, toggle] = React.useState(false)



// obj is automatically derived as 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 derivation 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 be explicitly declared:



type User = {

  name: string

  age: number

}



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

useRef<T>

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:

  • In the first mode ref1.current is read-only and can be passed to the built-in ref property, binding DOM elements.
  • The second way ref2.current is mutable (similar to declaring a class member variable)


const ref = React.useRef(0)



React.useEffect(() = > {

  ref.current += 1

}, [])
Copy the code

When using either of these methods, you need to check the type:



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

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



// Bad

function MyComponent() {

  const ref1 = React.useRef<HTMLDivElement>(null!). React.useEffect(() = > {

    Ref1.current. Focus does not need to be checked. Ref1.current

    doSomethingWith(ref1.current.focus())

  })

  return <div ref={ref1}> etc </div>

}
Copy the code

useEffect

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



function App() {

  // Use undefined as the return value of the callback function

  React.useEffect(() = > {

    // do something...

  }, [])



  // The return value is a function

  React.useEffect(() = > {

    // do something...

    return () = >[])} {}},Copy the code

useMemo<T> / useCallback<T>

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

The useCallback parameter must be specified, otherwise ts will not report an error. The default value is any



const value = 10

// Automatic inference returns number

const result = React.useMemo(() = > value * 2, [value])



// Automatic inference (value: number) => number

const multiply = React.useCallback((value: number) = > value * multiplier, [

  multiplier,

])
Copy the code

Passing in generics is also supported. The generics of useMemo specify the return value type, and the generics of useCallback specify the parameter type



// You can also explicitly specify the return value type. If the return value is inconsistent, an error is reported

const result = React.useMemo<string>(() = > 2[]),// The parameter of type () => number cannot be assigned to the parameter of type () => string.



const handleChange = React.useCallback<

  React.ChangeEventHandler<HTMLInputElement>

>(evt= > {

  console.log(evt.target.value)

}, [])
Copy the code

Custom Hooks

Note that if the return value of a custom Hook is an array type, TS is automatically derived to the Union type. What we really need is the specific type of each item in the array. We need to manually add the const assertion to handle this:



function useLoading() {

  const [isLoading, setState] = React.useState(false)

  const load = (aPromise: Promise<any>) = > {

    setState(true)

    return aPromise.then(() = > setState(false))}// Actual need: [Boolean, typeof load] Type

  / / instead of automatic deduction: (Boolean | typeof load) []

  return [isLoading, load] as const

}
Copy the code

If you run into 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 there are a lot of custom hooks to work with, here’s a handy utility method to handle tuple returns:



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

The use of defaultProps is not recommended in most articles. For more information, see the link **

Recommended method: Use the default parameter values instead of the default attributes:

type GreetProps = { age? : number }const Greet = ({ age = 21 }: GreetProps) = > {

  / *... * /

}
Copy the code

DefaultProps type

TypeScript3.0+ provides a significant improvement in the type derivation of default properties, although there are still some boundary cases that are not recommended. If you need to use TypeScript3.0+, see the following:



type 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

Types or Interfaces

Interface and Type use scenarios are very similar in everyday React development

Implements and extends static operations do not allow implementations of one or the other, so the use of union types is 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 {

  // A class can only implement object types or intersections of object types that have statically known members.

  x = 2

  y = 3

  area() {

    return this.x + this.y

  }

}



interface ShapeOrPerimeter extends RectangleShape {}

// Interfaces can only extend object types or intersections of object types that use statically known members
Copy the code

Type or Interface?

There are several common rules:

  • Using interfaces when defining public apis (such as editing a library) makes it easier for users to inherit interfaces
  • 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, but in most cases used in React, interface and type can achieve the same functional effect. The biggest difference between type and interface is:

  • The type cannot be edited twice, while the interface can be extended at any time


interface Animal {

  name: string

}



// We can continue to add a new property: color

interface Animal {

  color: string

}



/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /



type Animal = {

  name: string

}



// type The type does not support attribute extension

// Error: Duplicate identifier 'Animal'

type Animal = {

  color: string

}
Copy the code

Get the Type not exported

In some cases, when we introduce a third-party library, we find that the component we want to use does not export the required component parameter type or return value type. In this case, we can use ComponentProps/ ReturnType to obtain the desired type.



// Get the parameter type

import { Button } from 'library' // However, 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

function foo() {

  return { baz: 1 }

}



type FooReturn = ReturnType<typeof foo> // { baz: number }
Copy the code

Props

Normally, we use type to define Props, and we want to add clear comments during daily development to improve maintainability and code readability.

Now we have this type



type OtherProps = {

  name: string

  color: string

}
Copy the code

In use, the corresponding type of hover is shown below



// type OtherProps = {

// name: string;

// color: string;

// }

const OtherHeading: React.FC<OtherProps> = ({ name, color }) = > (

  <h1>My Website Heading</h1>

)
Copy the code

Add more detailed comments to make it clearer to use. Note that the comments need to use /**/, // 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

Common Props TS type

Basic 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'

  /** Any object whose properties need to be used (not recommended, but useful as a placeholder) */

  obj: object

  /** acts almost the same as' object '. */

  obj2: {}

  /** List all the properties of an object (recommended) */

  obj3: {

    id: string

    title: string

  }

  /** array of objects! (common) */

  objArr: {

    id: string

    title: string

  }[]

  /** A dictionary with any number of attributes of the same type */

  dict1: {

    [key: string]: MyTypeHere

  }

  /** is exactly the same as dict1 */

  dict2: Record<string, MyTypeHere>

  /** Any function that is not called at all */

  onSomething: Function

  /** Function with no arguments & return value */

  onClick: () = > void

  /** Functions that take arguments */

  onChange: (id: number) = > void

  /** The function that carries the click event */

  onClick(event: React.MouseEvent<HTMLButtonElement>): void

  /** Optional properties */optional? : OptionalType }Copy the code

The React attribute type is commonly used



export declare interface AppBetterProps {

  children: React.ReactNode // In general, it is recommended to use Great. All types of Great are supported

  functionChildren: (name: string) = >React.ReactNode style? : React.CSSProperties// Pass the style objectonChange? : React.FormEventHandler<HTMLInputElement> }export declare interface AppProps {

  children1: JSX.Element // Array is not supported

  children2: JSX.Element | JSX.Element[] // Generally, strings are not supported

  children3: React.ReactChildren // Ignore the naming, which is not an appropriate type, of the utility class type

  children4: React.ReactChild[] / / is very good

  children: React.ReactNode // Best, support all types recommended use

  functionChildren: (name: string) = > React.ReactNode // recommended function as a child render prop typestyle? : React.CSSProperties// Pass the style objectonChange? : React.FormEventHandler<HTMLInputElement>// The form event. The generic parameter is the type of event.target

}
Copy the code

Forms and Events

onChange

Change event, there are two methods that define parameter types.

The first method uses the inferred method signature (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 delegate type provided by @types/react. Either method works.



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 much about the type of event, you can use React.syntheticEvent directly, or use the 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, often used for type determination

  • Typeof and instanceof: Used for type differentiation
  • Keyof: Obtains the keyof an object

  • O[K]: Attribute lookup

  • [K in O]: indicates the mapping type

  • + or – or readonly or ? : Addition, subtraction, read-only, and optional modifiers

  • x ? Y: Z: Conditional types for generic types, type aliases, function parameter types

  • ! : Null assertion of the nullable type

  • As: Type assertion

  • Is: Type protection for function return types

Tips

Access component property types using lookup types

Reduce unnecessary exports of type by looking for types, and if you need to provide complex types, you should extract them into a file that is exported as a public API.

Now that we have a Counter component, we need the mandatory parameter 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 in 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 App
Copy 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 App
Copy the code

Do not use function declarations in type or interface

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

StrictFunctionTypes enforces stricter type checking when comparing function types, but strictFunctionTypes do not take effect under the first declaration.

✅

interface ICounter {

  start: (value: number) = >String} ❌ interface ICounter1 {start(value: number): string} 🌰 interface Animal {} interface Dogextends 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 // Ok
Copy 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 and clientY to get the coordinates of the pointer.

You might think of setting Event to any, but that would miss 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 clientY property value from the event object in the event handler. In this case, we’ve set event to any, causing TypeScript to compile without informing us of the error. We had a problem when we accessed through event.clienty, because the Event object of the Touch event does not have the clientY property.

It would be a waste of time to write a type declaration for an Event object via interface. Fortunately, the React declaration file provides a type declaration for an event object.

Event Indicates the type of the Event object

  • ClipboardEvent

    ClipboardEvent object
  • DragEvent<T =Element> Drag and drop the event object

  • ChangeEvent<T = Element> Change event object

  • KeyboardEvent<T = Element> KeyboardEvent object

  • MouseEvent<T = Element> MouseEvent object

  • TouchEvent<T = Element> Touches the event object

  • WheelEvent<T = Element> Scroll the wheel time object

  • AnimationEvent<T = Element> AnimationEvent object

  • TransitionEvent<T = Element> TransitionEvent object

Event handler type

Is there a more convenient way to define the function type when we define event handlers? The answer is to use the EventHandler type aliases provided by the React declaration file to define the type of the EventHandler 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

BivarianceHack is the type definition for the event handler function that receives an event object of the type of the received generic variable E and returns void

As to why bivarianceHack is used instead of (event: E): void, this has to do with functionality compatibility under strictfunctionTypes option. (event: E): void. If the argument is of a derived type, you cannot pass it 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

The async function is often used when doing asynchronous operations. When the function is called, it returns a Promise object. You can use the THEN method to add a callback function. Promise

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



type IResponse<T> = {

  message: string

  result: T

  success: boolean

}



async function getResponse() :Promise<IResponse<number[] > >{

  return {

    message: 'Achieve success'.result: [1.2.3].success: true,

  }

}



getResponse().then(response= > {

  console.log(response.result)

})
Copy the code

First declare a generic interface for IResponse to define the type of response, and use the generic T variable to determine the type of result. We then declare an asynchronous function getResponse and define the return value of type Promise

>. The last call to getResponse returns a promise, which is called by then. At this point, the first callback received by then is of type {message: string, result: Number [], success: Boolean}.

Component of a generic parameter

The name attribute of the following component specifies the format of the passed argument. If you want to deduce the actual type from the passed argument type instead of specifying it, you 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 an external parameter type, just ->



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:

  • For many types, use 🌰

When we need an ID function, the parameter of the function can be any value, the return value is to return the parameter as it is, and it can only accept one parameter. In the js era, we can easily throw out a line

const id = arg= > arg
Copy the code

Since it can take any value, it means that the input and return values of our function should be of any type. If we don’t use generics, we have to define them repeatedly



type idBoolean = (arg: boolean) = > boolean

type idNumber = (arg: number) = > number

type idString = (arg: string) = > string

// ...
Copy the code

If we use generics, we just have to



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

  return arg

}



/ / or



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

  return arg

}
Copy the code
  • It needs to be used in a variety of ways, such as generic Partial, a common tool.

The function is to make the properties 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 generically recursively

type DeepPartial<T> = T extends Function

  ? T

  : T extends object

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

Bytedance understand car emperor team recruitment

We are a product line of Bytedance. At present, our business is in a rapid development stage. Since its official birth in August 2017, We have been the second in the automotive Internet industry in just three years.

Now the mainstream technology stack of the front-end team is React and Typescript, mainly responsible for understanding Cardie App, M station, PC station, cardie small program product matrix, massive commercial business, commercial data products, etc. We have a lot of technical practices in a variety of application scenarios, such as client-like, multi-host, technical site construction, middle and background system, full stack, rich interaction, etc. We are committed to technology-driven business development and explore all possibilities.

Join the car emperor and build the most professional and open front end team in the automotive field!

Resume direct: [email protected]

Email subject: Apply for + city + job name