编程知识 cdmana.com

Byte react + typescript practice summary

️ Prepare knowledge

  • be familiar with React
  • be familiar with TypeScript ( Reference books :2ality's guide[1], Beginners suggest reading :chibicode's tutorial[2]

  • Familiar with React Official documents  TS part [3]

  • Familiar with TypeScript playground React part [4]

Reference to this document TypeScript The latest version

How to introduce React

import * as React from 'react'

import * as ReactDOM from 'react-dom'

This way of quoting It turns out that [5] It's the most reliable way ,  Recommended .

And another way of quoting :

import React from 'react'

import ReactDOM from 'react-dom'

Additional configuration needs to be added :"allowSyntheticDefaultImports": true

How functional components are declared

Several ways to make a statement

The first one is : Also be more recommend A kind of , Use  React.FunctionComponent, Abbreviation form :React.FC:

// Great
type AppProps = {
  message: string
}

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

)

Use React.FC Declare function components and General statement as well as  PropsWithChildren  Is the difference between the :

  • React.FC Explicitly defines the return type , Other ways are implicit
  • React.FC For static properties :displayName、propTypes、defaultProps Provides type checking and automatic completion

  • React.FC by children Provides an implicit type (ReactElement | null), But at the moment , The type provided exists some issue[6]( problem )

For example, the following usage React.FC Type error will be reported :

const App: React.FC = props => props.children
const App: React.FC = () => [123]
const App: React.FC = () => 'hello'

resolvent :

const App: React.FC<{}> = props => props.children as any
const App: React.FC<{}> = () => [123as any
const App: React.FC<{}> = () => 'hello' as any
//  perhaps
const App: React.FC<{}> = props => (props.children as unknown) as JSX.Element
const App: React.FC<{}> = () => ([123as unknown) as JSX.Element
const App: React.FC<{}> = () => ('hello' as unknown) as JSX.Element

In general , Use  React.FC  The most simple and effective way to declare , Recommended ; If there is a type incompatibility problem , It is recommended to use There are two ways :

The second kind : Use  PropsWithChildren, This way, you don't need to define frequently children The type of , Automatic setting children The type is ReactNode:

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

)

The third kind of : Make a direct statement :

type AppProps = {
  message: string
  children?: React.ReactNode
}

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

)

Hooks

useState<T>

In most cases ,TS Will automatically deduce for you  state  The type of :

// `val` It can be deduced as boolean type , toggle receive boolean Type parameter 
const [val, toggle] = React.useState(false)
// obj Automatically deduces to type : {name: string}
const [obj] = React.useState({ name'sj' })
// arr Automatically deduces to type : string[]
const [arr] = React.useState(['One''Two'])

Use derived types as interfaces / type :

export default function App({
  // user Automatically deduces to type : {name: string}
  const [user] = React.useState({ name'sj'age32 })
  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>
}

however , When the initial value of some states is null (null), You need to explicitly declare the type :

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

useRef<T>

When the initial value is  null  when , There are two ways to create :

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

The difference between the two is

  • In the first way ref1.current yes read-only (read-only), And it can be passed to the built-in ref attribute , binding DOM Elements   ;
  • In the second way ref2.current yes Variable ( It's like declaring a member variable of a class )
const ref = React.useRef(0)
React.useEffect(() => {
  ref.current += 1
}, [])

When using these two methods , You need to check the type :

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

In some cases , The type check can be omitted , By adding  !  Assertion , Not recommended

// Bad
function MyComponent({
  const ref1 = React.useRef<HTMLDivElement>(null!)
  React.useEffect(() => {
    //   There's no need to do type checking , It needs to be guaranteed ref1.current.focus There must be
    doSomethingWith(ref1.current.focus())
  })
  return <div ref={ref1}> etc </div>
}

useEffect

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

function App({
  // undefined As the return value of the callback function
  React.useEffect(() => {
    // do something...
  }, [])
  //  The return value is a function
  React.useEffect(() => {
    // do something...
    return () => {}
  }, [])
}

useMemo<T> / useCallback<T>

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

useCallback  The parameters of must be typed , otherwise ts No mistake. , The default is  any

const value = 10
//  Automatically infer that the return value is  number
const result = React.useMemo(() => value * 2, [value])
//  Automatic inference  (value: number) => number
const multiply = React.useCallback((value: number) => value * multiplier, [
  multiplier,
])

It also supports passing in generics , useMemo  The generic of specifies the return value type ,useCallback  The generic of specifies the parameter type

//  You can also explicitly specify the return value type , If the return value is inconsistent, an error will be reported 
const result = React.useMemo<string>(() => 2, [])
//  type “() => number” Argument to cannot be assigned to type “() => string” Parameters of .
const handleChange = React.useCallback<
  React.ChangeEventHandler<HTMLInputElement>
>(evt => {
  console.log(evt.target.value)
}, [])

Customize Hooks

We need to pay attention to , Customize Hook If it is An array type ,TS It automatically deduces to  Union  type , What we really need is the specific type of each item in the array , Need to be manually added  const  Assertion   To deal with :

function useLoading({
  const [isLoading, setState] = React.useState(false)
  const load = (aPromise: Promise<any>) => {
    setState(true)
    return aPromise.then(() => setState(false))
  }
  //  The actual need : [boolean, typeof load]  type
  //  Instead of automatically deriving :(boolean | typeof load)[]
  return [isLoading, load] as const
}

If you use  const  I met problem [7], 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]
}

If there's a lot of customization Hook Need to deal with , Here's a convenient tool to handle 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)
}

The default attribute defaultProps

Most of the articles are Not recommended Use defaultProps ,  For discussion, click Reference link [8]

Recommend ways : Use Default parameter value Instead of The default attribute :

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

defaultProps type

TypeScript3.0+[9]  In the default properties There is a great improvement in the type derivation of , Although still There are some boundaries case There are still problems [10], It is not recommended to use , If there are scenarios that need to be used , You can refer to the following ways :

type IProps = {
  name: string
}
const defaultProps = {
  age25,
}

//  The 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" />

Types or Interfaces

In everyday react In development  interface  and  type  The use scenarios of are very similar

implements  And  extends  Static operation , One or another implementation is not allowed , therefore Union type 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 {
  //  Class can only implement object types or intersection of object types with static known members .
  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 static known members

Use Type still Interface?

There are several common rules :

  • In defining public API when ( For example, editing a library ) Use   interface, This makes it easy for users to inherit interfaces
  • In defining component properties (Props) And status (State) when , It is recommended to use   type, because   type It's more restrictive

interface  and  type  stay ts There are two different concepts in English , But in React Most of the  case  in ,interface  and  type  Can achieve the same functional effect ,type  and  interface The biggest difference yes :

  • type  Type cannot be edited twice , and   interface  Can be extended at any time
interface Animal {
  name: string
}

//  You can continue on the basis of the original attributes , Add new properties :color
interface Animal {
  color: string
}
/********************************/
type Animal = {
  name: string
}
// type Type does not support property extension
// Error: Duplicate identifier 'Animal'
type Animal = {
  color: string
}

Get not exported Type

In some scenarios, when we introduce a third-party library, we will find that the component we want to use does not export the component parameter type or return value type we need , At this point we can go through ComponentProps/ ReturnType To get the type you want .

//  Get parameter type 
import { Button } from 'library' //  But not exported props type
type ButtonProps = React.ComponentProps<typeof Button> //  obtain props
type AlertButtonProps = Omit<ButtonProps, 'onClick'//  Remove onClick
const AlertButton: React.FC<AlertButtonProps> = props => (
  <Button onClick={() => alert('hello')} {...props} />
)
//  Get return value type 
function foo({
  return { baz1 }
}
type FooReturn = ReturnType<typeof foo> // { baz: number }

Props

Usually we use  type  To define  Props, To improve maintainability and code readability , In the daily development process, we hope to add clear comments .

Now there is such a  type

type OtherProps = {
  name: string
  color: string
}

In use ,hover The corresponding types are shown as follows

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

Add relatively detailed notes , It's clearer when it's used , We need to pay attention to , Annotations need to use  /**/ , //  Can't be vscode distinguish

// 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>

  )
}

Commonly used Props ts type

Basic attribute types

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 that needs to use its properties ( It is not recommended to use , But it's useful as a space occupying agent ) */
  obj: object
  /**  Function and `object` Almost the same as , and  `Object` Exactly the same as  */
  obj2: {}
  /**  Properties that list the total number of objects  ( Recommended ) */
  obj3: {
    id: string
    title: string
  }
  /** array of objects! (common) */
  objArr: {
    id: string
    title: string
  }[]
  /**  A dictionary of any number of attributes , With the same type */
  dict1: {
    [key: string]: MyTypeHere
  }
  /**  Function and dict1 Exactly the same  */
  dict2: Record<string, MyTypeHere>
  /**  Any function that will not be called at all  */
  onSomething: Function
  /**  No parameters & The function that returns the value  */
  onClick() => void
  /**  Functions with parameters  */
  onChange(id: number) => void
  /**  Functions that carry click events  */
  onClick(event: React.MouseEvent<HTMLButtonElement>): void
  /**  Optional properties  */
  optional?: OptionalType
}

Commonly used React Attribute types

export declare interface AppBetterProps {
  children: React.ReactNode //  In general, it is recommended to use , Supports all types of  Great
  functionChildren(name: string) => React.ReactNode
  style?: React.CSSProperties //  Pass on style object
  onChange?: React.FormEventHandler<HTMLInputElement>
}

export declare interface AppProps {
  children1: JSX.Element //  Bad ,  Arrays are not supported
  children2: JSX.Element | JSX.Element[] //  commonly ,  String not supported
  children3: React.ReactChildren //  Ignore naming , Not a suitable type , Tool class types
  children4: React.ReactChild[] //  very good
  children: React.ReactNode //  The best , Supports all types of   Recommended
  functionChildren(name: string) => React.ReactNode // recommended function as a child render prop type
  style?: React.CSSProperties //  Pass on style object
  onChange?: React.FormEventHandler<HTMLInputElement> //  Form Events ,  The generic parameter is event.target The type of
}

Forms and Events

onChange

change  event , There are two ways to define parameter types .

The first method uses the inferred method signature ( for example :React.FormEvent <HTMLInputElement> :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>

  )
}

The second method is mandatory  @types / react  The type of delegation provided , Both methods are available .

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>

  )
}

onSubmit

If you don't care much about the type of event , You can use it directly  React.SyntheticEvent, If the target form has custom named input that you want to access , You can use type extensions


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>

  )
}

Operators

Common operators , It is often used in type judgment

  • typeof and instanceof: For type discrimination
  • keyof: obtain object Of key

  • O[K]: Property search

  • [K in O]: Mapping type

  • + or - or readonly or ?: Add 、 Subtraction 、 Read only and optional modifiers

  • x ? Y : Z: For generic types 、 Type the alias 、 The conditional type of the function parameter type

  • !: Null assertion of nullable type

  • as: Types of assertions

  • is: Type protection of function return type

Tips

Use lookup types to access component property types

Reduce... By finding types  type  Unnecessary derivation of , If you need to provide complex  type, Should be extracted as public API In the exported file .

Now we have a Counter Components , need name This must pass parameter :

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

Among other components that reference it, we have two ways to get Counter Parameter type

The first is through  typeof  The operator ( recommend

// 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

The second is to export from 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

Not in type or interface Use function declarations in

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

--strictFunctionTypes  Enforce more stringent type checking when comparing function types , But in the first way, strict inspection doesn't work .



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



interface ICounter1 {
  start(value: number): string
}





interface Animal {}
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 // Ok

Event handling

We often use... In event handlers when we register events  event  Event object , For example, when we use mouse events, we use  clientXclientY  To get the coordinates of the pointer .

You may think of putting  event  Set to  any  type , But then we lose the meaning of static checking the code .

function handleEvent(event: any{、
  console.log(event.clientY)
}

Imagine when we sign up for a  Touch  event , Then the error passes through the... In the event handler  event  Object to get it  clientY  The value of the property , Here we have  event  Set to  any  type , Lead to  TypeScript  We are not prompted for errors at compile time , When we go through  event.clientY  There was a problem with the visit , because  Touch  The event  event  The object is not  clientY  This attribute .

adopt  interface  Yes  event  It's a waste of time to write type declarations with objects , Fortunately,  React  The statement file of provides Event Object's type declaration .

Event Event object type

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

  • ChangeEvent<T = Element> Change Event object

  • KeyboardEvent<T = Element> Keyboard event object

  • MouseEvent<T = Element> Mouse event object

  • TouchEvent<T = Element> Touch the event object

  • WheelEvent<T = Element> Time objects

  • AnimationEvent<T = Element> Animated event object

  • TransitionEvent<T = Element> Transition event object

Event handler type

When we define event handling functions, is there a more convenient way to define their function types ? The answer is to use  React  The statement file provides  EventHandler  Type the alias , Through different events  EventHandler  To define the type of the event handler

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

bivarianceHack  Define... For the type of event handler , Function receives a  event  object , And its type is the received generic variable  E  The type of , The return value is  void

Why use bivarianceHack instead of (event: E): void, This is related to strictfunctionTypes Option is related to the compatibility of functions .(event: E): void, If the parameter is a derived type , 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) => {}

Promise type

We often use... When doing asynchronous operations  async  function , When the function is called  return  One  Promise  object , have access to  then  Method to add a callback function .Promise<T>  Is a generic type ,T  Generic variables are used to determine  then  Method, the parameter type of the first callback function received .

type IResponse<T> = {
  message: string
  result: T
  success: boolean
}
async function getResponse(): Promise<IResponse<number[]>> {
  return {
    message' To be successful ',
    result: [123],
    successtrue,
  }
}

getResponse().then(response => {
  console.log(response.result)
})

First statement  IResponse  The generic interface of is used to define  response  The type of , adopt  T  Generic variables to determine  result  The type of . And then declared a An asynchronous function  getResponse  And define the type of function return value as  Promise<IResponse<number[]>> . Last call  getResponse  Method returns a  promise  type , adopt  then  call , here  then  Method to receive the parameters of the first callback function  response  The type of ,{ message: string, result: number[], success: boolean} .

Components of generic parameters

The following component is name Attributes all specify the format of parameter transfer , If you don't want to specify , I want to deduce the actual type by passing in the type of the parameter , That's where generics are used .

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

  )
}

If you need to pass in 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>
  )
}

When to use generics

When your function , Interfaces or classes :

  • It needs to work on a lot of types , Take up a

When we need a id function , The argument to a function can be any value , The return value is to return the parameter as it is , And it can only take one parameter , stay js It's easy for us to throw out a line in this era

const id = arg => arg

Because it can accept any value , In other words, the input parameter and return value of our function should be of any type , If you don't use generics , We can only define it repeatedly

type idBoolean = (arg: boolean) => boolean
type idNumber = (arg: number) => number
type idString = (arg: string) => string
// ...

If you use generics , We just need

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

//  or
const id1: <T>(arg: T) => T = arg => {
  return arg
}
  • When it needs to be used in a lot of places , For example, common tool generics   Partial.

The function is to change the properties of the type Become optional ,  Notice that this is shallow  Partial.

type Partial<T> = { [P in keyof T]?: T[P] }

If it needs to be deep Partial We can do this through generic recursion

type DeepPartial<T> = T extends Function
  ? T
  : T extends object
  ? { [P in keyof T]?: DeepPartial<T[P]> }
  : T
type PartialedWindow = DeepPartial<Window>

Reference material

[1]

2ality's guide: http://2ality.com/2018/04/type-notation-typescript.html

[2]

chibicode's tutorial: https://ts.chibicode.com/todo/

[3]

TS part : https://reactjs.org/docs/static-type-checking.html#typescript

[4]

React part : http://www.typescriptlang.org/play/index.html?jsx=2&esModuleInterop=true&e=181#example/typescript-with-react

[5]

It turns out that : https://www.reddit.com/r/reactjs/comments/iyehol/import_react_from_react_will_go_away_in_distant/

[6]

some issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33006

[7]

problem : https://github.com/babel/babel/issues/9800

[8]

Reference link : https://twitter.com/hswolff/status/1133759319571345408

[9]

TypeScript3.0+: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html

[10]

There are some boundaries case There are still problems : https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/issues/61


- EOF -

Recommended reading    Click on the title to jump to

1、TypeScript Introduction to advanced types : Attach a large number of code examples ( Collection !)

2、 Can be in Nginx Run in JavaScript, much !

3、 Great use of programmers React “ Repeated engraving ” Implemented a Windows 11


I think this article will help you ? Please share with more people

Recommended attention 「 Front end Daquan 」, Improve front-end skills

 Front end Daquan
Front end Daquan
Click for selected front end development resources .「 Front end Daquan 」 Everyday sharing Web Front end related technical articles 、 Practical cases 、 Tool resources 、 Selected courses 、 Hot news .
201 Original content
official account

Praise and watching is the greatest support

版权声明
本文为[Front end Daquan]所创,转载请带上原文链接,感谢
https://cdmana.com/2021/10/20211002145644407f.html

Scroll to Top