As a beginner of Reac, I was always in a fog about how to declare components in several ways! What are the higher-order components in React? What are the disadvantages of using higher-order components? What are the render props? What are the usage scenarios? Why a functional Hooks component? Sum them up today, convenient for you, convenient for me, convenient for him!

The class components

You can use class components when dealing with React lifecycle methods (especially componentDidCatch)

React.componentcreates stateful components that are instantiated and have access to the component’s lifecycle methods.

Here is a component to catch React errors

import type { ErrorInfo, ReactNode } from "react";
import React from "react";

interface Props {
  children: ReactNode;
}

interface State {
  error: Error | null;
  errorInfo: ErrorInfo | null;
}
class ErrorBoundary extends React.Component<Props.State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      error: null.errorInfo: null}; }componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    // Catch render errors for all child components under component wraps
    this.setState({
      error,
      errorInfo,
    });
    // You can also call the backend interface from here to store the error information to the database
  }

  render() {
    if (this.state.errorInfo) {
      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>
      );
    }
    // If there are no errors, use Render prop
    return this.props.children; }}export default ErrorBoundary;
Copy the code

Use the sample

import React from "react";
import ReactDOM from "react-dom";

import ErrorBoundary from "./ErrorBoundary";

const Index = () = > {
  // The ErrorBoundary component catches a undefined error
  console.log(a);

  return <h1>hello world</h1>;
};

ReactDOM.render(
  <ErrorBoundary>
    <Index />
  </ErrorBoundary>.document.getElementById("root"));Copy the code

Class component problem: Member functions are not automatically boundthis

React.Com ponent created component, its member function does not automatically binding this, need a developer manual binding, this won’t be able to access to current component instance objects

Examples of errors:

import React from "react";

class Index extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: "hello world"}; }handleClick() {
    // Cannot read property 'state' of undefined
    console.log(this.state);
  }
  render() {
    return <button onClick={this.handleClick}>Click on the I</button>; }}export default Index;
Copy the code

Solution 1: Use functional components (recommended)

Because functional components don’t have this, you don’t need to worry about these issues

import React, { useState } from "react";

const Index = () = > {
  const [text, setText] = useState("hello world");

  const handleClick = () = > {
    // 'hello world'
    console.log(text);
  };

  return <button onClick={handleClick}>Click on the I</button>;
};

export default Index;
Copy the code

Solution 2: Use arrow functions in events outside of Render

The arrow function uses bind to bind this in the enclosing scope (in other words, this does not change with scope).

import React from "react";

class Index extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: "hello world"}; }// Use the arrow function here
  handleClick = () = > {
    // { text: 'hello world'}
    console.log(this.state);
  };
  render() {
    return <button onClick={this.handleClick}>Click on the I</button>; }}export default Index;
Copy the code

Solution 3: Use bind in render

import React from "react";

class Index extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: "hello world"}; }handleClick() {
    console.log(this.state);
  }
  render() {
    return (
      // Use bind to bind
      <button onClick={this.handleClick.bind(this)}>Click on the I</button>); }}export default Index;
Copy the code

The handleClick function is regenerated every time render is performed, so it has a performance impact. This sounds like a big problem, but in most applications, the performance impact of this approach is minimal.

Bottom line: If you run into performance issues, avoid using bind or arrow functions in Render. Refer to the link

Solution 4: Use the arrow function in Render

import React from "react";

class Index extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: "hello world"}; }handleClick() {
    console.log(this.state);
  }
  render() {
    return (
      // Use arrow function to bind
      <button onClick={()= >Enclosing handleClick ()} > click me</button>); }}export default Index;
Copy the code

Same problem as plan 3

Solution 5: Bind in the constructor

import React from "react";

class Index extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: "hello world"};// bind in the constructor
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log(this.state);
  }
  render() {
    return <button onClick={this.handleClick}>Click on the I</button>; }}export default Index;
Copy the code

Each time you declare an event, you have to do the binding in the constructor, which is not very readable or maintainable

PropTypes/defaultProps components

As your application grows, you can catch a lot of errors through type checking. For some applications, you can use JavaScript extensions such as TypeScript to type check the entire application.

But even if you don’t use TypeScript, React has some type checking built in. To do type checking on the props of a component, you simply configure the specific propTypes attribute:

React official documentation -PropTypes

When react.componentconfigures propTypes and defaultProps when creating a component, they are configured as properties of the component class, not as properties of the component instance, which is called static properties of the class. The configuration is as follows:

import React from "react";
import PropTypes from "prop-types";

class MyButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: "hello world"};// bind in the constructor
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(this.state);
  }
  render() {
    return <button onClick={this.handleClick}>Click on the I</button>;
  }
}

MyButton.propTypes = {
  // The static property type of the class
  name: PropTypes.string.isRequired,
};

MyButton.defaultProps = {
  // Static attribute defaults for the class
  // name: ''
};

const Index = () = > {
  return (
    <React.StrictMode>
      <MyButton />
    </React.StrictMode>
  );
};

export default Index;
Copy the code

When isRequired is set, the control will be warned if the corresponding prop is not passed

In this example, we use the class components, but the same function can also be used for function component, or by the React. The memo/React forwardRef create components.

However! We all use Typescript now, just to get you started

High order Component (HOC)

A generic component converts props/state to UI, while a higher-order component converts one component to another.

Simply put, HOC is a function that takes a component as an argument and returns a new component

Official documentation -HOC

HOC is essentially a decorator pattern within a design pattern

HOC is common in React third-party libraries, such as Redux’s Connect

Advantages of HOC: reduce the complexity of the original component code logic, extract the general logic to higher-order components, and finally realize the reuse of component logic

  • Gets an instance of the original componentref
  • Removed from the original componentstate/props

Applicable scenarios:

For example, two pages with almost the same UI and almost the same functionality, with only a few different operations, write two page-level components with a lot of coupling. Because of its excessive coupling, one function is often added (both components are added), and the second is changed as the first is changed.

So when you add new functionality, write a higher-level component, add methods to HOC, and wrap that component around so that the new code doesn’t coupling and the old logic doesn’t change

Here is an example of a high-level component that reuses Table data requests

See the online Demo for details

Problems with higher-order components

Unable to get the ref of the original component

Refs will not pass through wrapped components. This is because ref is not a prop property. Just like the key, it gets special processing by React. If you add a Ref to HOC, that ref will refer to the outermost container component, not the wrapped component.

For details, see Forwarding Refs in Advanced Components

How do you deal with that?

Use the React. ForwardRef API to explicitly forward refs to internal components. The React. ForwardRef takes a render function that accepts the props and ref arguments and returns a React node. Such as:

// hocLog.jsx

import React from "react";

// A log to print the props of the component - advanced component
function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      if(prevProps.label ! = =this.props.label) {
        console.log("old props:", prevProps);
        console.log("new props:".this.props); }}render() {
      const{ forwardedRef, ... rest } =this.props;

      // define the custom prop property "forwardedRef" as ref
      return <Component ref={forwardedRef} {. rest} / >; }}// the react. forwardRef callback has the second argument "ref".
  // We can pass this as a regular prop property to LogProps, such as "forwardedRef"
  // It can then be mounted to a child component wrapped around LogProps.
  return React.forwardRef((props, ref) = > {
    return <LogProps {. props} forwardedRef={ref} />;
  });
}

export default logProps;
Copy the code
// index.jsx
import React, { useState } from "react";

import hocLog from "./hocLog";

class MyButton extends React.Component {
  getData = () = > {
    console.log("I've been called.");
  };

  render() {
    const { label, handleClick } = this.props;
    return (
      <>
        <button onClick={handleClick}>{label}</button>
      </>); }}const NewMyButton = hocLog(MyButton);

const Index = () = > {
  const buttonRef = React.createRef();

  const [label, setLable] = useState("Please click on me");

  // Invoke a member inside a child component
  const handleClick = () = > {
    buttonRef.current.getData();

    setLable("I've already been clicked.");
  };

  return (
    <NewMyButton label={label} handleClick={handleClick} ref={buttonRef} />
  );
};

export default Index;
Copy the code

Unable to get a static method of the original class component

When you apply HOC to a component, the original component is wrapped with a container component. This means that the new component does not have any static methods of the original component

To solve this problem, you can copy these methods to the container component before returning

Official documentation -HOC must copy static methods

render props

“Render Prop” refers to a simple technique for sharing code between React components using a prop with a value of function

React – Render prop

Components with Render Prop accept a function that returns a React element and calls it instead of implementing its own render logic.

More specifically, Render Prop is a function prop that tells components what to render.

import React, { useState } from "react";

interface Props {
  header: () = > React.ReactNode;
  footer: () = > React.ReactNode;
}

/ / the React. VFC reference: https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components
const MyButton: React.VFC<Props> = ({ header, footer }) = > {
  const [text, setText] = useState("hello world");

  const handleClick = () = > {
    // 'hello world'
    console.log(text);
  };

  return (
    <>
      {header()}
      <button onClick={handleClick}>Click on the I</button>
      {footer()}
    </>
  );
};

const Index = () = > {
  return (
    <MyButton
      header={()= > <h1>Future composition head</h1>}
      footer={() => <h1>Future foot</h1>} / >
  );
};

export default Index;
Copy the code

props.childrenIs a special onerender prop

So how do I pass properties to props. Children?

In React, props. Children is a special Render prop that represents all nodes of the component.

There are three possibilities for the props. Children value:

  • If the current component has no child nodes,props.childrenundefined;
  • If the current component has only one child node,props.childrenobject;
  • If the current component has multiple child nodes,props.childrenforarray.

How do we pass the parent component’s doSomething method to {props. Children}? How to pass props to an uncertain child component in a parent component?

React.Children gives us a handy way to do this. Among them:

The function of cloneElement is to clone and return a new ReactElement (the inner child is cloned as well). The new element will retain the props, ref, and key of the old element and integrate the new props (as long as it is defined in the second parameter).

React.children. Map to traverse child nodes without worrying about whether props. Children is undefined or object

Then we call this.props. DoSomething () directly in the child component.

import React, { useEffect } from "react";

interface ChildProps {
  doSomething: () = > void;
  name: string;
  age: number;
}

const Child: React.VFC<ChildProps> = ({ doSomething, name, age }) = > {
  useEffect(() = > {
    console.log("Child component receives name,name:", name, age);
  }, [name, age]);

  return <button onClick={doSomething}>Click on me to trigger an external event</button>;
};

// -------------
interface Item {
  key: string;
  name: string;
  age: number;
}

interface ParentProps {
  items: Item[];
  renderItem: (items: Item[]) = >React.ReactNode; label? : React.ReactNode; children: React.ReactElement; }const Parent: React.VFC<ParentProps> = ({ children, label, items, renderItem, }) = > {
  const selfProps = { name: "a".age: 10 };

  const doSomething = () = > {
    console.log("DoSomething was triggered.");
  };

  // Pass some props to props. Children
  const childrenWithProps = React.Children.map(children, (child) = >React.cloneElement(child, { doSomething, ... selfProps }), );return (
    <>
      {label && <h2>{label}</h2>} // This is a render prop {renderItem(items)}<div style={{ border: "1px solid black", margin: 10}} >
        <h3>Child components</h3>This is a special render prop {childrenWithProps}</div>
    </>
  );
};

const Index = () = > {
  const items = [
    { key: "1".name: "Wang".age: 10 },
    { key: "2".name: "老李".age: 20},];return (
    <Parent
      label='Title of parent component'
      items={items}
      renderItem={()= >
        items.map((item) => (
          <div key={item.key}>Name :{item.name}, age {item.age}</div>))} ><Child />
    </Parent>
  );
};

export default Index;
Copy the code

For a more detailed discussion: What is the use of Render props?

Pass the component as Prop

function PassThrough(props: { as: React.ElementType<any>}) {
  const { as: Component } = props;

  return <Component />;
}
Copy the code

Stateless/pure function components

It is generally recommended to write pure functions

Pure functions: the return of a function depends only on its arguments and has no side effects during execution;

  • The result of a function is affected only by its parameters;
  • A function is said to have side effects if there are external observable changes to its execution;

Common side effects (there are many ways for a function to change externally observable during execution);

  • Modify external variables;
  • callDOM APIModify page;
  • AjaxRequests;
  • window.reloadRefresh the browser;
  • console.logPrinting data to the console is also a side effect;

Function components also have the following salient features:

  1. Components are not instantiated and overall rendering performance is improved

    Since the component is reduced to a function of the Render method, there is no component instantiation process, and there is no need to allocate extra memory without instantiation process, thus improving performance.

  2. Component not accessiblethisobject

    Function components cannot access objects in this because there is no instantiation process, such as this.ref, this.state, etc. You cannot create components in this form if you want to access them

  3. Stateless components can only access input props, and the same props will get the same render without side effects

Use stateless components whenever possible.

Here is an example of a stateless component

import React, { useState, useEffect } from "react";
import { fetchUser } from "./api";

// This is a stateless component
const UserInfo = (props) = > {
  const { user } = props;

  const { name, age } = user;
  return (
    <div>
      <div>{name}</div>
      <div>{age}</div>
    </div>
  );
};

const UserInfoPage = (props) = > {
  const [user, setUser] = useState(null);

  useEffect(() = > {
    async function getUser() {
      const user = await fetchUser("/userAppi");

      if(user) { setUser(user); } } getUser(); } []);return <UserInfo user={user} />;
};
Copy the code

Has a stateful function component /Hooks component

Now that you have the class component, why do you have the Hooks component?

Function components more closely match the React framework design concept, namely UI=Function(data).

The React component itself is positioned as a function, a function that eats data and spit out the UI. As developers, we write declarative code,

The React framework’s main job is to convert declarative code into imperative DOM operations in a timely manner, and map data-level descriptions to user visible UI changes.

This means that, in principle, React data should always be tightly bound to render, whereas class components can’t.

The UserInfoPage in the above code is the stateful function component

Custom Hooks component

Please refer to official – custom Hook for details

Below is a custom Hook for a data request

export function useFetch(request: RequestInfo, init? : RequestInit) {
  const [response, setResponse] = useState<null | Response>(null);
  const [error, setError] = useState<Error | null> ();const [isLoading, setIsLoading] = useState(true);

  useEffect(() = > {
    // Used to interrupt the fetch request
    const abortController = new AbortController();
    setIsLoading(true);
    (async() = > {try {
        const response = awaitfetch(request, { ... init,signal: abortController.signal,
        });
        setResponse(awaitresponse? .json()); setIsLoading(false);
      } catch (error) {
        if (error.name === "AbortError") {
          return;
        }
        setError(error);
        setIsLoading(false);
      }
    })();
    return () = > {
      // Interrupts the request while the page is unmounted
      abortController.abort();
    };
  }, [init, request]);
  // Return: response data, error message,laoding status
  return { response, error, isLoading };
}
Copy the code

The difference between functional components and class components

Essential difference: Function components capture state inside render

You can see the effect if you try to switch the user to Sophie within 3 seconds of clicking the ProfilePage button in the form of a class component in this online Demo

We clicked “Followed Sophie” on Dan’s page instead of “Followed Sophie”!

The content of user is delivered via props, while the value of props is immutable. Why is the value of user changed from Dan to Sophie?

How are functional components different from class components?

Reference documentation

  1. How is a functional component different from a class component? – recommend 🤖
  2. React creates components in three ways and their differences
  3. React-effect official document
  4. Higher Order Components in a React Hooks World

The last

Do you know of any other component declaration considerations? Feel free to leave your thoughts in the comments! The article is shallow, also please give for everybody not stingy give advice!

Feel the harvest of friends welcome to praise, pay attention to a wave!

The articles

  1. How to build the enterprise front-end development specification 🛠
  2. Front-end developers should know the CI setup process
  3. Understand the basic knowledge of JavaScript module system, and build their own library 🍜
  4. What is NPM?