How are React function components different from classes?

The first thing we need to know is that the performance of the project mainly depends on the role of the code, rather than choosing functional or class components. Although the optimization strategies are slightly different , the performance difference between them is negligible.

1. Functional components capture the values ​​used for rendering

First let's look at the following component:

function App(props) {
  const showMessage = () => {
    alert('Hello' + props.user);
  };

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

  return (
    <button onClick={handleClick}>Say</button>
  );
}

It renders an exploit that simulates a network request, then displays a button to confirm the warning. For example, if the props.user passed in is jie, then Hello jie will pop up after three seconds.

So how should we write this component with classes? A simple refactoring might look like this:

class App extends React.Component {
  showMessage = () => {
    alert('Hello' + this.props.user);
  };

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

  render() {
    return <button onClick={this.handleClick}>Say</button>;
  }
}

We usually think that the two of them are equivalent when we do code refactoring, but is this really the case? We rarely pay attention to the meaning between them.

Next, we create a new react project, and create two new components under src, one classComponent component and one functionComponent component. The code is the two components we wrote above, but the content is slightly different:

classComponent:

import React from 'react';

class ProfilePage extends React.Component {
  showMessage = () => {
    alert('你选择了 ' + this.props.user);
  };

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

  render() {
    return <button onClick={this.handleClick}>选择</button>;
  }
}

export default ProfilePage;

functionComponent:

import React from 'react';

function ProfilePage(props) {
  const showMessage = () => {
    alert('你选择了 ' + props.user);
  };

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

  return (
    <button onClick={handleClick}>选择</button>
  );
}

export default ProfilePage;

In app.js we introduce these two components:

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

import ProfilePageFunction from './functionComponent';
import ProfilePageClass from './classComponent';

export default class App extends React.Component {
  state = {
    user: '小杰',
  };
  render() {
    return (
      <>
        <label>
          <b>选择你想要拜访的朋友</b>
          <select
            value={this.state.user}
            onChange={e => this.setState({ user: e.target.value })}
          >
            <option value="小杰">小杰</option>
            <option value="小尚">小尚</option>
            <option value="小宁">小宁</option>
          </select>
        </label>
        <h1>欢迎来到 {this.state.user}的 家!</h1>
        <p>
          <ProfilePageFunction user={this.state.user} />
          <b> (这是来自函数式组件的)</b>
        </p>
        <p>
          <ProfilePageClass user={this.state.user} />
          <b> (这是来自类组件的)</b>
        </p>
      </>
    )
  }
}

Run the project, scientific research sees this interface:

 When we click the button above, we execute the functional component, and when we click the button below, we execute the class. If we follow our previous thinking, both of them will have the same result, but is this really the case?

We execute in the following order:

1. Click the Functional Components button

2. Switch to the friends you want to visit immediately after clicking

The execution results of functional components are as follows:

The page that pops up is still the value we selected at that time

Let's try the same operation again with the class component:

Now the page pops up is the value we changed in real time.  

In this example, the first behavior is correct. Because at the beginning I chose to visit Xiaojie and clicked OK to issue the command, and then I switched to Xiaoshang, but I didn't click OK, my component should not confuse the person I want to visit.  Here, the implementation of the class component is clearly wrong. 

So why does the class component behave like this in our example?

Let's take a closer look at the method in our class component:showMessage

showMessage = () => {
    alert('你选择了 ' + this.props.user);
  };

This class method reads data from it. Props are immutable in React, so they never change. However, thisis, and always will be, mutable.

In fact, that's what class components are for. React itself changes over time so that you can get the latest instance in render methods as well as lifecycle methods. So if our component re-renders while the request has already been made, that will change.

Our components belong to a specific render with specific props and state.

However, calling a callback function to read the timeout breaks this association. Our callback isn't tied to any particular render, so it loses the correct props.

2. Closure makes class components render with specific props and state

We want to somehow "fix" the link between rendering with the correct props and the callbacks that read those props. They got lost somewhere in the class. 

One way is to read the events before calling them, and pass them explicitly to the timeout callback:

import React from 'react';

class ProfilePage extends React.Component {
  showMessage = (user) => {
    alert('你选择了 ' + user);
  };

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

  render() {
    return <button onClick={this.handleClick}>确定</button>;
  }
}

export default ProfilePage;

This method will work. However, this approach makes the code significantly more verbose and error-prone over time. What if we need more than one prop? What if we also need to access state?

However, if we can take advantage of JavaScript closures then the problem will be solved.

Normally we avoid closures, but in React, props and state are immutable, which eliminates one of the major drawbacks of closures.

This means that if you capture the props or state used by that render in a particular render, you will find that they will always be consistent, just as you expect.

class ProfilePage extends React.Component {

  render() {
    const props = this.props;

    const showMessage = () => {
      alert('你选择了 ' + props.user);
    };
  
    const handleClick = () => {
      setTimeout(showMessage, 3000);
    };
    return <button onClick={handleClick}>确定</button>;
  }
}

You already "capture" props when you render. That way, any code inside it (including ) is guaranteed to get the props used for this particular render. The above example is correct, but it looks weird. What's the point of using a class if you define various functions in the method instead of using the class' method?

So at this time we understand the difference between functional components and class components:

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

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

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

React calls the function again when the parent component is rendered with different props. But our click event handler "belongs" to the previous render with its own value, and the callback function can also read this value. They all remain intact.

 

3. Distinguish between the use of useState and useRef

With Hooks, the same principle applies to state.  Look at this example:

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

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

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

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

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

If I send a specific message, the component should not be confused about which message was actually sent. The variable of this function component captures the one render we execute the click handler in the browser. So when I click "Send", the content in the input box will be set as the popup value at that moment.

So we know that functions in React capture props and state by default. But what if we want to read the latest props and state that don't belong to this particular render? 

In functional components, you can also have a mutable variable that is shared across all render frames of the component. It is called "ref":

function MyComponent() {
  const ref = useRef(null);
  // 你可以通过 ref.current 来获取保存的值.
  // ...
}

In many cases, you don't need them, and allocating them would be a waste. However, if you wish, you can manually track these values ​​like this:

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

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

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

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

If we read in state, we'll get the information at the moment we hit the send button. But when we read by ref, we will get the latest value, even if we keep typing after pressing the send button.

In general, you should avoid reading or setting refs during rendering , since they are mutable. We want to keep rendering predictable. However, manually updating the ref can be a bit annoying if we want the latest value for a particular prop or state. We can automate this by using an effect:

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

  // 保持追踪最新的值。
  const latestMessage = useRef('');
  useEffect(() => {
    latestMessage.current = message;
  });

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

As we saw above, closures actually help us solve subtle problems that are hard to notice. Also, they make it easier to write code that runs correctly in concurrent mode . This works because the logic inside the component captures and includes the correct props and state when rendering it.

React functions always capture their value - now we know why.

Guess you like

Origin blog.csdn.net/qq_49900295/article/details/127022158