Note: The Demo in this chapter uses class components + TS as an example. After the hook section in the next chapter, the demo is all functional components

Antd Ant. Design/Components /…

All the code for this section is in the 👉 repository

Portal 🤖

  • React Booklet – Get Started JSX ✅ ✅

  • React Booklet – Set sail ✅ ✅

  • React booklet – Hooks

  • React Booklet – CSS solutions

  • React Booklet – Life Cycle

  • React Booklet – State Management Redux

  • React Booklet – State management Redux middleware

  • React Booklet – State Management Mobx

  • React booklet – Router

  • React Booklet – SSR

  • React Booklet – React Ecology

Initialize the project

Using webpack

npx create-react-app my-app --template typescript

# or

yarn create react-app my-app --template typescript
Copy the code

Using vite

npm init vite@latest my-vue-app -- --template react-ts

# or

yarn create vite my-vue-app --template react-ts
Copy the code

For details, see 👉 warehouse 🏠

Remove — Template typescript if you don’t need to integrate typescript yet

Parent-child communication

Parent component -> child component

Parent -> child is simpler

The parent component passes properties directly via props

Class components can use this.props.[property name] to get the corresponding property

For functional components, just use props.[property name]

Child component –> Parent component

The principle is similar to that of parent -> child

The parent component uses props to pass a function to the child component

This function is then fired from the child component using this.props.< function name >

The following is a counter accumulative 🌰

The parent component is responsible for managing the data and methods

The following is the communication between components

The parent component passes the count variable to the child component

The child component triggers the parent component accumulation method

import React, { Component } from 'react';
import { Button } from 'antd';

interfaceIProps { count? :number; onClick? :() = > void;
}

interface IState {
  count: number;
}

class ChildComponent extends Component<IProps.IState> {
  render() {
    const { count, onClick } = this.props;
    return <Button onClick={onClick}>{count}</Button>; }}class ParentComponent extends Component<IProps.IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      count: 0}; }handleClick() {
    this.setState({
      count: this.state.count + 1}); }render() {
    return (
      <ChildComponent count={this.state.count} onClick={this.handleClick} />); }}export default ParentComponent;
Copy the code

If you execute the above code you will see that the data is displayed correctly indicating that the parent component’s data has been correctly passed to the child component

But as soon as you click on the button you get an error and you get this error message

TypeError: Cannot read property ‘setState’ of undefined

So this is undefined

In the last lecture we said to be aware of the binding of this when binding events to a class component

React doesn’t bind this for us and if we didn’t bind it manually it would be undefined

Why didn’t React bind this for us? You can stamp 👉 for this article

There are two ways of doing this

  1. Manually bind this to the method at the constructor of the parent component
constructor(props: IProps) {
  super(props);
  this.state = {
    count: 0};this.handleClick = this.handleClick.bind(this);
}
Copy the code
  1. Use the arrow function
<ChildComponent count={this.state.count} onClick={() = > this.handleClick()} />
Copy the code

Life cycle function

Refer mainly to the official life cycle map

The details of the life cycle will be updated in the next few chapters

I’ll just mention a few of the most common lifecycle functions and what they are used for

  • constructor

    • Initialize the internal state

    • Bind this to the event

  • render

    • The React soul is used to describe UI and interactions

    • Props /state/forceUpdate will invoke the lifecycle again to update the page

  • shouldComponentUpdate

    • Optimize performance by comparing data before and after update
  • componentDidMount

    • Network request
  • componentWillUnmount

    • Optimize performance, such as the clearing timer

State of ascension

State ascension is a conceptual thing

Status refers to some data that is shared between components

Promotion refers to saving these states in the parent component closest to them

For example, to change a theme, we need to store the state of the theme in the root component and pass it down through the props layer by layer

ref

Let’s implement a simple implementation of an input function

Its internal data is maintained by our state

So we just read the value of state which is the value in the input

import React, { Component } from 'react';

interface IProps {}

interface IState {
  inputVal: string;
}

class App extends Component<IProps.IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      inputVal: ' '}; }handleInputChange(e: any) {
    this.setState({
      inputVal: (e.target as HTMLInputElement).value,
    });
  }
  render() {
    return (
      <input
        value={this.state.inputVal}
        onChange={(e)= > this.handleInputChange(e)}
      />); }}export default App;
Copy the code

Now we get the input value directly from the DOM

React gives us the ref attribute that we can use to get an instance of the element

import React, { Component } from 'react';

interface IProps {}

interface IState {}

class App extends Component<IProps.IState> {
  private inputRef: React.RefObject<HTMLInputElement>;
  constructor(props: IProps) {
    super(props);
    this.state = {};
    this.inputRef = React.createRef();
  }
  render() {
    return (
      <>{/* mode 1 dom in current property */}<input
          ref={this.inputRef}
          onChange={()= >Console. log(this.inputref.current)} /> {console.log(this.inputref.current)} /> {<input
          ref={(inputRef)= > (this.inputRef = inputRef as any)}
          onChange={() => console.log(this.inputRef)}
        />
      </>); }}export default App;
Copy the code

The difference between the two is

  • The former example is under the current attribute

  • And the example created by the function is the property that we named

Do not do this to the DOM in a production environment

Do not use imperative for anything that can be done declaratively

Unless we need to do things like focus animations that we need to get to the DOM

Controlled/uncontrolled components

Both controlled and uncontrolled components are generally specific to form elements because they have their own value attribute to manage their own state

Controlled means that the state of an element is maintained by external data. A data-driven view is the former in the example above

Uncontrolled means that the state of the element is maintained by itself, meaning that JQ manipulates the DOM to get the data and in this case the REF

context

Let’s say we have an App component

Then there is a HeaderWrapper component under the App component

The HeaderWrapper component has another Header component inside it

So if we want to put data from the App component into the Header component

The data flows through the HeaderWrapper component

But this component doesn’t need the props that the Header component does

Especially when you use type constraints you’ll need to write props constraints for intermediate components that pass data that they don’t need 😳

If we pass it through the layers then the code looks like this

import React, { Component } from 'react';

interfaceIProps { header? :string;
}

interfaceIState { header? :string;
}

class App extends Component<IProps.IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      header: 'This is what the Header component needs.'}; }render() {
    return <HeaderWrapper header={this.state.header} />; }}class HeaderWrapper extends Component<IProps.IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {};
  }
  render() {
    return <Header {. this.props} / >; }}class Header extends Component<IProps.IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {};
  }
  render() {
    return <h1>{this.props.header}</h1>; }}export default App;
Copy the code

Well it’s just gone through one layer and we’re already feeling the trouble if it’s 🤔

React gives us a property called context to solve cross-component communication problems

Commonly used API are

  • React.createContext(defaultValue)

  • contextType

  • Provider

  • Consumer

import React, { Component } from 'react';

interface IProps {}

interface IState {}

// step1 Create one context
const HeaderContext = React.createContext({
  header: 'This is what the Header component needs.'});class App extends Component<IProps.IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {};
  }
  render() {
    return (
      <HeaderContext.Provider value={{ header:'this isHeaderWhat the component needs'}}>
        <HeaderWrapper />
        <FunHeader />
      </HeaderContext.Provider>); }}// At this point our HeaderWrapper component is clean and no longer needs to pass the props it doesn't need
// But you can get the value in the context if you want it
class HeaderWrapper extends Component<IProps.IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {};
  }
  render() {
    return <Header {. this.props} / >; }}class Header extends Component<IProps.IState> {
  // step3 assign contentType to the context where you need it
  static contextType = HeaderContext;
  constructor(props: IProps) {
    super(props);
    this.state = {};
  }
  render() {
    // Step 4 Use 🥰
    return <h1>{this.context.header}</h1>; }}// If the component is functional, use the following notation
function FunHeader() {
  return (
    <HeaderContext.Consumer>
      {(value) => <h1>{value.header}</h1>}
    </HeaderContext.Consumer>
  );
}

export default App;
Copy the code

But we don’t usually use context in real development

We usually use redux/mobx in a build environment

You can see the history of context at 👉 here

Synthetic events

Binding events onClick and so on in React are synthetic events in React

It differs from the native onclick event primarily to smooth out differences between browsers

Because React is not only expected to run on the Web but also on the client side, ios, Android, etc

The event object in React is the first parameter passed to bind an event by default

React also encapsulates this object for use in all development scenarios

dangerouslySetInnerHTML

Suppose we have the following code and we want to render the DOM element in the tag

If rendered directly it will be rendered as a string on the page

We need to use dangerouslySetInnerHTML to tell React that this is a DOM element

But dangerously, as its name suggests, also has side effects

Inappropriate use may expose your page to XSS attacks

So forget about this property 😛

import React, { Component } from 'react';

interface IProps {}

interface IState {
  tag: string;
}

class App extends Component<IProps.IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {
      tag: '<h2>HELLO REACT</h2>'}; }render() {
    return (
      <>{/* display in the page<h2>HELLO REACT</h2>*/} {this.state.tag} {/* page correctly parse h2 tag */}<div dangerouslySetInnerHTML={{ __html: this.state.tag}} ></div>
      </>); }}export default App;
Copy the code

Fragments

All JSX must be wrapped with a root element

If you do not want to create additional elements then you can use Fragments to wrap them

This element does not create any additional DOM nodes, so anything you do with this component will be invalidated

You can also use the shorthand <> JSX

StrictMode

By default StrictMode is wrapped around the tag when creating a project using scaffolding

Like Fragment StrictMode does not create any UI elements as it literally means it’s primarily used

  • Identify insecure lifecycles

  • Use outdated REF’s API

  • Check for unexpected side effects

    • Development environments call Constructor twice
  • Identify obsolete findDOMNode methods

  • Detect outdated context API

Error boundary

Error bounds depend on the lifecycle function componentDidCatch so currently only class components can implement error bounds

Error boundaries help us degrade the UI in the event of a page error without the page crashing

Below post a paragraph of official website demo poke me 👇
import React from 'react';

interface IProps {}

interface IErrorState {
  error: any;
  errorInfo: any;
}

interface ICountState {
  counter: number;
}

class ErrorBoundary extends React.Component<IProps.IErrorState> {
  constructor(props: IProps) {
    super(props);
    this.state = { error: null.errorInfo: null };
  }

  componentDidCatch(error: any, errorInfo: any) {
    // Catch errors in any components below and re-render with error message
    console.log('error:', error);
    console.log('errorInfo:', errorInfo);
    this.setState({
      error: error,
      errorInfo: errorInfo,
    });
    // You can also log error messages to an error reporting service here
  }

  render() {
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // Normally, just render children
    return this.props.children; }}class BuggyCounter extends React.Component<IProps.ICountState> {
  constructor(props: IProps) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(({ counter }) = > ({
      counter: counter + 1,})); }render() {
    if (this.state.counter === 5) {
      // Simulate a JS error
      throw new Error('I crashed! ');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>; }}function App() {
  return (
    <div>
      <p>
        <b>
          This is an example of error boundaries in React 16.
          <br />
          <br />
          Click on the numbers to increase the counters.
          <br />
          The counter is programmed to throw when it reaches 5. This simulates a
          JavaScript error in a component.
        </b>
      </p>
      <hr />
      <ErrorBoundary>
        <p>
          These two counters are inside the same error boundary. If one crashes,
          the error boundary will replace both of them.
        </p>
        <BuggyCounter />
        <BuggyCounter />
      </ErrorBoundary>
      <hr />
      <p>
        These two counters are each inside of their own error boundary. So if
        one crashes, the other is not affected.
      </p>
      <ErrorBoundary>
        <BuggyCounter />
      </ErrorBoundary>
      <ErrorBoundary>
        <BuggyCounter />
      </ErrorBoundary>
    </div>
  );
}

export default App;
Copy the code

Render Props

Render prop is a function that tells a component what to render

React.docschina.org/docs/render…

High order component

A higher-order component is a function that receives a component and returns a new component

The main functions are

  • Manipulates all props passed in

  • Life cycle of an operational component

  • Static methods on actionable components

  • Get refs

  • Operational state

  • You can render hijack

import React, { Component } from 'react';

interfaceIProps { theme? :string;
}

interfaceIState { theme? :string;
}

class App extends Component<IProps.IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {};
  }
  render() {
    return (
      <>
        <HeaderWrapper />
        <ArticleWrapper />
      </>); }}class Header extends Component<IProps.IState> {
  render() {
    return <p style={{ color: this.props.theme}} >NavBar</p>; }}class Article extends Component<IProps.IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {};
  }
  render() {
    return <p>Article</p>; }}// The generic type T means to receive the props of the component. Since we need to inject new props, T must inherit the interface that has the new properties
function ThemeHOC<T extends IProps> (Component: React.ComponentType<T>) {
  return class extends React.Component {
    render() {
      // Inject the theme property to the component
      return <Component {.(this.props as T)} theme={'red'} / >; }}; }// Props state render method
function LifeHOC<T> (Component: React.ComponentType<T>) {
  return class extends React.Component {
    constructor(props: T) {
      super(props);

      // Hijack an instance of the original component and modify it
      console.log(this);

      // Manipulates all props passed in
      // Life cycle of operable components
      // The static method of the actionable component
      / / for refs
      // Can operate state
      // Can render hijacking
    }
    render() {
      return <Component {.(this.props as T)} / >; }}; }const HeaderWrapper = ThemeHOC(Header);
const ArticleWrapper = LifeHOC(Article);

export default App;
Copy the code

Type checking

If your project doesn’t use typescript yet and you want to restrict types

You’ll probably use this library prop-types

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class App extends Component {
  static propTypes = {
    nickname: PropTypes.string.isRequired,
    age: PropTypes.number,
  };

  static defaultProps = {
    nickname: 'nanshu'.age: 18};render() {
    return (
      <div>
        <h1>{this.props.nickname}</h1>
        <h1>{this.props.age}</h1>
      </div>); }}Copy the code

But prop-types only does warning at the warning level ⚠️ it doesn’t interrupt our program