Redux: Os componentes não são renderizados novamente com conectar ()

Criado em 20 ago. 2015  ·  32Comentários  ·  Fonte: reduxjs/redux

Estou começando a conhecer o Redux por ter lido a documentação e estou começando apenas modificando o exemplo do mundo real. No meu caso, troquei as chamadas de API HTTP do GitHub pelas minhas e troquei os conceitos Repo e Usuário por conceitos análogos em meu ambiente. Até agora, não deve ser muito diferente, certo?

É aqui que estou tendo problemas:

Minhas chamadas de API estão sendo bem-sucedidas, a resposta JSON está ficando camelCased e normalizada, _mas_ meu componente não é renderizado novamente depois que a solicitação de API ocorre com sucesso. Pouco depois de USER_REQUEST ser despachado e o componente de usuário ser atualizado para mostrar "Carregando ...", USER_SUCCESS é despachado e seu próximo estado contém uma chave entities preenchida:

pasted_image_8_19_15__3_27_pm

Isso significaria que o problema está no middleware, certo? Devo armazenar as entidades da ação na loja, e o componente deve observar a mudança e renderizar novamente, sim?

Onde, no exemplo do mundo real, os dados recuperados e normalizados com êxito _realmente_ são colocados no armazenamento? Vejo onde está sendo normalizado, mas não onde o resultado dessa normalização está sendo entregue à loja.

Muito obrigado!

question

Comentários muito úteis

Os dados são definidos / atualizados / excluídos na loja por meio dos resultados das ações de manuseio nos redutores. Os redutores recebem o estado atual de uma parte do seu aplicativo e esperam obter o novo estado de volta. Um dos motivos mais comuns pelos quais seus componentes podem não ser renderizados novamente é que você está modificando o estado existente em seu redutor em vez de retornar uma nova cópia do estado com as alterações necessárias (verifique a seção Solução de problemas ). Quando você altera o estado existente diretamente, o Redux não detecta uma diferença no estado e não notifica seus componentes que o armazenamento mudou.

Portanto, eu definitivamente verificaria seus redutores e verificaria se você não está alterando o estado existente. Espero que ajude!

Todos 32 comentários

Os dados são definidos / atualizados / excluídos na loja por meio dos resultados das ações de manuseio nos redutores. Os redutores recebem o estado atual de uma parte do seu aplicativo e esperam obter o novo estado de volta. Um dos motivos mais comuns pelos quais seus componentes podem não ser renderizados novamente é que você está modificando o estado existente em seu redutor em vez de retornar uma nova cópia do estado com as alterações necessárias (verifique a seção Solução de problemas ). Quando você altera o estado existente diretamente, o Redux não detecta uma diferença no estado e não notifica seus componentes que o armazenamento mudou.

Portanto, eu definitivamente verificaria seus redutores e verificaria se você não está alterando o estado existente. Espero que ajude!

Além do que @ernieturner disse, certifique-se de que a função mapStateToProps em sua função connect(mapStateToProps)(YourConnectedComponent) mapeie o estado relevante (neste caso entities ) para que o componente conectado esteja escutando para essa fatia do estado.

Além disso, lembre-se de que connect() faz uma comparação superficial: https://github.com/rackt/react-redux/blob/d5bf492ee35ad1be8ffd5fa6be689cd74df3b41e/src/components/createConnect.js#L91

É provável que, como a forma do seu estado conectado não mudou, ele está supondo que você não fará uma atualização.

Ah, entendi! Meu mapStateToProps estava tentando extrair um objeto do estado, mas não estava especificando a chave correta. Em vez de extrair entities.users[login] , configurei para entities.users[id] . O objeto entities.users foi codificado por login string, então quando o id numérico não foi encontrado, nenhum adereço foi realmente alterado, então nenhuma nova renderização foi necessária.

Obrigado a todos pela ajuda! Eu realmente aprecio o tempo que você dedicou para ajudar.

@timdorr
Isso é ridículo. Como forçar connect() verificar mudanças profundas? Caso contrário, será necessário criar dezenas de componentes de contêiner para cada nível mais profundo de estado. Não é normal

Você absolutamente deve ter muitos componentes conectados em qualquer aplicativo de tamanho razoável. Se você tiver um único componente conectado de nível superior, irá renderizar novamente grandes partes do seu aplicativo desnecessariamente sempre que o estado mudar. E você estará evitando uma das principais otimizações do react-redux, que é apenas renderizar novamente quando o estado ao qual está inscrito mudar.

Se você precisar avaliar uma consulta complexa de estado, use a nova seleção para acelerar isso.

@timdorr @wzup

Pode ser útil esclarecer que a conexão em locais mais baixos da árvore não é estritamente necessária para resolver o problema da comparação profunda.

Conectar-se em lugares mais baixos em sua árvore de componentes pode realmente ajudar com _desempenho_, mas não é a solução fundamental para o problema de comparação profunda. A solução para os problemas de comparação profunda é que seus redutores atualizem o estado de forma imutável, de modo que uma comparação superficial seja adequada para saber quando uma parte mais profunda do estado mudou.

Em outras palavras, conectar um componente de nível superior a um grande pedaço de estado aninhado funciona bem _mesmo com comparações superficiais_, contanto que seus redutores retornem um novo estado sem alterar os objetos de estado existentes.

@naw Obrigado por essa dica. Com base nisso, acabei adicionando um novo redutor onde estava tendo este problema:

const reducers = combineReducers({
    //...bunch of reducers which always return objects of 3 or 4 properties that change sometimes but
    // the shape stays the same
    dataVersion: (state = Symbol(), action = {}) => {
        switch (action.type) {
            case SAME_ACTION_AS_THE_ONE_THAT_UPDATES_A_COMPLEX_PROPERTY:
            case ANOTHER_ACTION_THAT_ALSO_UPDATES_A_COMPLEX_PROPERTY:
                return Symbol():
            default:
                return state;
        }
    }
})

Isso ainda parece estranho para mim, no entanto. Se eu tiver um redutor que vai:

    switch (action.type) {
       case UPDATE_THIS:
           return {a: action.a, b: action.b, c: action.c};
       default:
           return state;
     }

o fato de que estou retornando um objeto recém-inicializado que por acaso tem a mesma forma do estado anterior deve ser o suficiente para acionar um previousState !== newState . Se eu deveria estar retornando dados imutáveis ​​todas as vezes, parece que é mais ineficiente fazer uma comparação superficial da forma do que apenas da identidade. (Supondo que seja isso que o Redux está realmente fazendo. O fato de que minha solução alternativa Symbol funcione me faz pensar que é isso que está acontecendo)

Neste caso específico, adicionar mais componentes de contêiner não resolve o problema. Infelizmente, não posso vincular um projeto inteiro do github para esclarecimento porque estou lidando com o código-fonte fechado da minha empresa.

@Zacqary A comparação do estado atual com o estado anterior é feita com estrito igual (===). A comparação de stateProps (o resultado de mapStateToProps) é feita com igual superficial.

@Zacqary

mapStateToProps está "reunindo" dados para o seu componente conectado entrando na loja.

mapStateToProps retira várias peças da loja e as atribui às chaves em um novo objeto stateProps .

O objeto stateProps resultante será novo a cada vez (então sua identidade muda), então não podemos apenas comparar a identidade do objeto stateProps si --- nós _temos_ que descer um nível e verifique a identidade do objeto de cada valor, que é onde shallowEqual entra em jogo, como @jimbolla disse.

Normalmente surgem problemas quando você escreve redutores de tal forma que a identidade do objeto de uma parte do estado _não muda_, enquanto algum atributo aninhado dentro dela _muda_. Este é um estado mutante em vez de retornar um novo estado imutável, o que faz com que react-redux pense que nada mudou, quando na verdade algo _did_ mudou.

o fato de que estou retornando um objeto recém-inicializado que por acaso tem a mesma forma do estado anterior deve ser suficiente para acionar um previousState! == newState.

Se você retornar um novo objeto para alguma fatia de estado e mapStateToProps usar essa fatia de estado como o valor de uma de suas chaves, react-redux verá que a identidade do objeto mudou, e não impede uma nova renderização.

Existe algo específico que não está funcionando como você esperava? Não tenho certeza do que você quer dizer com sua solução alternativa Symbol .

Vale a pena considerar se o componente adicional deve simplesmente ser movido para baixo no componente connected() para herdar os adereços. Eu resolvi esse problema tentando connect() dois componentes, mas isso era uma complexidade desnecessária.

@ernieturner Sei que isso é antigo, mas talvez você queira atualizar o link da seção de solução de problemas .

Felicidades

Eu tive esse problema, bem, não exatamente.
Eu apliquei as propriedades ao estado, como:

  constructor(props){
    this.state = {
      text: props.text
    }
  }

E não estava funcionando. Então, apliquei valor diretamente de adereços como

{this.props.text}

E funciona muito bem :)

@AlexanderKozhevin A menos que eu esteja enganado, seu construtor está faltando um super(props) .
Normalmente, você não tem permissão para usar this antes de chamar super(props) .

@ cdubois-mh Certo, obrigado pela nota.

Eu escrevi um redutor de raiz de aplicativo feito sob medida e resolvo esse problema.
Devolver uma cópia de todo o estado
{...state}
no final do redutor de raiz ajudou para mim

@daedalius É assim que um redutor deve funcionar.
Ele deve retornar o estado se a ação for irrelevante ou retornar uma cópia do estado com os valores modificados esperados.

Se você não retornar uma cópia, o react nem sempre será capaz de detectar que um valor mudou.
(por exemplo, se você modificar uma entrada aninhada)

Verifique este estado:

{
    "clients": [
        { "name": "John", "cid": 4578 },
        { "name": "Alex", "cid": 5492 },
        { "name": "Bob",  "cid": 254 }
    ]
}

Se você modificar o nome de um cliente, mas não retornar um clone do estado, então você não modificou o estado, você modificou o objeto no array.

O próprio objeto ainda terá a mesma referência de antes, portanto, o react perderá a mudança.
Ao retornar um clone, a referência do próprio estado agora será diferente (uma vez que é um novo objeto) e reagirá iniciará todo o processo de renderização.

Se eu estiver errado, corrija-me, mas é assim que eu entendo o funcionamento dos redutores.

Sim, isso é correto. Além disso, @daedalius , observe que você não quer _sempre_ fazer uma cópia superficial de state - você só deve fazer isso se algo mudar, caso contrário, poderá acabar com uma nova renderização desnecessária de seus componentes.

forçar atualização

<AfterConnect
    _forceUpdate={Symbol()}
/>

@BrookShuihuaLee O que isso quer dizer? você não forneceu nenhuma informação ou explicação sobre seu código. O que isso faz? Por que isso é relevante para a discussão atual?

@CedSharp A função shallowEqual retornará falso, se definirmos _forceUpdate = {Symbol ()}

@BrookShuihuaLee Seria ótimo se você pudesse incluir uma explicação como esta em sua primeira resposta, assim as pessoas entenderão melhor ^^

@CedSharp yeah

@BrookShuihuaLee : Parece uma má ideia. Por que você gostaria de fazer isso?

@BrookShuihuaLee , concordo com @markerikson .
Há um motivo pelo qual você NÃO deve retornar um clone quando o estado não mudou. A menos que você possa fornecer um exemplo válido em que sua solução pareça necessária, eu recomendo fortemente que não continue a trabalhar no Redux.

@CedSharp
@markerikson
Porque o componente AfterConnect será renderizado com frequência, por design. Não há problema em renderizá-lo novamente quando seu componente pai estiver sendo renderizado. Não quero fazer todos os cálculos no componente pai e definir toneladas de adereços para o AfterConnect.

@BrookShuihuaLee , sinto muito, mas como o seu componente é relevante para a discussão em questão?
Se bem entendi, você está falando sobre seu próprio componente personalizado? ForceUpdate é algo fortemente desencorajado no mundo React e eu não o recomendaria como solução.

Em vez disso, você poderia usar uma solução observável em que procura mudanças em outro lugar que não o estado? Não sei por que você precisaria renderizar novamente aquele componente específico sem que seu estado seja modificado, mas se você sabe por que ele deveria renderizar novamente, talvez você pudesse usar algo como mobx?
(https://github.com/mobxjs/mobx)

Estou tentando entender qual é a sua solução e por que ela se aplica a essa situação.

@CedSharp Não quero dizer que todos devam usar forceUpdate. Mas a recomendação é uma coisa, a escolha é outra. O React também mantém o ReactComponent.prototype.forceUpdate para desenvolvedores. As pessoas precisam de escolhas.

Eu estava tentando dar outra escolha. Talvez não seja a melhor escolha.

Não é ideal, mas encontrei esse problema porque estruturei o estado do meu aplicativo incorretamente. Eu contornei o problema de comparação superficial apenas atualizando um campo raso na fatia do estado que estava me causando problemas.

case SOME_ACTION:
      return {
        ...state,
        ts: (new Date).getTime(),
      };

IMO, não é uma boa solução, mas para qualquer pessoa atualmente em apuros (ou seja, você tem que liberar no final do dia), é uma solução rápida e você não precisa forçar uma renderização em outro lugar.

O código deve ser refatorado para que uma comparação superficial, mesmo em um estado profundamente aninhado, funcione.

O estado deve ser imutável. Copie profundamente o estado no redutor e, em seguida, a comparação superficial em conectar é suficiente e eficaz.

Meu problema era ter um objeto com matrizes dentro dele. Como era uma comparação superficial, nunca alcançou os objetos dentro dos arrays filhos. Resolvi usando list:state.obj.list vez de lists:state.obj em mapStateToProps 👍🏼

Acabei de encontrar esse problema e, depois de ler as primeiras respostas, verifiquei meu redutor e percebi que havia um erro de digitação. Renomeei uma propriedade e atualizei o INITIAL_STATE, mas esqueci de alterar a chave que estava sendo atualizada por esta ação problemática.

Basicamente, eu estava atualizando alguma chave aleatória que não era usada no componente, então, obviamente, não seria renderizada novamente quando essa chave aleatória fosse atualizada.

VERIFIQUE SE HÁ TIPOS NO SEU REDUTOR! :)

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

dmitry-zaets picture dmitry-zaets  ·  3Comentários

ramakay picture ramakay  ·  3Comentários

mickeyreiss-visor picture mickeyreiss-visor  ·  3Comentários

amorphius picture amorphius  ·  3Comentários

ilearnio picture ilearnio  ·  3Comentários