Como um componente conectado redux sabe quando renderizar novamente?

86

Provavelmente estou perdendo algo muito óbvio e gostaria de me esclarecer.

Aqui está o meu entendimento.
Em um componente de reação ingênuo, temos states& props. Atualizar statecom setStaterenderiza novamente todo o componente. propssão geralmente somente leitura e atualizá-los não faz sentido.

Em um componente react que se inscreve em um redux store, por meio de algo parecido store.subscribe(render), ele obviamente se refaz a cada vez que o store é atualizado.

react-redux tem um helper connect()que injeta parte da árvore de estado (que é de interesse para o componente) e actionCreators quanto propsao componente, geralmente por meio de algo como

const TodoListComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

Mas, com o entendimento de que um setStateé essencial para a TodoListComponentreagir à mudança árvore do estado redux (re-rendem), eu não consigo encontrar nenhuma stateou setStatecódigo relacionado no TodoListarquivo de componente. É mais ou menos assim:

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

Alguém pode me apontar na direção certa sobre o que estou perdendo?

PS Estou seguindo o exemplo de lista de tarefas que vem junto com o pacote redux .

Joe Lewis
fonte

Respostas:

109

A connectfunção gera um componente wrapper que assina a loja. Quando uma ação é despachada, o retorno de chamada do componente do wrapper é notificado. Em seguida, ele executa sua mapStatefunção e compara superficialmente o objeto de resultado desta vez com o objeto de resultado da última vez (portanto, se você reescrever um campo de armazenamento redux com o mesmo valor, ele não acionará uma nova renderização). Se os resultados forem diferentes, ele os passa para o seu componente "real" como adereços.

Dan Abramov escreveu uma ótima versão simplificada de connectat ( connect.js ) que ilustra a ideia básica, embora não mostre nenhum trabalho de otimização. Também tenho links para vários artigos sobre o desempenho do Redux que discutem algumas idéias relacionadas.

atualizar

O React-Redux v6.0.0 fez algumas mudanças internas importantes em como os componentes conectados recebem seus dados da loja.

Como parte disso, escrevi um post que explica como a connectAPI e seus componentes internos funcionam e como eles mudaram com o tempo:

Redux idiomático: a história e a implementação do React-Redux

marcadorikson
fonte
Obrigado! Você é a mesma pessoa que me ajudou outro dia @ HN - news.ycombinator.com/item?id=12307621 com links úteis? Prazer em conhecê-lo :)
Joe Lewis
4
Sim, esse sou eu :) I passar maneira muito tempo olhando para discussões sobre Redux online - Reddit, HN, SO, Médio, e vários outros lugares. Feliz em ajudar! (Também para sua informação, eu recomendo fortemente os canais de bate-papo Reactiflux no Discord. Muitas pessoas saindo por aí e respondendo a perguntas. Normalmente estou online lá à noite US EST. O link do convite está em reactiflux.com .)
markerikson
Supondo que isso respondeu à pergunta, se importa em aceitar a resposta? :)
markerikson
É comparar os próprios objetos ou as propriedades daqueles antes / depois dos objetos? Se for o último, verifica apenas o primeiro nível. É isso que você quer dizer com "raso?"
Andy
Sim, um "check igualdade rasa" meios para comparar os primeiros campos de nível de cada objeto: prev.a === current.a && prev.b === current.b && ...... Isso pressupõe que quaisquer alterações de dados resultarão em novas referências, o que significa que atualizações de dados imutáveis ​​são necessárias em vez de mutação direta.
markerikson
13

Minha resposta está um pouco fora do campo esquerdo. Ele lança luz sobre um problema que me levou a este post. No meu caso, parecia que o aplicativo não estava sendo renderizado novamente, embora recebesse novos adereços.
Os desenvolvedores do React tiveram uma resposta para essa pergunta frequente, algo que diz que se o (armazenamento) sofreu uma mutação, 99% das vezes esse é o motivo pelo qual o react não renderá novamente. Ainda nada sobre os outros 1%. Mutação não era o caso aqui.


TLDR;

componentWillReceivePropsé como o statepode ser sincronizado com o novo props.

Borda de caso: Uma vez que stateas atualizações, em seguida, o aplicativo faz re-render!


Acontece que se seu aplicativo está usando apenas statepara exibir seus elementos, propspode atualizar, mas statenão fará, portanto, não renderizará novamente.

Eu tinha stateque depender do propsrecebido do redux store. Os dados de que eu precisava ainda não estavam na loja, então eu os busquei componentDidMount, como é apropriado. Eu recebi os adereços de volta, quando meu redutor atualizou a loja, porque meu componente está conectado via mapStateToProps. Mas a página não foi renderizada e o estado ainda estava cheio de strings vazias.

Um exemplo disso é dizer que um usuário carregou uma página de "edição de postagem" de um url salvo. Você tem acesso a postIddo url, mas a informação ainda não storefoi encontrada, então você a busca. Os itens em sua página são componentes controlados - portanto, todos os dados que você está exibindo estão em state.

Usando redux, os dados foram buscados, o armazenamento foi atualizado e o componente é connect editado, mas o aplicativo não refletiu as alterações. Olhando mais de perto, propsforam recebidos, mas o aplicativo não foi atualizado. statenão atualizou.

Bem, props será atualizado e propagado, mas statenão. Você precisa informar especificamente statepara atualizar.

Você não pode fazer isso em render() , e componentDidMountjá terminou seus ciclos.

componentWillReceiveProps é onde você atualiza state propriedades que dependem de um propvalor alterado .

Exemplo de uso:

componentWillReceiveProps(nextProps){
  if (this.props.post.category !== nextProps.post.category){
    this.setState({
      title: nextProps.post.title,
      body: nextProps.post.body,
      category: nextProps.post.category,
    })
  }      
}

Devo agradecer a este artigo que me iluminou sobre a solução que dezenas de outros posts, blogs e repositórios deixaram de mencionar. Qualquer outra pessoa que teve problemas para encontrar uma resposta para este problema evidentemente obscuro, aqui está:

Métodos de ciclo de vida do componente ReactJs - um mergulho profundo

componentWillReceivePropsé onde você atualizará statepara se manter em sincronia com as propsatualizações.

Uma vez que stateas atualizações, então campos dependendo de state fazer re-render!

SherylHohman
fonte
3
A React 16.3partir de agora, você deve usar o em getDerivedStateFromPropsvez de componentWillReceiveProps. Mas, na verdade, você nem deve fazer tudo isso conforme articulado aqui
kyw
ele não funciona, sempre que o estado é alterado, o componentWillReceiveProps vai funcionar, tantos casos eu tento que isso não funciona. Especialmente, adereços complexos ou aninhados de vários níveis, então tenho que atribuir um componente com um ID e acionar a mudança para ele e atualizar o estado para renderizar novos dados
May Weather VN
0

Esta resposta é um resumo do artigo de Brian Vaughn intitulado Você provavelmente não precisa do estado derivado (7 de junho de 2018).

Derivar o estado dos adereços é um antipadrão em todas as suas formas. Incluindo o uso do mais antigo componentWillReceivePropse do mais recente getDerivedStateFromProps.

Em vez de derivar o estado dos adereços, considere as seguintes soluções.

Duas recomendações de melhores práticas

Recomendação 1. Componente totalmente controlado
function EmailInput(props) {
  return <input onChange={props.onChange} value={props.email} />;
}
Recomendação 2. Componente totalmente não controlado com uma chave
// parent class
class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

  handleChange = event => {
    this.setState({ email: event.target.value });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

// child instance
<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

Duas alternativas se, por qualquer motivo, as recomendações não funcionarem para sua situação.

Alternativa 1: redefinir o componente não controlado com um ID prop
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if (props.userID !== state.prevPropsUserID) {
      return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}
Alternativa 2: redefinir o componente não controlado com um método de instância
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail
  };

  resetEmailForNewUser(newEmail) {
    this.setState({ email: newEmail });
  }

  // ...
}
Deixe-me pensar sobre isso
fonte
-2

como eu sei que a única coisa que o redux faz, na mudança de estado da loja está chamando componentWillRecieveProps se seu componente era dependente de um estado mutado e então você deve forçar seu componente a atualizar é assim

Alteração de estado de 1 loja-2 chamadas (componentWillRecieveProps (() => {alteração de estado de 3 componentes}))

ghazaleh javaheri
fonte