ReactJS - O render é chamado sempre que "setState" é chamado?

503

O React renderiza novamente todos os componentes e subcomponentes sempre que setStateé chamado?

Se sim, por quê? Eu pensei que a idéia era que o React renderizasse apenas o necessário - quando o estado mudou.

No exemplo simples a seguir, as duas classes são renderizadas novamente quando o texto é clicado, apesar do estado não ser alterado nos cliques subsequentes, pois o manipulador onClick sempre define stateo mesmo valor:

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

Eu esperava que as renderizações só acontecessem se os statedados tivessem mudado.

Aqui está o código do exemplo, como um JS Fiddle , e um snippet incorporado:

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/
Brad Parks
fonte
Eu tive o mesmo problema, não sei a solução exata. Mas limpei os códigos indesejados do componente em que ele começou a funcionar normalmente.
Jaison
Gostaria de salientar que no seu exemplo - porque como o seu elemento é projetado não depende apenas de um estado único - chamar setState()mesmo com dados fictícios faz com que o elemento seja renderizado de maneira diferente, então eu diria que sim. Absolutamente, ele deve tentar renderizar novamente seu objeto quando algo puder ter mudado, porque, caso contrário, sua demonstração - assumindo que era o comportamento pretendido - não funcionaria!
Tadhg McDonald-Jensen
Você pode estar certo @ TadhgMcDonald-Jensen - mas, pelo que entendi, o React o renderizou pela primeira vez (já que o estado muda de nada para algo da primeira vez) e nunca mais teve que renderizar novamente. Mas eu estava errado, é claro - pois parece que o React exige que você escreva seu próprio shouldComponentUpdatemétodo, que eu assumi que uma versão simples dele já deve estar incluída no próprio React. Parece que a versão padrão incluída no react simplesmente retorna true- o que força o componente a renderizar novamente a cada vez.
Brad Parks
Sim, mas ele só precisa renderizar novamente no DOM virtual; somente o DOM real será alterado se o componente for renderizado de maneira diferente. As atualizações no DOM virtual geralmente são insignificantes (pelo menos em comparação com a modificação de coisas na tela real), portanto, chamar renderização toda vez que for necessário atualizar, visto que nenhuma mudança ocorreu não é muito cara e segura do que supor que ela deva ser a mesma.
Tadhg McDonald-Jensen

Respostas:

570

O React renderiza novamente todos os componentes e subcomponentes sempre que o setState é chamado?

Por padrão - sim.

Existe um método booleano shouldComponentUpdate (objeto nextProps, objeto nextState) , cada componente possui esse método e é responsável por determinar "o componente deve atualizar (executar a função de renderização )?" toda vez que você muda de estado ou passa novos adereços do componente pai.

Você pode escrever sua própria implementação do método shouldComponentUpdate para seu componente, mas a implementação padrão sempre retorna true - ou seja, sempre executa novamente a função de renderização.

Citação de documentos oficiais http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate

Por padrão, shouldComponentUpdate sempre retorna true para evitar erros sutis quando o estado é alterado no local, mas se você for cuidadoso ao sempre tratar o estado como imutável e como somente leitura de props e state em render (), poderá substituir shouldComponentUpdate por uma implementação que compara os adereços antigos e o estado com seus substitutos.

Próxima parte da sua pergunta:

Se sim, por quê? Eu pensei que a idéia era que o React renderizasse apenas o mínimo necessário - quando o estado mudou.

Existem duas etapas do que podemos chamar de "render":

  1. Renderizações do DOM virtual: quando o método de renderização é chamado, ele retorna uma nova estrutura de dom virtual do componente. Como mencionei antes, esse método de renderização é chamado sempre quando você chama setState () , porque shouldComponentUpdate sempre retorna true por padrão. Portanto, por padrão, não há otimização aqui no React.

  2. Renderizações nativas do DOM: o React altera os nós DOM reais no seu navegador apenas se eles foram alterados no Virtual DOM e o mínimo necessário - esse é o ótimo recurso do React que otimiza a mutação do DOM real e torna o React rápido.

Petr
fonte
47
Ótima explicação! E a única coisa que realmente faz o React ainda brilhar nesse caso é que ele não altera o DOM real , a menos que o DOM virtual tenha sido alterado. Portanto, se minha função render retornar a mesma coisa duas vezes seguidas, o DOM real não será alterado ... Obrigado!
Brad Parks
1
Acho que @tungd está certo - veja a resposta abaixo. Também é uma questão de uma relação pai-filho entre componentes. Por exemplo, se TimeInChild for um irmão de Main , seu render () não será chamado desnecessariamente.
Gvlax #
2
Se o seu estado contiver um objeto javascript relativamente grande (~ 1k de propriedades no total), que é renderizado como uma grande árvore de componentes (~ 100 no total) ... você deve permitir que as funções de renderização construam o dom virtual ou antes de definir o estado, compare o novo estado com o antigo manualmente e chame apenas setStatese detectar que há uma diferença? Se sim, como fazer isso da melhor maneira - compare as seqüências de caracteres json, construa e compare hashes de objetos, ...?
Vincent Sels
1
@ Pet, mas mesmo que o react reconstrua o dom virtual, se o antigo dom virtual for o mesmo que o novo dom virtual, o dom do navegador não será tocado, não é?
Jaskey
3
Além disso, verifique o uso de React.PureComponent ( reactjs.org/docs/react-api.html#reactpurecomponent ). Ele somente atualiza (é renderizado novamente) se o estado ou os props do componente foram realmente alterados. Cuidado, porém, que a comparação é superficial.
debated
105

Não, o React não processa tudo quando o estado muda.

  • Sempre que um componente está sujo (seu estado é alterado), esse componente e seus filhos são renderizados novamente. Isso, em certa medida, é re-renderizar o mínimo possível. O único momento em que a renderização não é chamada é quando algum ramo é movido para outra raiz, onde teoricamente não precisamos renderizar novamente. No seu exemplo, TimeInChildé um componente filho de Main, portanto, também é renderizado novamente quando o estado das Mainalterações.

  • React não compara dados de estado. Quando setStateé chamado, ele marca o componente como sujo (o que significa que ele precisa ser renderizado novamente). O importante a ser observado é que, embora o rendermétodo do componente seja chamado, o DOM real é atualizado apenas se a saída for diferente da árvore DOM atual (também conhecida como difração entre a árvore DOM Virtual e a árvore DOM do documento). No seu exemplo, mesmo que os statedados não tenham sido alterados, a hora da última alteração foi alterada, tornando o DOM Virtual diferente do DOM do documento, portanto, por que o HTML é atualizado.

tungd
fonte
Sim, esta é a resposta correta. Como experiência, modifique a última linha como React.renderComponent (<div> <Main /> <TimeInChild /> </div>, document.body) ; e remova <TimeInChild /> do corpo de render () do componente Main . A renderização () de TimeInChild não será chamada por padrão porque não é mais uma filha de Main .
Gvlax
Obrigado, coisas assim são um pouco complicadas, é por isso que os autores do React recomendaram que o render()método fosse "puro" - independente do estado externo.
tungd
3
@ tungd, o que isso significa some branch is moved to another root? Como você chama branch? Como você chama root?
Verde
2
Eu realmente prefiro a resposta marcada como a correta. Eu entendo sua interpretação de 'renderização' no lado nativo, 'real', ... mas se você o observar do lado do reagir-nativo, deve dizer que ele é renderizado novamente. Felizmente, é inteligente o suficiente para determinar o que realmente mudou e apenas atualizar essas coisas. Esta resposta pode ser confuso para novos usuários, primeiro você dizer não, e então você explicar que as coisas se tornado ...
Wira
1
@tungd u pode explicar a pergunta de verdewhat does it mean some branch is moved to another root? What do you call branch? What do you call root?
madhu131313
7

Embora seja declarado em muitas das outras respostas aqui, o componente deve:

  • implementar shouldComponentUpdatepara renderizar apenas quando o estado ou as propriedades mudam

  • alterne para estender um PureComponent , que já implementa um shouldComponentUpdatemétodo internamente para comparações superficiais.

Aqui está um exemplo que usa shouldComponentUpdate, que funciona apenas para esse caso de uso simples e para fins de demonstração. Quando isso é usado, o componente não se reproduz novamente a cada clique e é renderizado quando exibido pela primeira vez e depois de ser clicado uma vez.

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>

Brad Parks
fonte
6

Sim. Ele chama o método render () toda vez que chamamos setState, quando "shouldComponentUpdate" retorna false.

lakmal_sathyajith
fonte
7
Por favor, seja mais elaborative
Karthick Ramesh
3

Outro motivo para "atualização perdida" pode ser o seguinte:

  • Se o getDerivedStateFromProps estático for definido, ele será executado novamente em todos os processos de atualização, de acordo com a documentação oficial https://reactjs.org/docs/react-component.html#updating .
  • portanto, se esse valor de estado vier de adereços no início, ele será substituído em todas as atualizações.

Se for o problema, então U pode evitar definir o estado durante a atualização, verifique o valor do parâmetro state como este

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

Outra solução é adicionar uma propriedade inicializada ao estado e configurá-la na primeira vez (se o estado for inicializado com um valor não nulo).

Zoltán Krizsán
fonte
0

Nem todos os componentes.

o statecomponente in se parece com a fonte da cachoeira do estado de todo o aplicativo.

Portanto, a mudança acontece de onde o setState chamou. A árvore rendersentão é chamada a partir daí. Se você usou componente puro, o renderserá ignorado.

Singhi John
fonte