Redux: mapStateToProps () não sendo chamado

Criado em 13 mai. 2016  ·  4Comentários  ·  Fonte: reduxjs/redux

Eu li toneladas de documentação - em todos os sites populares - mas em nenhum lugar encontrei uma resposta. Estou perguntando aqui para ver se perdi alguma documentação ou uma parte vital de compreensão.

O problema que vejo é que a função mapStateToProps () do meu componente só é chamada uma vez (na inicialização). Nunca mais é chamado, mesmo quando o redutor é chamado. Estou começando a pensar que é porque não entendo como as ações / redutores / suportes estão todos interligados.

Deixe-me primeiro dizer o que eu _do_ sei:

  • Eu entendo que as ações descrevem atualizações em uma parte (uma propriedade) dessa loja.
  • Entendo que cada redutor é responsável por atualizar seu slice / porção da loja. (E eu definitivamente entendo que o redutor deve retornar um novo objeto para sua fatia.)
  • Eu entendo que mapStateToProps "pega" certos dados da loja e os passa como adereços para o componente nomeado.

Mas ... Eu não encontrei uma descrição de como as ações, redutores e mapStateToProps se relacionam entre si. Minhas perguntas:

  • Eu penso na loja como um objeto, com propriedades que armazenam dados sobre o estado do aplicativo. Essas propriedades de nível superior podem ser objetos que fornecem uma subárvore para conter informações sobre uma parte do aplicativo. Isso é verdade?
  • Quando uma ação é enviada para a loja, qual propriedade (ou subárvore da loja) é atualizada? Parece que há uma criação / associação automática a uma propriedade dentro da loja, mas como isso é determinado?
  • Como mapStateToProps "sabe" se deve ouvir as atualizações da propriedade / parte adequada da loja? O que os une?
  • Como posso depurar isso? O que eu preciso fazer para descobrir por que o envio da ação não parece disparar mapStateToProps?
  • Ou estou pensando tudo errado ...?
question

Comentários muito úteis

Você examinou http://redux.js.org/docs/Trou trouble.html e verificou que seu caso não é um dos problemas que ele descreve?

Eu penso na loja como um objeto, com propriedades que armazenam dados sobre o estado do aplicativo. Essas propriedades de nível superior podem ser objetos que fornecem uma subárvore para conter informações sobre uma parte do aplicativo. Isso é verdade?

Sim. Store mantém o objeto de estado de nível superior internamente. Ele fornece acesso a esse objeto de store.getState() . Esse objeto corresponde a tudo o que você retorna do redutor de raiz e geralmente é semelhante a uma árvore.

Quando uma ação é enviada para a loja, qual propriedade (ou subárvore da loja) é atualizada? Parece que há uma criação / associação automática a uma propriedade dentro da loja, mas como isso é determinado?

Não, não há criação automática de nada. A loja apenas começa a apontar para o resultado de chamar seu redutor de raiz.

Se o seu redutor de raiz é uma função que você mesmo escreveu, o resultado é o que você obterá após despachar uma ação:

function counter(state = 0, action) {
  if (action.type === 'INCREMENT') {
    return state + 1; // will become the next value of store.getState() after store.dispatch()
  }
  return state;
}

Se você _gerar_ o redutor de raiz com algo como combineReducers({ foo, bar }) , é o mesmo como se você escrevesse um redutor de raiz à mão parecido com este:

function root(state = {}, action) {
  return {
    foo: foo(state.foo, action),
    bar: bar(state.bar, action)
  }
}

É daí que muitas vezes vêm as propriedades no estado. Seus valores são determinados pelo que seus redutores foo e bar retornaram durante o manuseio da ação, respectivamente, portanto, novamente, você tem controle total sobre eles.

Como mapStateToProps "sabe" se deve ouvir as atualizações da propriedade / parte adequada da loja? O que os une?

Você passa mapStateToProps para a função connect() . Ele gera um componente que usa store.subscribe() para ouvir todas as mudanças na loja. Quando a loja muda, o componente gerado lê store.getState() e passa para mapStateToProps() para determinar os próximos adereços a serem passados. Se eles mudaram, ele irá renderizar novamente seu componente com eles.

Como posso depurar isso? O que eu preciso fazer para descobrir por que o envio da ação não parece disparar mapStateToProps?

Adicione um middleware como https://github.com/theaqua/redux-logger. Verifique se o estado é atualizado após a ação da maneira esperada. Se mapStateToProps nem mesmo for chamado, isso significa que

  • Ou você se esqueceu de dispatch() uma ação e acabou de chamar um criador de ação,
  • Ou seu redutor retornou um valor referencialmente idêntico, então connect() não se preocupou em chamar mapStateToProps() .

Ambos os casos são descritos em http://redux.js.org/docs/Trou troubleshooting.html.

No futuro, pedimos que você tente fazer perguntas no StackOverflow primeiro e usar o rastreador de problemas quando tiver certeza de que há um bug na biblioteca que você consegue reproduzir de forma consistente com um exemplo que pode compartilhar.

Obrigado!

Todos 4 comentários

Você examinou http://redux.js.org/docs/Trou trouble.html e verificou que seu caso não é um dos problemas que ele descreve?

Eu penso na loja como um objeto, com propriedades que armazenam dados sobre o estado do aplicativo. Essas propriedades de nível superior podem ser objetos que fornecem uma subárvore para conter informações sobre uma parte do aplicativo. Isso é verdade?

Sim. Store mantém o objeto de estado de nível superior internamente. Ele fornece acesso a esse objeto de store.getState() . Esse objeto corresponde a tudo o que você retorna do redutor de raiz e geralmente é semelhante a uma árvore.

Quando uma ação é enviada para a loja, qual propriedade (ou subárvore da loja) é atualizada? Parece que há uma criação / associação automática a uma propriedade dentro da loja, mas como isso é determinado?

Não, não há criação automática de nada. A loja apenas começa a apontar para o resultado de chamar seu redutor de raiz.

Se o seu redutor de raiz é uma função que você mesmo escreveu, o resultado é o que você obterá após despachar uma ação:

function counter(state = 0, action) {
  if (action.type === 'INCREMENT') {
    return state + 1; // will become the next value of store.getState() after store.dispatch()
  }
  return state;
}

Se você _gerar_ o redutor de raiz com algo como combineReducers({ foo, bar }) , é o mesmo como se você escrevesse um redutor de raiz à mão parecido com este:

function root(state = {}, action) {
  return {
    foo: foo(state.foo, action),
    bar: bar(state.bar, action)
  }
}

É daí que muitas vezes vêm as propriedades no estado. Seus valores são determinados pelo que seus redutores foo e bar retornaram durante o manuseio da ação, respectivamente, portanto, novamente, você tem controle total sobre eles.

Como mapStateToProps "sabe" se deve ouvir as atualizações da propriedade / parte adequada da loja? O que os une?

Você passa mapStateToProps para a função connect() . Ele gera um componente que usa store.subscribe() para ouvir todas as mudanças na loja. Quando a loja muda, o componente gerado lê store.getState() e passa para mapStateToProps() para determinar os próximos adereços a serem passados. Se eles mudaram, ele irá renderizar novamente seu componente com eles.

Como posso depurar isso? O que eu preciso fazer para descobrir por que o envio da ação não parece disparar mapStateToProps?

Adicione um middleware como https://github.com/theaqua/redux-logger. Verifique se o estado é atualizado após a ação da maneira esperada. Se mapStateToProps nem mesmo for chamado, isso significa que

  • Ou você se esqueceu de dispatch() uma ação e acabou de chamar um criador de ação,
  • Ou seu redutor retornou um valor referencialmente idêntico, então connect() não se preocupou em chamar mapStateToProps() .

Ambos os casos são descritos em http://redux.js.org/docs/Trou troubleshooting.html.

No futuro, pedimos que você tente fazer perguntas no StackOverflow primeiro e usar o rastreador de problemas quando tiver certeza de que há um bug na biblioteca que você consegue reproduzir de forma consistente com um exemplo que pode compartilhar.

Obrigado!

Respondendo em ordem:

  • Redux geralmente assume que a parte superior de sua árvore de estado é um objeto Javascript simples (embora também seja comum usar os tipos de dados fornecidos pela biblioteca Immutable.js). O que você coloca dentro desse objeto e como estrutura o conteúdo depende inteiramente de você. A abordagem mais comum é organizar as coisas por domínio. Por exemplo, uma árvore de estado para um aplicativo de blog hipotético pode ser semelhante a: { users : {}, posts : {}, comments : {}, ui : {} } . Consulte http://redux.js.org/docs/FAQ.html#reducers -share-state e http://redux.js.org/docs/FAQ.html#organizing -state-nested-data.
  • Tecnicamente falando, ao criar sua loja, você define uma função _única_ de "redutor". Essa função é chamada com o estado atual e a ação recebida, e é responsável por retornar um novo objeto de estado, com _todas_ as atualizações apropriadas, feitas de maneira imutável (ou seja, sem edições diretas no local - tudo que precisa ser atualizado deve ser feito fazendo cópias, modificando-as e devolvendo as cópias, e atualizar um campo aninhado significa também copiar e atualizar todos os seus ancestrais). No entanto, uma vez que colocar cada pedaço de lógica de atualização em uma função gigante é uma má ideia, essa lógica de atualização quase sempre é dividida em funções menores de sub-redutor reutilizáveis. Como você estrutura essa lógica é novamente com você, mas nada é atualizado automaticamente - é responsabilidade do seu redutor fazer isso. O utilitário combineReducers que vem com o Redux torna mais fácil ter uma função redutora específica que é responsável por gerenciar atualizações para uma determinada fatia de dados, como: const combinedReducer = combineReducers({users : userReducer, posts : postReducer, comments : commentsReducer, ui : uiReducer});
  • O Redux simplesmente notifica todos os assinantes sempre que _qualquer_ ação é executada na loja. A função React Redux connect() inscreve e então chama mapStateToProps do componente para ver se algum dos dados extraídos mudou desde a última vez. Não há assinatura de estado aninhada específica. Consulte http://redux.js.org/docs/FAQ.html#store -setup-subscriptions.
  • A causa número um da não atualização de um componente é a mutação direta de estado em seu redutor. Consulte http://redux.js.org/docs/FAQ.html#react -not-rerendering. Além disso, há uma série de complementos que você pode usar para depurar e registrar ações. Eu tenho um monte deles listados em https://github.com/markerikson/redux-ecosystem-links/blob/master/devtools.md.

Espero que isso ajude a esclarecer as coisas.

Além disso, como disse Dan: questões de uso geralmente são mais adequadas para Stack Overflow. Além disso, a comunidade Reactiflux no Discord tem vários canais de bate-papo dedicados a tecnologias relacionadas ao React, incluindo Redux, e sempre há várias pessoas por aí dispostas a responder perguntas. Existe um link de convite em https://www.reactiflux.com .

@ richb-hanover Você provavelmente testemunhou a comparação superficial que é realizada pela conexão react-redux. Isso significa que objetos aninhados em outros objetos não resultarão em renderização novamente, mesmo se forem modificados, porque apenas a chave raiz é verificada para modificações.

http://redux.js.org/docs/faq/ReactRedux.html#why -isnt-my-component-re-rendering-or-my-mapstatetoprops-running

@ richb-hanover No meu caso, @gaearon detectou o problema:

Ou seu redutor retornou um valor referencialmente idêntico, portanto, connect () não se incomodou em chamar mapStateToProps ().

Eu estava usando essa correção e esse foi o código resultante

const mapStateToProps = (state, props) => {
    return { id: props.id, currentState: state[props.id] };
}

const mapDispatchToProps = (dispatch) => {
    return {
        increment: (id) => {
            dispatch({ type: 'INCREMENT', id: id })
        }
    }
}

const DumbListItem = ({
    increment,
    id,
    currentState
}) => (
        <div>
            <li onClick={() => increment()}>{id}</li>
            <span>{currentState}</span>
        </div>
    );

export const ConnectedListItem = connect(
    mapStateToProps,
    mapDispatchToProps
)(DumbListItem);

Como eu não passei o parâmetro id para a chamada de incremento (seja como increment(id) no modelo, ou por meio do segundo parâmetro em mapDispatchToProps ), a loja não atualize corretamente, portanto, verifique se esse é o seu problema.

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

Questões relacionadas

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

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

timdorr picture timdorr  ·  3Comentários

elado picture elado  ·  3Comentários

vraa picture vraa  ·  3Comentários