Реакция: всегда ли дочерние элементы перерисовываются, когда родительский компонент перерисовывает?

Насколько мне известно, если родительский компонент выполняет повторный рендеринг, то все его дочерние элементы будут повторно выполнять рендеринг, ЕСЛИ они не реализуют shouldComponentUpdate(). Я привел пример, где это не похоже на правду.

У меня есть 3 компонента: <DynamicParent/>, <StaticParent/> и <Child/>. Компоненты <Parent/> отвечают за рендеринг <Child/>, но делают это по-разному.

Функция рендеринга <StaticParent/> статически объявляет <Child/> перед исполнением, например:

 <StaticParent>
    <Child />
 </StaticParent>

В то время как <DynamicParent/> обрабатывает получение и рендеринг <Child/> динамически во время выполнения, например:

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

И <DynamicParent/>, и <StaticParent/> имеют onClick слушателей, которые изменяют свое состояние и выполняют повторную визуализацию при нажатии. Я заметил, что при нажатии <StaticParent/> и он, и <Child/> перерисовываются. Но когда я нажимаю <DynamicParent/>, повторно отображаются только родительский объект и НЕ <Child/>.

<Child/> - это функциональный компонент без shouldComponentUpdate(), поэтому я не понимаю, почему он не перерисовывается. Может кто-нибудь объяснить, почему это так? Я не могу найти в документации ничего, связанного с этим вариантом использования.


person Gabriel West    schedule 26.04.2018    source источник
comment
показать больше вашего кода?   -  person Leland Barton    schedule 27.04.2018
comment
Его в ссылке   -  person Gabriel West    schedule 27.04.2018


Ответы (4)


Я отправлю ваш фактический код для контекста:

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

Когда вы пишете этот код в рендере приложения:

<StaticParent />

Рендеринг выглядит так:

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

А на самом деле происходит (примерно) следующее:

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

React.createElement(StaticParent, null);

Когда вы визуализируете свой DynamicParent следующим образом:

<DynamicParent>
    <Child />
</DynamicParent>

Вот что происходит на самом деле (опять же, грубо говоря)

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

И в обоих случаях это Ребенок:

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

Что это значит? Что ж, в вашем компоненте StaticParent вы вызываете React.createElement(Child, null) каждый раз, когда вызывается метод рендеринга StaticParent. В случае DynamicParent дочерний элемент создается один раз и передается как опора. А поскольку React.createElement - это чистая функция, то, вероятно, она где-то запомнена для повышения производительности.

Что заставило бы отрисовку Child снова запускаться в случае DynamicParent, так это изменение свойств Child. Если, например, состояние родительского элемента использовалось в качестве опоры для дочернего элемента, это вызвало бы повторный рендеринг в обоих случаях.

Я действительно надеюсь, что Дэн Абрамов не появится в комментариях, чтобы отбросить этот ответ, это было больно писать (но интересно)

person SrThompson    schedule 27.04.2018

Это в основном из-за того, что у вас есть 2 разных «ребенка».

  • this.props.children
  • <Child/>

Это не одно и то же, первый - это prop, переданный от Application -> DynamicParent, а второй - это Component, отображаемый в StaticParent, у них есть отдельные циклы рендеринга / жизненного цикла.

Ваш включенный пример

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 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>
    );
  }
}
person Xlee    schedule 27.04.2018

В качестве комментария к ответу SrThompsons: «Что заставило бы отрисовку Child снова запускаться в случае DynamicParent, так это изменение свойств Child. Если состояние родителя использовалось как опора для Child, например, это вызвало бы повторную визуализацию в в обоих случаях ". Таким образом, реквизиты или нет реквизиты, переданные дочернему компоненту, как бы это ни выглядело, вызовут повторную визуализацию, если родительский повторный рендеринг (поэтому используйте React.memo для дочернего компонента без реквизита :))

«Реализуете ли вы свой компонент как компонент класса, расширяющий React.Component, или как функциональный компонент, функция рендеринга вызывается снова всякий раз, когда родительский контейнер снова рендерится». Пожалуйста, прочтите здесь для получения дополнительной информации, потому что это отличная статья. https://medium.com/free-code-camp/yeah-hooks-are-good-but-have-you-tried-faster-react-components-e698a8db468c "

person user1665355    schedule 16.01.2020

Он будет повторно отображать только те компоненты, в которых были изменения. Если в дочернем компоненте ничего не изменилось, он не будет повторно отрисован.

person AJ Genung    schedule 26.04.2018
comment
Если вы посмотрите на мой пример в ‹StaticParent /›, то в ‹Child /› не было никаких изменений, но он все еще рендерился. - person Gabriel West; 27.04.2018