ReactJS - Рендерится ли вызывается каждый раз, когда вызывается setState?

503

React рендерит все компоненты и подкомпоненты каждый раз, когда setStateвызывается?

Если так, то почему? Я думал, что идея была в том, что React рендерится так мало, как нужно - когда состояние меняется.

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

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

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

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

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]: http://jsfiddle.net/fp2tncmb/2/
Брэд Паркс
источник
У меня была такая же проблема, я не знаю точное решение. Но я очистил ненужные коды от компонента, который начал работать как обычно.
Джейсон
Я хотел бы указать на то, что в вашем примере - потому что то, как устроен ваш элемент, не зависит исключительно от уникального состояния - вызов setState()даже с фиктивными данными заставляет элемент отображаться по-другому, поэтому я бы сказал да. Абсолютно следует попытаться перерисовать ваш объект, когда что-то могло измениться, потому что в противном случае ваша демонстрация - при условии, что это было предполагаемое поведение - не сработала бы!
Tadhg McDonald-Jensen
Вы можете быть правы @ TadhgMcDonald-Jensen - но, насколько я понимаю, React визуализировал бы его в первый раз (поскольку состояние меняется с нуля на что-то в первый раз), а затем никогда не выполнял рендеринг снова. Конечно, я был неправ - поскольку похоже, что React требует, чтобы вы написали свой собственный shouldComponentUpdateметод, который, как я предполагал, должна быть уже включена в простую версию React. Похоже, версия по умолчанию, включенная в реагирование, просто возвращает, trueчто заставляет компонент каждый раз перерисовываться.
Брэд Паркс
Да, но он должен только повторно выполнить рендеринг в виртуальной DOM, тогда он только изменит фактическую DOM, если компонент визуализируется по-другому. Обновления виртуальной DOM, как правило, незначительны (по крайней мере, по сравнению с модификациями на реальном экране), поэтому вызов метода render каждый раз, когда может потребоваться обновление, а затем обнаружение того, что никаких изменений не произошло, не очень дорого и безопаснее, чем предполагать, что они должны отображаться так же.
Tadhg McDonald-Jensen

Ответы:

570

React рендерит все компоненты и подкомпоненты каждый раз, когда вызывается setState?

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

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

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

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

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

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

Если так, то почему? Я думал, что идея состояла в том, что React рендерится так мало, как нужно - когда состояние меняется.

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

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

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

Petr
источник
47
Отличное объяснение! И одна вещь, которая действительно заставляет React по-прежнему сиять в этом случае, это то, что он не меняет реальный DOM, если не был изменен виртуальный DOM. Так что, если моя функция рендеринга возвращает одно и то же 2 раза подряд, реальный DOM не меняется вообще ... Спасибо!
Брэд Паркс
1
Я думаю, что @tungd прав - смотрите его ответ ниже. Это также вопрос родительско-дочерних отношений между компонентами. Например, если TimeInChild является родственным элементом Main, его render () не будет вызываться без необходимости.
Gvlax
2
Если ваше состояние содержит относительно большой объект javascript (всего ~ 1k свойств), который отображается как большое дерево компонентов (всего ~ 100) ... следует ли функциям рендеринга создавать виртуальный dom, или вы должны, перед установкой состояние, сравнить новое состояние со старым вручную и вызывать, только setStateесли вы обнаружите разницу? Если так, как это сделать лучше всего - сравнить строки json, построить и сравнить хеши объектов, ...?
Винсент Селс
1
@Petr, но даже несмотря на то, что реагирует на реконструкцию виртуального дома, если старый виртуальный дом совпадает с новым виртуальным домом, домен браузера не будет затронут, не так ли?
Яски
3
Также обратите внимание на использование React.PureComponent ( actjs.org/docs/react-api.html#reactpurecomponent ). Он обновляется (перерисовывается) только в том случае, если состояние или реквизиты компонента действительно изменились. Остерегайтесь, однако, что сравнение поверхностно.
спорщик
105

Нет, React не рендерит все, когда состояние меняется.

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

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

tungd
источник
Да, это правильный ответ. В качестве эксперимента измените последнюю строку как React.renderComponent (<div> <Main /> <TimeInChild /> </ div>, document.body) ; и удалить <TimeInChild /> из тела визуализации () из главного компонента. Визуализации () из TimeInChild не будет вызываться по умолчанию , потому что это не ребенок Main больше.
Gvlax
Спасибо, что-то вроде этого немного сложнее, поэтому авторы React рекомендовали, чтобы render()метод был «чистым» - независимым от внешнего состояния.
июня
3
@tungd, что это значит some branch is moved to another root? Что ты называешь branch? Что ты называешь root?
Зеленый
2
Я действительно предпочитаю ответ, помеченный как правильный. Я понимаю вашу интерпретацию «рендеринга» на нативной, «реальной» стороне ... но если вы посмотрите на это с реакционно-нативной стороны, вы должны сказать, что это рендеринг снова. К счастью, он достаточно умен, чтобы определить, что действительно изменилось, и только обновить эти вещи. Этот ответ может вводить в заблуждение новых пользователей, сначала вы говорите «нет», а затем объясняете, что все оказывается оказанным ...
WiRa
1
@tungd Можете ли вы объяснить вопрос Гринаwhat does it mean some branch is moved to another root? What do you call branch? What do you call root?
madhu131313
7

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

  • реализовать 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>

Брэд Паркс
источник
6

Да. Он вызывает метод render () каждый раз, когда мы вызываем setState, а когда «shouldComponentUpdate» возвращает false.

lakmal_sathyajith
источник
7
Пожалуйста, будьте более
разборчивы
3

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

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

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

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

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

Золтан Крижсан
источник
0

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

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

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

Сингхи Джон
источник