Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
997 views
in Technique[技术] by (71.8m points)

reactjs - this.props.children not re-rendered on parent state change

I have a piece of code

import React, {Component} from 'react';

class App extends Component {
  render() {
    return (
      <Container>
        <Child/>
      </Container>
    )
  }
}

class Container extends Component {
  render() {
    console.log('Container render');
    return (
      <div onClick={() => this.setState({})}>
        {this.props.children}
      </div>
    )
  }
}

class Child extends Component {
  render() {
    console.log('Child render');
    return <h1>Hi</h1>
  }
}

export default App;

When clicking on 'Hi' msg, only Container component keeps re-rendering but Child component is not re-rendered.

Why is Child component not re-rendered on Container state change?

I would reason, that it doesn't happen due to it being a property of Container component, but still this.props.child is evaluated to a Child component in JSX, so not sure.

<div onClick={() => this.setState({})}>
  {this.props.children}
</div>

Full example https://codesandbox.io/s/529lq0rv2n (check console log)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The question is quite old, but since you didn't seem to get a satisfying answer I'll give it a shot too.

As you have observed by yourself, changing

// Scenario A

<div onClick={() => this.setState({})}>
  {this.props.children}
</div>

to

// Scenario B

<div onClick={() => this.setState({})}>
  <Child />
</div>

will in fact, end up with

Container render
Child render

in the console, every time you click.

Now, to quote you

As fas as I understand, if setState() is triggered, render function of Container component is called and all child elements should be re-rendered.

You seemed to be very close to understanding what is happening here.

So far, you are correct, since the Container's render is executed, so must the components returned from it call their own render methods.

Now, as you also said, correctly,

<Child />

// is equal to

React.createElement(Child, {/*props*/}, /*children*/)

In essence, what you get from the above is just an object describing what to show on the screen, a React Element.

The key here is to understand when the React.createElement(Child, {/*props*/}, /*children*/) execution happened, in each of the scenarios above.

So let's see what is happening:

class App extends Component {
  render() {
    return (
      <Container>
        <Child/>
      </Container>
    )
  }
}

class Container extends Component {
  render() {
    console.log('Container render');
    return (
      <div onClick={() => this.setState({})}>
        {this.props.children}
      </div>
    )
  }
}

class Child extends Component {
  render() {
    console.log('Child render');
    return <h1>Hi</h1>
  }
}

You can rewrite the return value of App like this:

<Container>
  <Child/>
</Container>

// is equal to

React.createElement(
  Container, 
  {}, 
  React.createElement(
    Child, 
    {}, 
    {}
  )
)

// which is equal to a React Element object, something like

{
  type: Container,
  props: {
    children: {
      type: Child,    // |
      props: {},      // +---> Take note of this object here
      children: {}    // |
    }
  }
}

And you can also rewrite the return value of Container like this:

<div onClick={() => this.setState({})}>
  {this.props.children}
</div>

// is equal to

React.createElement(
  'div', 
  {onClick: () => this.setState({})}, 
  this.props.children
)

// which is equal to React Element

{
  type: 'div',
  props: {
    children: this.props.children
  }
}

Now, this.props.children is the same thing as the one included in the App's returned React Element:

{
  type: Child,
  props: {},
  children: {}
}

And to be exact, these two things are referentially the same, meaning it's the exact same thing in memory, in both cases.

Now, no matter how many times Container get's re-rendered, since its children are always referentially the same thing between renders (because that React Element was created in the App level and it has no reason to change), they don't get re-rendered.

In short, React doesn't bother to render a React Element again if it is referentially (===) equal to what it was in the previous render.

Now, if you were to change the Container you would have:

<div onClick={() => this.setState({})}>
  <Child />
</div>

// is equal to

React.createElement(
  'div', 
  {onClick: () => this.setState({})}, 
  React.createElement(
    Child, 
    {}, 
    {}
  ) 
)

// which is equal to

{
  type: 'div',
  props: {
    children: {
      type: Child,
      props: {},
      children: {}
    }
  }
}

However in this case, if you were to re-render Container, it will have to re-execute

React.createElement(
  Child, 
  {}, 
  {}
)

for every render. This will result in React Elements that are referentially different between renders, so React will actually re-render the Child component as well, even though the end result will be the same.

Reference


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...