简体   繁体   中英

React: Do children always rerender when the parent component rerenders?

It is to my knowledge that if a parent component rerenders, then all its children will rerender UNLESS they implement shouldComponentUpdate() . I made an example where this doesn't seem to be the true.

I have 3 components: <DynamicParent/> , <StaticParent/> and <Child/> . The <Parent/> components are responsible for rendering the <Child/> but do so in different ways.

<StaticParent/> 's render function statically declares the <Child/> before runtime, like so:

 <StaticParent>
    <Child />
 </StaticParent>

While the <DynamicParent/> handles receiving and rendering the <Child/> dynamically at runtime, like so:

 <DynamicParent>
    { this.props.children }
 </DynamicParent>

Both <DynamicParent/> and <StaticParent/> have onClick listeners to change their state and rerender when clicked. I noticed that when clicking <StaticParent/> both it and the <Child/> are rerendered. But when I click <DynamicParent/> , then only the parent and NOT <Child/> are rerendered.

<Child/> is a functional component without shouldComponentUpdate() so I don't understand why it doesn't rerender. Can someone explain why this is to be the case? I can't find anything in the docs related to this use case.

I'll post your actual code for context:

class Application extends React.Component {
  render() {
    return (
      <div>
        {/* 
          Clicking this component only logs 
          the parents render function 
        */}
        <DynamicParent>
          <Child />
        </DynamicParent>

        {/* 
          Clicking this component logs both the 
          parents and child render functions 
        */}
        <StaticParent />
      </div>
    );
  }
}

class DynamicParent extends React.Component {
  state = { x: false };
  render() {
    console.log("DynamicParent");
    return (
      <div onClick={() => this.setState({ x: !this.state.x })}>
        {this.props.children}
      </div>
    );
  }
}

class StaticParent extends React.Component {
  state = { x: false };
  render() {
    console.log("StaticParent");
    return (
      <div onClick={() => this.setState({ x: !this.state.x })}>
        <Child />
      </div>
    );
  }
}

function Child(props) {
  console.log("child");
  return <div>Child Texts</div>;
}

When you write this code in your Application render:

<StaticParent />

What's rendered is this:

 <div onClick={() => this.setState({ x: !this.state.x })}>
    <Child />
 </div>

And in reality, what happens (roughly) is this:

function StaticParent(props) {
  return React.createElement(
    "div",
    { onClick: () => this.setState({ x: !this.state.x }) },
    React.createElement(Child, null)
  );
}

React.createElement(StaticParent, null);

When you render your DynamicParent like this:

<DynamicParent>
    <Child />
</DynamicParent>

This is what actually happens (again, roughly speaking)

function DynamicParent(props) {
    return React.createElement(
        "div",
        { 
            onClick: () => this.setState({ x: !this.state.x }), 
            children: props.children 
        }
    );
}

React.createElement(
      DynamicParent,
      { children: React.createElement(Child, null) },
);

And this is the Child in both cases:

function Child(props) {
    return React.createElement("div", props, "Child Text");
}

What does this mean? Well, in your StaticParent component you're calling React.createElement(Child, null) every time the render method of StaticParent is called. In the DynamicParent case, the Child gets created once and passed as a prop. And since React.createElement is a pure function, then it's probably memoized somewhere for performance.

What would make Child's render run again in the DynamicParent case is a change in Child's props. If the parent's state was used as a prop to the Child, for example, that would trigger a re-render in both cases.

I really hope Dan Abramov doesn't show up on the comments to trash this answer, it was a pain to write (but entertaining)

It's mainly cause of you have 2 different "children".

  • this.props.children
  • <Child/>

They're not the same thing, first one is a prop passed down from Application -> DynamicParent , while the second one is a Component rendered in StaticParent , they have separate rendering/life cycles.

Your included example

class Application extends React.Component {
  render() {
    return (
      <div>
        {/* 
          Clicking this component only logs 
          the parents render function 
        */}
        <DynamicParent>
          <Child />
        </DynamicParent>

        {/* 
          Clicking this component logs both the 
          parents and child render functions 
        */}
        <StaticParent />
      </div>
    );
  }
}

Is literally the same as:

class Application extends React.Component {
  render() {
    // If you want <Child/> to re-render here
    // you need to `setState` for this Application component.
    const childEl = <Child />;
    return (
      <div>
        {/* 
          Clicking this component only logs 
          the parents render function 
        */}
        <DynamicParent>
          {childEl}
        </DynamicParent>

        {/* 
          Clicking this component logs both the 
          parents and child render functions 
        */}
        <StaticParent />
      </div>
    );
  }
}

As a comment to SrThompsons answer: "What would make Child's render run again in the DynamicParent case is a change in Child's props. If the parent's state was used as a prop to the Child, for example, that would trigger a re-render in both cases." So props or not props passed to child component however it may look will cause a rerender if parent rerenders (so use React.memo for a child without props :) )

"Whether you're implementing your component as a class component that extends React.Component, or as a functional component, the render function is called again whenever the parent container renders again." Please read here for more info, because great article. https://medium.com/free-code-camp/yeah-hooks-are-good-but-have-you-tried-faster-react-components-e698a8db468c "

It will only re-render components that have had a change. If nothing on the child component has changed, it will not be re-rendered.

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