React function components and class components

Original text: overreacted. IO/how – are – fun…

What is the difference between a function component and a class component in react.js development?

Previously, it was generally thought that the difference was that class components provided more features (such as state). This is no longer true with the adoption of React Hooks (through which function components can also have state and class life cycle callbacks).

As you may have heard, one of these two types of components performs better. What kind? Many of these performance tests are flawed, so one has to be cautious about drawing conclusions from them. Performance depends largely on what your code does (and your implementation logic), regardless of whether you use functional components or class components. We observed that although the performance optimization strategies for functional and class components were somewhat different, the differences in their performance were minimal.

Either way, we don’t recommend using a function component to override an existing class component unless you have another reason or you like to be the first to do it. React Hooks are new (as React was in 2014), and there are currently no best practices for using Hooks.

Are there any other differences besides these? Is there really a fundamental difference between a functional component and a class component? Of course, there are — in the mental Model “(😅) In this article, we’ll take a look at the biggest differences between the two types Of components. This difference, which existed in 2015 when the function component function component was introduced in React, was largely ignored.

The difference between function components and class components

The function component captures the state inside render

Let’s take a step by step look at what this means.

** Note that this article is not a value judgment on function and class components. I’m just showing you the difference between the two component programming models in the React ecosystem. To learn how to use functional components better, please refer to the official document Hooks FAQ **.

Suppose we have the following function component:

function ProfilePage(props) {
  const showMessage = (a)= > {
    alert('Followed ' + props.user);
  };

  const handleClick = (a)= > {
    setTimeout(showMessage, 3000);
  };

  return (
    <button onClick={handleClick}>Follow</button>
  );
}
Copy the code

The component renders a button, simulates an asynchronous request when clicked, and displays a popover in the request’s callback function. For example, if the props. User value is Dan, 3 seconds after clicking the button, we will see the Followed Dan prompt pop-up. Very simple.

(Note that in the above code, the arrow function is still a normal function, there is no difference. Because there is no this problem. HandleClick () has no problem replacing the arrow function with a normal function.

How do we implement a class component that does the same thing? A simple translation:

class ProfilePage extends React.Component {
  showMessage = (a)= > {
    alert('Followed ' + this.props.user);
  };

  handleClick = (a)= > {
    setTimeout(this.showMessage, 3000);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>; }}Copy the code

Normally, we would think of the above two component implementations as being completely equivalent. Developers often refactor code between functional components and class components, as above, without realizing the underlying differences.

However, there are subtle differences between the two implementations above. Take a closer look. Can you see the difference? To be honest, it took me a while to see the difference.

** If you want to see the source code online, you can click here **. The rest of this article is devoted to this difference and why it matters.

Before we go any further, I want to reiterate that the differences mentioned in this article have nothing to do with React hooks themselves! In the above example, I didn’t use hooks.

This article explains the difference between function components and class components in the React ecosystem. If you plan to use function components on a large scale in React development, you may want to be aware of this difference.

We’ll use a common bug encountered during react development to demonstrate this difference.

Next, let’s duplicate this bug. When you open the online example, there is a name drop-down on the page, with two components of interest, one function component and the other class component.

For each concern button, perform the following operations respectively:

  1. Click one of the focus buttons
  2. Reselect the name in the drop – down box within 3 seconds
  3. After 3 seconds, notice the difference in the text in the Alert popup

You should notice the difference between the two alert popovers:

  • In the test case of a functional component, select it from the drop-down boxDan, click the follow button to quickly switch the drop-down box toSophie, 3 seconds later, the alert popover is still the sameFollowed Dan
  • In the test case of the class component, repeat the same action and after 3 seconds, the Alert popup will displayFollowed Sophie

In this case, the implementation using the function component is correct, and the implementation of the class component is obviously buggy. If I follow one person first and then switch to another person’s page, the follow button shouldn’t confuse which one I’m actually following.

(PS, I also recommend you really follow Sophie)

So why is there a problem with our class component?

Let’s take a closer look at the showMessage implementation of the class component:

class ProfilePage extends React.Component {
  showMessage = (a)= > {
    alert('Followed ' + this.props.user);
  };
Copy the code

This method reads this.props. User. In the React ecosystem, props is immutable data that never changes. However, this is always mutable.

Indeed, the meaning of this is mutable. React will modify the data on this during execution to ensure that you can read the latest data (props, state) in render and other lifecycle methods.

So, if our component is re-rendered during network request processing, this.props changes. After that, the showMessage method reads the changed this.props.

This reveals an interesting fact about user interface rendering. If we think of the user interface (UI) as a visual representation of the current application state (UI=render(state)), then the event handler is as much a part of the render result as the user interface. Our event handler is the props and state associated with render when the event is triggered.

However, we broke the connection between showMessage and this.props by delaying the call to showMessage with a setTimeout in the button click event handler. The showMessage callback is no longer bound to any render, and it also loses the props it was associated with. Reading data from this breaks the association.

If the function component does not exist, how can we solve this problem?

We need to somehow fix the association between showMessage and its render and corresponding props.

One way we can do this is to read the props in the button click function and explicitly pass it to the showMessage, like this:

class ProfilePage extends React.Component {
  showMessage = (user) = > {
    alert('Followed ' + user);
  };

  handleClick = (a)= > {
    const {user} = this.props;
    setTimeout((a)= > this.showMessage(user), 3000);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>; }}Copy the code

This way can solve the problem. However, this solution introduced redundant code, which could easily introduce other problems over time. What if our showMessage method wanted to read more props? What if showMessage also accesses state? If showMessage calls another method and that method reads another state, such as this.props. Something or this.state. Something, we have the same problem again. We might need to explicitly pass this.props this.state in the showMessage to any other method called.

By doing so, we risk undermining the benefits of class components. It can also be difficult to determine when passing is needed and when not, further increasing the risk of introducing bugs.

Again, simply putting all the code in the onClick handler can cause us other problems. For code readability, maintainability, and so on, we often break up large functions into separate, smaller functions. This problem is not unique to React, but is common to any UI library that maintains mutable data on this.

Maybe we could bind some methods in the constructor of the class?

class ProfilePage extends React.Component {
  constructor(props) {
    super(props);
    this.showMessage = this.showMessage.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }

  showMessage() {
    alert('Followed ' + this.props.user);
  }

  handleClick() {
    setTimeout(this.showMessage, 3000);
  }

  render() {
    return <button onClick={this.handleClick}>Follow</button>; }}Copy the code

Unfortunately, the above code does not solve this problem! Remember, the reason for this problem is that we read this.props too late, and it has nothing to do with syntax. However, if we could rely entirely on JavaScript’s closure mechanism, we could solve this problem completely.

For the most part, we’ll try to avoid closures because it’s a little harder to determine the value of a variable in the case of closures. However, in React, props and state are immutable. (Strictly speaking, we strongly recommend using props and state as immutable data.) The immutable properties of props and state perfectly solve the problem of using closures.

This means that if we access the props and state in the Render method through the closure, we can ensure that the props and state are the same as the render data when showMessage executes:

class ProfilePage extends React.Component {
  render() {
    // Capture the props!
    const props = this.props;

    // Note: we are *inside render*.
    // These aren't class methods.
    const showMessage = (a)= > {
      alert('Followed ' + props.user);
    };

    const handleClick = (a)= > {
      setTimeout(showMessage, 3000);
    };

    return <button onClick={handleClick}>Follow</button>; }}Copy the code

In render execution, you managed to capture the props at that time.

This way, any code in the Render method can access the props at render execution, rather than the modified values. React won’t sneak around with our cheese anymore.

As above, we can add any helper functions to the Render method as needed, and these functions should properly access the props of the render execution. Closures, the saviour of all.

The code above, functionally fine, looks a little strange. If component logic is defined as functions inside Render, rather than as instance methods of classes, why use classes at all?

Indeed, we strip away the cloak of a class and what is left is a function component:

function ProfilePage(props) {
  const showMessage = (a)= > {
    alert('Followed ' + props.user);
  };

  const handleClick = (a)= > {
    setTimeout(showMessage, 3000);
  };

  return (
    <button onClick={handleClick}>Follow</button>
  );
}
Copy the code

This function component, like the class component above, has an internal function that captures props, and React passes in props as the function argument. Unlike this, props is immutable; React does not modify props.

If you use the props structure in the function argument, the code looks clearer:

function ProfilePage({ user }) {
  const showMessage = (a)= > {
    alert('Followed ' + user);
  };

  const handleClick = (a)= > {
    setTimeout(showMessage, 3000);
  };

  return (
    <button onClick={handleClick}>Follow</button>
  );
}
Copy the code

When the parent component passes in different props to re-render the ProfilePage, React calls the ProfilePage again. But before we do that, we have captured the props of the last render by clicking on the event handler for the focus button.

That’s why, in the function component of this example, there is no problem.

As you can see, the functionality is exactly right. (PS again, I suggest you also follow Sunil)

Now we understand the difference between a functional component and a class component:

The function component captures the state inside render

Function components work with React Hooks

Function components also capture state inside Render in case of Hooks. Take a look at this example:

function MessageThread() {
  const [message, setMessage] = useState(' ');

  const showMessage = (a)= > {
    alert('You said: ' + message);
  };

  const handleSendClick = (a)= > {
    setTimeout(showMessage, 3000);
  };

  const handleMessageChange = (e) = > {
    setMessage(e.target.value);
  };

  return (
    <>
      <input value={message} onChange={handleMessageChange} />
      <button onClick={handleSendClick}>Send</button>
    </>
  );
}
Copy the code

(Online demo, click here)

Although this is a rudimentary message sending component, it also presents the same problem as the previous example: if I click the send button, the component should send the message I typed at the moment I clicked the button.

OK, we now know that the function component captures props and state by default. But what if you want to read the latest props, state, and not the data captured at render at some point? Even if we want to read the old props and state at some point in the future?

In the class component, we simply need to read this.props this.state to access the latest data, because React will modify this. In a function component, we can also have a mutable data that can share the same data in each render. This is useRef from hooks:

function MyComponent() {
  const ref = useRef(null);
  // You can read or write `ref.current`.
  // ...
}
Copy the code

However, you need to maintain the ref values yourself.

The ref in the function component plays the same role as the instance attribute in the class component. You are probably familiar with DOM Refs, but refs in hooks are more generic. The ref in hooks is just a container, you can put whatever you want in the container.

It even seems that this.something in the class component is similar to something. Current in hooks; they do represent the same concept.

By default, React does not create refs for props and state in function components. In most cases, you don’t need to do this either, which also requires extra work to assign refs. Of course, you can manually implement code to keep track of the latest state:

function MessageThread() {
  const [message, setMessage] = useState(' ');
  const latestMessage = useRef(' ');

  const showMessage = (a)= > {
    alert('You said: ' + latestMessage.current);
  };

  const handleSendClick = (a)= > {
    setTimeout(showMessage, 3000);
  };

  const handleMessageChange = (e) = > {
    setMessage(e.target.value);
    latestMessage.current = e.target.value;
  };
Copy the code

If we read the Message field in showMessage, then we’ll get the value of the input field when we click the button. However, if we read latestmessage.current, we get the most recent value in the input field — even if we keep typing new content after clicking the send button.

You can compare these two demos to see the difference.

In general, you should avoid reading or modifying refs in the Render function because refs are mutable. We want to make sure that render’s results are predictable. However, if we want to get the latest value for a certain props or state, manually updating the refs value every time is tedious. In this case, we can use the useEffect hook:

function MessageThread() {
  const [message, setMessage] = useState(' ');

  // Keep track of the latest value.
  const latestMessage = useRef(' ');
  useEffect((a)= > {
    latestMessage.current = message;
  });

  const showMessage = (a)= > {
    alert('You said: ' + latestMessage.current);
  };
Copy the code

(The demo is here)

We update ref values in useEffect to ensure that ref values are updated only after DOM updates. This ensures that our ref changes don’t break some of the new features in React, such as time sharding and interrupts, which rely on render that can be interrupted.

It’s not very common to use a ref like the one above. In most cases, we need to capture props and state. However, it is very easy to use refs when dealing with imperative apis, such as setting timers, subscribing to events, and so on. Remember, you can use ref to track any value — a prop, a state, an entire props, or a function.

Using ref is also useful in some performance optimization scenarios. For example, when using useCallback. However, using useReducer is a better solution in most scenarios.

conclusion

In this article, we reviewed a common problem with class components and how to use closures to solve it. However, as you have probably experienced, if you try to optimize the performance of hooks by specifying hooks dependencies, you will most likely access the old props or state in hooks. Does this mean closures are problematic? I don’t think so.

As we saw above, closures help us solve these subtle problems in some subtle scenarios. Not only that, closures make it easier to write bug-free code in parallel mode. Because closures capture the props and state of our render function runtime, parallel patterns are possible.

In all the cases I’ve experienced so far, access to the old props/state is usually due to the mistaken belief that “the function will not change” or “the props will always be the same”. This is not true in real time, and I hope this article has helped you understand that.

When we used functions to develop most react components, we needed to correct our understanding of code optimizations and what states would change.

As Fredrik puts it:

The most important rule I learned in using React hooks is, “Any variable can be changed at any time”.

Functions also obey this rule.

The React function always captures props, state, and finally, again.

Some places do not understand how to translate, there is a deletion, suggest reading the original!