简体   繁体   中英

Re-rendering in React functional component - why this behavior?

This question concerns the definition of callback functions inside functional components, with the callback functions being passed down as props to child functional components. I am aware that when defining functions inside functional components and those functions are being passed on as props to child components we should use useCallback . In this example I am deliberately not using it.

I have a dummy React app as below:

App.jsx

import { useState, useCallback } from 'react';
import './App.css';
import TestComponent from './components/TestComponent';
import TestComponent2 from './components/TestComponent2';

function App() {

    const [message, setMessage] = useState("");
    console.log('Defining myArrowfunc');
    const myArrowFunc = () => console.log('Hello!');
    console.log('myArrowfunc defined');

    return (
        <>
            <TestComponent message={message} myArrowFunc={myArrowFunc} />
            <TestComponent2 myArrowFunc={myArrowFunc} />
            <input
                type="text"
                value={message}
                onChange={e => setMessage(e.target.value)}
            />
            <button
                type="button"
                onClick={() => {
                    console.log('Setting message');
                    setMessage("x");
            }}>Set to x</button>
        </>
    );
}

export default App;

TestComponent.jsx

import { useEffect } from 'react';

function TestComponent(props) {
    
    useEffect(() => {
        console.log(`TestComponent.useEffect, props.message = ${props.message}`);
    }, [props]);
    
    return <h2>TestComponent - {props.message}</h2>;
}

export default TestComponent;

TestComponent2.jsx

import { useEffect } from 'react';

function TestComponent2(props) {
    
    useEffect(() => {
        console.log(`TestComponent2.useEffect`);
    }, [props.myArrowFunc]);
    
    return <h2>TestComponent2 - Placeholder</h2>;
}

export default TestComponent2;

Starting the app, the page loads and we are here:

初始应用加载

In the console window everything looks like expected. The console.log statements Defining myArrowFunc and myArrowFunc defined ran, and we can see the console.log statements from the useEffect hook within TestComponent and TestComponent2 which ran after they were rendered.

Now we click on the button Set to x , which invokes the callback function attached to the onClick event of the button. This callback calls setMessage("x") which updates the state of the App component and consequently triggers a re-render of the component.

In the console window we can see that the App functional component ran (eg from the console.log("Defining myArrowFunc) ) and one can also see that the useEffect hooks of the child components ran after they were re-rendered.

在此处输入图像描述

Now, that TestComponent re-rendered is of course understandable. message state is a prop on TestComponent which would cause a re-render of TestComponent . Also, in the dependency array of TestComponent is specified props (not props.message ), but props is a new object on every render (right?) and hence the reference comparison tells us props has changed and so useEffect runs.

That the useEffect hook of TestComponent2 ran can be understood (right?) from the fact that myArrowFunc is re-defined on each render of App , and so the prop passed to TestComponent2 has in fact changed (reference comparison).

Here comes the part where I become confused: the message state in App now holds "x", and so additional clicks to the button will not change the state, and so should not trigger a re-render of App (and any child components dependant). When I click the button the following output happens:

在此处输入图像描述

The statements console.log('Defining myArrowfunc'); and console.log('myArrowfunc defined'); ran. What does this mean? Did the component re-render? If the App function ran, then it should have defined a new myArrowFunc (right?), and since TestComponent2 takes that as a prop, it should have re-rendered and run its useEffect hook?

What's interesting as well is that when I again click the button, it does not look like App ran, the output becomes only "Setting message".

Would very much appreciate an outline / explanation of what exactly is going on here.

在此处输入图像描述

When you click the button, a console log with setting message run, and then a setState with will make a re-render.

At the following click, react is clever enough to see that the state you are setting is the same as before so it doesn't re-render

If you change then the value and click the button, it will re-render, but not for the following cases with the same value.

To sum up, the firsts console.log won't run if there's not a re-render, and the procedure of react and if default memoizing will prevent it

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM