Noções básicas sobre React-Redux e mapStateToProps ()

219

Estou tentando entender o método de conexão do react-redux e as funções que ele assume como parâmetros. Em particular mapStateToProps().

Pelo que entendi, o valor de retorno de mapStateToPropsserá um objeto derivado do estado (como ele vive na loja), cujas chaves serão passadas para o componente de destino (o componente ao qual a conexão é aplicada) como acessórios.

Isso significa que o estado consumido pelo seu componente de destino pode ter uma estrutura totalmente diferente do estado em que é armazenado em sua loja.

Q: está tudo bem?
P: Isso é esperado?
Q: Isso é um anti-padrão?

Pablo Barría Urenda
fonte
11
Não quero adicionar outra resposta à mistura ... mas percebo que ninguém responde à sua pergunta ... na minha opinião, NÃO é um anti-padrão. A chave está no nome mapStateTo Props que você está passando propriedades somente leitura para um componente consumir. Costumo usar meus componentes de contêiner para pegar o estado e alterá-lo antes de passá-lo para o componente de apresentação.
Matthew Brent
3
Dessa forma, meu componente de apresentação é muito mais simples ... eu posso renderizar this.props.someDataem vez de this.props.someKey[someOtherKey].someData... faz sentido?
Matthew Brent
3
Este tutorial explica bem o suficiente: learn.co/lessons/map-state-to-props-readme
Ayan
Olá Pablo, por favor, considere sua resposta escolhida.
vsync
Re-considerar como?
Pablo Barría Urenda 16/09

Respostas:

56

Q: Is this ok?
A: sim

P: Is this expected?
Sim, isso é esperado (se você estiver usando o react-redux).

P: Is this an anti-pattern?
R: Não, isso não é um antipadrão.

Chama-se "conectar" seu componente ou "torná-lo inteligente". É por design.

Permite dissociar seu componente do seu estado por um tempo adicional, o que aumenta a modularidade do seu código. Também permite simplificar o estado do componente como um subconjunto do estado do aplicativo, o que, de fato, ajuda a cumprir o padrão Redux.

Pense da seguinte maneira: uma loja deve conter todo o estado do seu aplicativo.
Para aplicativos grandes, isso pode conter dezenas de propriedades aninhadas em várias camadas.
Você não deseja transportar tudo isso a cada ligação (caro).

Sem mapStateToPropsou algum análogo, você seria tentado a criar seu estado de outra maneira para melhorar o desempenho / simplificar.

Richard Strickland
fonte
6
Não acho que conceder acesso a todo e qualquer componente de toda a loja, por maior que seja, tem algo a ver com desempenho. passar objetos ao redor não ocupa memória, pois é sempre o mesmo objeto. A única razão que leve para os componentes das peças de que necessita é provavelmente 2 razões: (1) -acesso mais fácil profunda (2) -Evitar erros onde um poder mexer componente-se estado que não pertencem a ele
vsync
@vsync Você poderia explicar como isso permite um acesso profundo mais fácil? Você quer dizer que os acessórios locais agora podem ser usados ​​em vez de precisar se referir ao estado global e, portanto, são mais legíveis?
Siddhartha
Além disso, como um componente poderia atrapalhar o estado que não pertence a ele quando o estado é passado como imutável?
Siddhartha
se o estado é imutável, acho que está bem, mas ainda assim, como boa prática, é melhor expor aos componentes apenas as partes relevantes para eles. Isso também ajuda outros desenvolvedores a entender melhor quais partes (do objeto state ) são relevantes para esse componente. Em relação ao "acesso mais fácil", é mais fácil, em certo sentido, que o caminho para algum estado profundo seja transmitido diretamente ao componente como um suporte, e esse componente seja cego para o fato de haver Redux nos bastidores. Os componentes não devem se importar com o sistema de gerenciamento de estado usado e devem trabalhar apenas com os acessórios que recebem.
vsync
119

Sim, está correto. É apenas uma função auxiliar ter uma maneira mais simples de acessar as propriedades do seu estado

Imagine que você tem uma postschave no seu aplicativostate.posts

state.posts //
/*    
{
  currentPostId: "",
  isFetching: false,
  allPosts: {}
}
*/

E componente Posts

Por padrão connect()(Posts), todos os adereços de estado estarão disponíveis para o Componente conectado

const Posts = ({posts}) => (
  <div>
    {/* access posts.isFetching, access posts.allPosts */}
  </div> 
)

Agora, quando você mapeia o state.postscomponente, ele fica um pouco melhor

const Posts = ({isFetching, allPosts}) => (
  <div>
    {/* access isFetching, allPosts directly */}
  </div> 
)

connect(
  state => state.posts
)(Posts)

mapDispatchToProps

normalmente você tem que escrever dispatch(anActionCreator())

com bindActionCreatorsvocê pode fazê-lo também mais facilmente como

connect(
  state => state.posts,
  dispatch => bindActionCreators({fetchPosts, deletePost}, dispatch)
)(Posts)

Agora você pode usá-lo no seu Component

const Posts = ({isFetching, allPosts, fetchPosts, deletePost }) => (
  <div>
    <button onClick={() => fetchPosts()} />Fetch posts</button>
    {/* access isFetching, allPosts directly */}
  </div> 
)

Atualização em actionCreators ..

Um exemplo de um actionCreator: deletePost

const deletePostAction = (id) => ({
  action: 'DELETE_POST',
  payload: { id },
})

Então, bindActionCreatorsbasta executar suas ações, envolvê-las em uma dispatchchamada. (Eu não li o código fonte do redux, mas a implementação pode ser algo como isto:

const bindActionCreators = (actions, dispatch) => {
  return Object.keys(actions).reduce(actionsMap, actionNameInProps => {
    actionsMap[actionNameInProps] = (...args) => dispatch(actions[actionNameInProps].call(null, ...args))
    return actionsMap;
  }, {})
}
webdeb
fonte
Acho que posso sentir falta de alguma coisa, mas de onde são dispatch => bindActionCreators({fetchPosts, deletePost}, dispatch)obtidas as ações fetchPostse deletePost?
Ilyo 21/10
@ilyo estes são os seus criadores de ação, você tem que importá-los
webdeb
2
Boa resposta! Eu acho que também é bom enfatizar que esse pedaço de código state => state.posts(a mapStateToPropsfunção) dirá ao React quais estados acionarão uma nova renderização do componente quando atualizado.
Miguel Péres
38

Você acertou a primeira parte:

Sim mapStateToPropstem o estado da loja como argumento / parâmetro (fornecido por react-redux::connect) e é usado para vincular o componente a determinada parte do estado da loja.

Ao vincular, quero dizer que o objeto retornado por mapStateToPropsserá fornecido no momento da construção como adereços e qualquer alteração subsequente estará disponível através componentWillReceiveProps.

Se você conhece o padrão de design do Observer, é exatamente isso ou uma pequena variação dele.

Um exemplo ajudaria a tornar as coisas mais claras:

import React, {
    Component,
} from 'react-native';

class ItemsContainer extends Component {
    constructor(props) {
        super(props);

        this.state = {
            items: props.items, //provided by connect@mapStateToProps
            filteredItems: this.filterItems(props.items, props.filters),
        };
    }

    componentWillReceiveProps(nextProps) {
        this.setState({
            filteredItems: this.filterItems(this.state.items, nextProps.filters),
        });
    }

    filterItems = (items, filters) => { /* return filtered list */ }

    render() {
        return (
            <View>
                // display the filtered items
            </View>
        );
    }
}

module.exports = connect(
    //mapStateToProps,
    (state) => ({
        items: state.App.Items.List,
        filters: state.App.Items.Filters,
        //the State.App & state.App.Items.List/Filters are reducers used as an example.
    })
    // mapDispatchToProps,  that's another subject
)(ItemsContainer);

Pode haver outro componente de reação chamado itemsFiltersque manipula a exibição e persiste o estado do filtro no estado Redux Store, o componente Demo está "ouvindo" ou "inscrito" nos filtros de estado do Redux Store, portanto, sempre que os filtros armazenam mudanças de estado (com a ajuda de filtersComponent) reagem -redux detecta que houve uma alteração e notifica ou "publica" todos os componentes de escuta / inscritos enviando as alterações para seuscomponentWillReceiveProps quais, neste exemplo, acionam um refiltro dos itens e atualizam a exibição devido ao fato de que o estado de reação foi alterado .

Deixe-me saber se o exemplo é confuso ou não é claro o suficiente para fornecer uma explicação melhor.

Quanto a: isso significa que o estado consumido pelo componente de destino pode ter uma estrutura totalmente diferente do estado em que está armazenado em sua loja.

Não recebi a pergunta, mas sei que o estado de reação ( this.setState) é totalmente diferente do estado da Redux Store!

O estado de reação é usado para manipular o redesenho e o comportamento do componente de reação. O estado de reação está contido no componente exclusivamente.

O estado do Redux Store é uma combinação dos estados dos redutores do Redux, sendo cada um responsável por gerenciar uma lógica de aplicativo de pequena parte. Esses atributos redutores podem ser acessados ​​com a ajuda de react-redux::connect@mapStateToPropsqualquer componente! O que torna o aplicativo Redux Store State acessível em todo o aplicativo, enquanto o estado do componente é exclusivo para si.

Mohamed Mellouki
fonte
5

Este exemplo de reação e redux é baseado no exemplo de Mohamed Mellouki. Mas valida usando regras de prettify e linting . Observe que definimos nossos props e métodos de despacho usando PropTypes para que nosso compilador não grite conosco. Este exemplo também incluiu algumas linhas de código que estavam faltando no exemplo de Mohamed. Para usar o connect, você precisará importá-lo do react-redux . Este exemplo também vincula o método filterItems, o que evita problemas de escopo no componente . Este código fonte foi formatado automaticamente usando o JavaScript Prettify .

import React, { Component } from 'react-native';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

class ItemsContainer extends Component {
  constructor(props) {
    super(props);
    const { items, filters } = props;
    this.state = {
      items,
      filteredItems: filterItems(items, filters),
    };
    this.filterItems = this.filterItems.bind(this);
  }

  componentWillReceiveProps(nextProps) {
    const { itmes } = this.state;
    const { filters } = nextProps;
    this.setState({ filteredItems: filterItems(items, filters) });
  }

  filterItems = (items, filters) => {
    /* return filtered list */
  };

  render() {
    return <View>/*display the filtered items */</View>;
  }
}

/*
define dispatch methods in propTypes so that they are validated.
*/
ItemsContainer.propTypes = {
  items: PropTypes.array.isRequired,
  filters: PropTypes.array.isRequired,
  onMyAction: PropTypes.func.isRequired,
};

/*
map state to props
*/
const mapStateToProps = state => ({
  items: state.App.Items.List,
  filters: state.App.Items.Filters,
});

/*
connect dispatch to props so that you can call the methods from the active props scope.
The defined method `onMyAction` can be called in the scope of the componets props.
*/
const mapDispatchToProps = dispatch => ({
  onMyAction: value => {
    dispatch(() => console.log(`${value}`));
  },
});

/* clean way of setting up the connect. */
export default connect(mapStateToProps, mapDispatchToProps)(ItemsContainer);

Este código de exemplo é um bom modelo para um ponto de partida para seu componente.

Patrick W. McMahon
fonte
2

O React-Redux connect é usado para atualizar o armazenamento para todas as ações.

import { connect } from 'react-redux';

const AppContainer = connect(  
  mapStateToProps,
  mapDispatchToProps
)(App);

export default AppContainer;

É muito simples e claramente explicado neste blog .

Você pode clonar o projeto do github ou copiar e colar o código desse blog para entender o Redux connect.

ArunValaven
fonte
bom manual de formapStateToProps thegreatcodeadventure.com/…
zloctb 9/09/17
1

Aqui está um esboço / padrão para descrever o comportamento de mapStateToProps:

(Esta é uma implementação bastante simplificada do que um contêiner Redux faz.)

class MyComponentContainer extends Component {
  mapStateToProps(state) {
    // this function is specific to this particular container
    return state.foo.bar;
  }

  render() {
    // This is how you get the current state from Redux,
    // and would be identical, no mater what mapStateToProps does
    const { state } = this.context.store.getState();

    const props = this.mapStateToProps(state);

    return <MyComponent {...this.props} {...props} />;
  }
}

e a seguir

function buildReduxContainer(ChildComponentClass, mapStateToProps) {
  return class Container extends Component {
    render() {
      const { state } = this.context.store.getState();

      const props = mapStateToProps(state);

      return <ChildComponentClass {...this.props} {...props} />;
    }
  }
}
zloctb
fonte
-2
import React from 'react';
import {connect} from 'react-redux';
import Userlist from './Userlist';

class Userdetails extends React.Component{

render(){
    return(
        <div>
            <p>Name : <span>{this.props.user.name}</span></p>
            <p>ID : <span>{this.props.user.id}</span></p>
            <p>Working : <span>{this.props.user.Working}</span></p>
            <p>Age : <span>{this.props.user.age}</span></p>
        </div>
    );
 }

}

 function mapStateToProps(state){  
  return {
    user:state.activeUser  
}

}

  export default connect(mapStateToProps, null)(Userdetails);
SM Chinna
fonte