web-dev-qa-db-fra.com

Réagissez: les enfants se rendent-ils toujours lorsque le composant parent se rend de nouveau?

Il est à ma connaissance que si un composant parent se rend de nouveau, alors tous ses enfants se rendront à moins qu'ils implémentent shouldComponentUpdate(). I fait un exemple où cela ne semble pas être vrai.

J'ai 3 composants: <DynamicParent/>, <StaticParent/> Et <Child/>. Les composants <Parent/> Sont responsables du rendu du <Child/> Mais le font de différentes manières.

La fonction de rendu de <StaticParent/> Déclare statiquement le <Child/> Avant l'exécution, comme ceci:

 <StaticParent>
    <Child />
 </StaticParent>

Alors que <DynamicParent/> Gère la réception et le rendu dynamiques du <Child/> Au moment de l'exécution, comme ceci:

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

<DynamicParent/> Et <StaticParent/> Ont tous les deux onClick écouteurs pour modifier leur état et leur rendu lorsqu'ils sont cliqués. J'ai remarqué qu'en cliquant sur <StaticParent/> Et le <Child/> Sont à nouveau rendus. Mais lorsque je clique sur <DynamicParent/>, Seuls les parents et NON <Child/> Sont restitués.

<Child/> Est un composant fonctionnel sans shouldComponentUpdate() donc je ne comprends pas pourquoi il ne se rend pas. Quelqu'un peut-il expliquer pourquoi cela doit être le cas? Je ne trouve rien dans les documents liés à ce cas d'utilisation.

15
Gabriel West

Je posterai votre code réel pour le contexte:

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>;
}

Lorsque vous écrivez ce code dans le rendu de votre application:

<StaticParent />

Ce qui est rendu est le suivant:

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

Et en réalité, ce qui se passe (à peu près) est le suivant:

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

React.createElement(StaticParent, null);

Lorsque vous rendez votre DynamicParent comme ceci:

<DynamicParent>
    <Child />
</DynamicParent>

C'est ce qui se passe réellement (encore une fois, grosso modo)

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) },
);

Et voici l'enfant dans les deux cas:

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

Qu'est-ce que ça veut dire? Eh bien, dans votre composant StaticParent, vous appelez React.createElement(Child, null) chaque fois que la méthode de rendu de StaticParent est appelée. Dans le cas DynamicParent, l'enfant est créé une fois et transmis comme accessoire. Et puisque React.createElement Est une fonction pure, il est probablement mémorisé quelque part pour les performances.

Ce qui rendrait le rendu de l'enfant exécuté à nouveau dans le cas DynamicParent est un changement dans les accessoires de l'enfant. Si l'état du parent était utilisé comme accessoire pour l'enfant, par exemple, cela déclencherait un nouveau rendu dans les deux cas.

J'espère vraiment que Dan Abramov ne se présente pas dans les commentaires pour jeter cette réponse, c'était pénible à écrire (mais amusant)

14
SrThompson

C'est principalement parce que vous avez 2 "enfants" différents.

  • this.props.children
  • <Child/>

Ce n'est pas la même chose, le premier est un prop transmis de Application -> DynamicParent, tandis que le second est un Component rendu dans StaticParent, ils ont des cycles de rendu/de vie distincts.

Votre exemple inclus

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>
    );
  }
}

Est littéralement le même que:

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>
    );
  }
}
4
Xlee