ReactJS — Вызывается ли рендеринг при вызове «setState»?

#javascript #reactjs

#javascript #reactjs

Вопрос:

React повторно отображает все компоненты и подкомпоненты при каждом вызове setState() ?

Если да, то почему? Я думал, идея заключалась в том, что React отображал ровно столько, сколько было необходимо — при изменении состояния.

В следующем простом примере оба класса снова выполняют рендеринг при щелчке по тексту, несмотря на то, что состояние не меняется при последующих щелчках, поскольку обработчик onClick всегда устанавливает state в одно и то же значение:

 this.setState({'test':'me'});
  

Я бы ожидал, что рендеринг произойдет только в том случае, если state изменились данные.

Вот код примера, в виде скрипки JS, и встроенный фрагмент:

 var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);  
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>  

Комментарии:

1. У меня была такая же проблема, я не знаю точного решения. Но я удалил ненужные коды из компонента, и он начал работать как обычно.

2. Я хотел бы отметить, что в вашем примере — поскольку дизайн вашего элемента зависит не только от уникального состояния — вызов setState() даже с фиктивными данными приводит к тому, что элемент отображается по-разному, поэтому я бы сказал «да». Безусловно, он должен попытаться повторно отобразить ваш объект, когда что-то могло измениться, потому что в противном случае ваша демонстрация — при условии, что это было предполагаемое поведение — не сработала бы!

3. Возможно, вы правы @TadhgMcDonald-Jensen — но, насколько я понимаю, React отрисовал бы его в первый раз (поскольку состояние меняется с ничего на что-то в тот первый раз), тогда больше никогда не приходилось бы отрисовывать. Хотя, конечно, я был неправ — похоже, что React требует от вас написания вашего собственного shouldComponentUpdate метода, который, как я предполагал, простая версия уже должна быть включена в сам React. Похоже, что версия по умолчанию, включенная в react, просто возвращает true , что заставляет компонент повторно отображать каждый раз.

4. Да, но для этого требуется только повторный рендеринг в виртуальном DOM, тогда он изменяет фактический DOM только в том случае, если компонент отображается по-другому. Обновления виртуального DOM обычно незначительны (по крайней мере, по сравнению с изменением параметров на реальном экране), поэтому вызывать render каждый раз, когда может потребоваться обновление, а затем видеть, что никаких изменений не произошло, не очень дорого и безопаснее, чем предполагать, что он должен отображать то же самое.

Ответ №1:

React повторно отображает все компоненты и подкомпоненты при каждом вызове setState?

По умолчанию — да.

Существует метод boolean shouldComponentUpdate (object nextProps, объект nextState), у каждого компонента есть этот метод, и он отвечает за определение «должен ли компонент обновляться (запускать функцию рендеринга)?» каждый раз, когда вы меняете состояние или передаете новые реквизиты из родительского компонента.

Вы можете написать свою собственную реализацию метода shouldComponentUpdate для вашего компонента, но реализация по умолчанию всегда возвращает true, что означает всегда перезапуск функции рендеринга.

Цитата из официальных документов http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate

По умолчанию shouldComponentUpdate всегда возвращает true, чтобы предотвратить незначительные ошибки при изменении состояния на месте, но если вы будете осторожны и всегда будете рассматривать состояние как неизменяемое и использовать только для чтения props и state в render(), то вы можете переопределить shouldComponentUpdate с помощью реализации, которая сравнивает старые props и state с их заменами.

Следующая часть вашего вопроса:

Если да, то почему? Я думал, идея заключалась в том, чтобы React отображал ровно столько, сколько необходимо — при изменении состояния.

Есть два этапа того, что мы можем назвать «рендерингом»:

  1. Рендеринг виртуального DOM: при вызове метода render он возвращает новую структуру компонента virtual dom. Как я упоминал ранее, этот метод рендеринга вызывается всегда при вызове setState(), потому что shouldComponentUpdate по умолчанию всегда возвращает значение true. Итак, по умолчанию в React здесь нет оптимизации.

  2. Рендеринг собственного DOM: React изменяет реальные узлы DOM в вашем браузере, только если они были изменены в виртуальном DOM, и так мало, как это необходимо — это отличная функция React, которая оптимизирует изменение реального DOM и делает React быстрым.

Комментарии:

1. Отличное объяснение! И единственное, что действительно заставляет React по-прежнему сиять в этом случае, это то, что он не изменяет реальный DOM, если не был изменен виртуальный DOM. Итак, если моя функция рендеринга возвращает одно и то же 2 раза подряд, реальный DOM вообще не изменяется… Спасибо!

2. Я думаю, что @tungd прав — смотрите его ответ ниже. Это также вопрос отношения родитель-потомок между компонентами. Например, если TimeInChild является братом Main , то его render() не будет вызываться без необходимости.

3. Если ваше состояние содержит относительно большой объект javascript (~ 1 тыс. общих свойств), который отображается в виде большого дерева компонентов (всего ~ 100)… должны ли вы позволить функциям рендеринга создавать виртуальный dom, или вам следует, перед установкой состояния, сравнить новое состояние со старым вручную и вызывать только setState если вы обнаружите, что есть разница? Если да, то как это лучше всего сделать — сравнить строки json, построить и сравнить хэши объектов, …?

4. @Petr , но даже если react реконструирует виртуальный dom, если старый виртуальный dom совпадает с новым виртуальным dom, dom браузера не будет затронут, не так ли?

5. Кроме того, посмотрите на использование React. PureComponent ( reactjs.org/docs/react-api.html#reactpurecomponent ). Он обновляется (повторно визуализируется) только в том случае, если состояние компонента или реквизиты действительно изменились. Однако имейте в виду, что сравнение неглубокое.

Ответ №2:

Нет, React не отображает все при изменении состояния.

  • Всякий раз, когда компонент загрязнен (его состояние изменено), этот компонент и его дочерние элементы повторно визуализируются. Это, в некоторой степени, заключается в том, чтобы как можно реже повторять рендеринг. Единственный раз, когда рендеринг не вызывается, это когда какая-то ветвь перемещается в другой корень, где теоретически нам не нужно ничего повторно отображать. В вашем примере TimeInChild является дочерним компонентом Main , поэтому он также повторно отображается при Main изменении состояния.

  • React не сравнивает данные о состоянии. При setState вызове компонент помечается как «грязный» (что означает, что его необходимо повторно отобразить). Важно отметить, что, хотя render вызывается метод компонента, реальный DOM обновляется, только если выходные данные отличаются от текущего дерева DOM (иначе говоря, разница между виртуальным деревом DOM и деревом DOM документа). В вашем примере, даже если state данные не изменились, изменилось время последнего изменения, что делает виртуальный DOM отличным от DOM документа, следовательно, почему обновляется HTML.

Комментарии:

1. Да, это правильный ответ. В качестве эксперимента измените последнюю строку как React.renderComponent(<div><Main/><TimeInChild/></div>, document.body) ; и удалите <TimeInChild/> из тела render() основного компонента. Render() TimeInChild по умолчанию вызываться не будет, поскольку он больше не является дочерним элементом Main .

2. Спасибо, подобные вещи немного сложны, вот почему авторы React рекомендовали, чтобы render() метод был «чистым» — независимым от внешнего состояния.

3. @tungd, что это значит some branch is moved to another root ? Что вы называете branch ? Что вы называете root ?

4. Я действительно предпочитаю ответ, помеченный как правильный. Я понимаю вашу интерпретацию «рендеринга» на родной, «реальной» стороне… но если вы посмотрите на это со стороны react-native, вы должны сказать, что он снова рендерится. К счастью, он достаточно умен, чтобы определить, что действительно изменилось, и обновлять только эти вещи. Этот ответ может сбить с толку новых пользователей, сначала вы говорите «нет», а затем объясняете, что вещи действительно отображаются…

5. @tungd можете ли вы, пожалуйста, объяснить вопрос Грина what does it mean some branch is moved to another root? What do you call branch? What do you call root?

Ответ №3:

ДА. Он вызывает render() метод каждый раз, когда мы вызываем setState только, за исключением случаев, когда shouldComponentUpdate возвращается false .

Комментарии:

1. Пожалуйста, будьте более подробными

Ответ №4:

Несмотря на то, что это указано во многих других ответах здесь, компонент должен либо:

  • реализовать shouldComponentUpdate рендеринг только при изменении состояния или свойств

  • переключитесь на расширение PureComponent, который уже реализует shouldComponentUpdate метод внутри для неглубоких сравнений.

Вот пример, в котором используется shouldComponentUpdate , который работает только для этого простого варианта использования и демонстрационных целей. Когда это используется, компонент больше не повторно отображает себя при каждом нажатии, а отображается при первом отображении и после того, как на него щелкнули один раз.

 var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    shouldComponentUpdate: function(nextProps, nextState) {
      if (this.state == null)
        return true;
  
      if (this.state.test == nextState.test)
        return false;
        
      return true;
  },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);  
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>  

Ответ №5:

Похоже, что принятые ответы больше не применяются при использовании перехватчиков React (с примитивными значениями, подробности см. в комментариях к этому ответу). Вы можете видеть в этой изолированной среде кода, что компонент класса повторно отображается, когда состояние устанавливается на то же значение, в то время как в функциональном компоненте установка состояния на то же значение не вызывает повторного отображения.

https://codesandbox.io/s/still-wave-wouk2?file=/src/App.js

Комментарии:

1. Это потому, что в вашем примере вы передаете примитивное значение перехватчику useState, а сложный объект — методу setState. Попробуйте передать объект или массив в перехватчик useState, чтобы увидеть повторный рендеринг в функциональном компоненте, потому что вы меняете ссылки на объекты. Просто чтобы прояснить ваш пример, но вы правы в том, что использование useState hook с примитивными значениями может предотвратить повторный рендеринг.

2. @MichalSvorc Это правда, что если бы мы использовали объект в useState, то он также выполнял бы повторный рендеринг, но дело в том, что невозможно использовать простой примитив с setState функцией компонента класса. Пример только показывает разницу между использованием простейшей формы состояния компонента класса и простейшей формы useState, и насколько версия hooks (совсем немного) более производительна 👍

3. конечно, я только что добавил объяснение того, почему useState не выполняется повторный рендеринг в вашем примере. Кто-то может передать объект в useState (что является законным), думая, что перехват автоматически предотвратит повторный рендеринг, потому что вы заявили, что «принятые ответы больше не применяются при использовании перехватов React». Кто-то может не сразу заметить разницу в использовании примитива и ссылки на объект.

4. Это имеет смысл, спасибо. Я добавил небольшое уточнение к ответу

Ответ №6:

React 18 и выше

Начиная с React 18, все обновления состояния отправляются автоматически пакетно. Таким образом, React группирует несколько обновлений состояния в один повторный рендеринг для повышения производительности.

Поэтому, когда вы обновляете свое состояние, React всегда пытается выполнить пакетные обновления в групповом обновлении, вызывая меньше рендеринг чем setState вызовов. Поведение такое же при использовании перехватов.

Вы можете прочитать очень длинное объяснение в автоматической дозации для отреагировать 18 объявлении.

Реагируйте 17 и ниже

В React 17 и ниже пакетами обрабатываются только обновления внутри обработчиков событий React. Обновления, запускаемые из promises, setTimeout, собственных обработчиков событий или других событий, по умолчанию не пакетируются в React.

Ответ №7:

Другой причиной «потерянного обновления» может быть следующее:

  • Если определен статический getDerivedStateFromProps, то он перезапускается в каждом процессе обновления в соответствии с официальной документацией https://reactjs.org/docs/react-component.html#updating .
  • итак, если это значение состояния исходит из реквизитов в начале, оно перезаписывается при каждом обновлении.

Если это проблема, то вы можете избежать установки состояния во время обновления, вам следует проверить значение параметра состояния следующим образом

 static getDerivedStateFromProps(props: TimeCorrectionProps, state: TimeCorrectionState): TimeCorrectionState {
   return state ? state : {disable: false, timeCorrection: props.timeCorrection};
}
  

Другое решение — добавить инициализированное свойство в state и настроить его в первый раз (если состояние инициализировано ненулевым значением).)

Ответ №8:

Не все компоненты.

state компонент in выглядит как источник водопада состояний всего приложения.

Итак, изменение происходит с того места, где вызывается setState. Дерево renders затем вызывается оттуда. Если вы использовали чистый компонент, render будет пропущен.

Ответ №9:

Независимо от хорошо объясненных ответов здесь, могут быть другие причины, по которым вы не видите ожидаемого изменения после изменения реквизита или состояния:

Обратите внимание на любое event.preventDefault(); место в коде, где вы хотите выполнить повторный рендеринг с помощью изменения состояния реквизита, поскольку это отменит любое событие, которое можно отменить после этого утверждения.

Ответ №10:

Вы могли бы использовать setState () только после сравнения текущего значения состояния и нового, и они отличаются.

Комментарии:

1. Вы используете setState() для того, чтобы обновить локальное состояние (переменные внутри состояния) компонента. Используйте setState() только в том случае, если переменная из state была изменена и будет иметь смысл, чтобы компонент снова выполнял рендеринг. Вы можете использовать useRef(), если вы не хотите повторно отображать компонент, или вы также можете повторно отобразить компонент с помощью некоторой переменной и отправить какое-либо действие, чтобы принудительно отобразить компонент снова. В некоторых случаях рекомендуется использовать только let или const для управления переменными. Пожалуйста, примите также во внимание useMemo, чтобы избежать повторного рендеринга компонентов, если это не требуется.

2. Под вашим вопросом есть кнопка редактирования, которая позволяет вам добавить детали к вашему ответу после публикации