Comprender React-Redux y mapStateToProps ()

219

Estoy tratando de entender el método de conexión de react-redux y las funciones que toma como parámetros. En particular mapStateToProps().

Según tengo entendido, el valor de retorno de mapStateToPropsserá un objeto derivado del estado (como vive en la tienda), cuyas claves se pasarán a su componente de destino (al que se aplica la conexión del componente) como accesorios.

Esto significa que el estado tal como lo consume su componente de destino puede tener una estructura muy diferente del estado tal como se almacena en su tienda.

P: ¿Está bien?
P: ¿Se espera esto?
P: ¿Es esto un antipatrón?

Pablo Barría Urenda
fuente
11
No quiero agregar otra respuesta a la mezcla ... pero me doy cuenta de que nadie realmente responde a su pregunta ... en mi opinión, NO es un antipatrón. La clave está en el nombre mapStateTo Props que está pasando propiedades de solo lectura para que un componente las consuma. A menudo usaré los componentes de mi contenedor para tomar el estado y cambiarlo antes de pasarlo al componente de presentación.
Matthew Brent
3
De esta manera, mi componente de presentación es mucho más simple ... podría estar representando this.props.someDataen lugar de this.props.someKey[someOtherKey].someData... ¿tiene sentido?
Matthew Brent
3
Este tutorial lo explica bastante bien: learn.co/lessons/map-state-to-props-readme
Ayan
Hola Pablo, vuelve a considerar la respuesta que elijas.
vsync
¿Volver a considerar cómo?
Pablo Barría Urenda

Respuestas:

56

Q: Is this ok?
A: sí

P: Is this expected?
Sí, esto se espera (si está utilizando react-redux).

P: Is this an anti-pattern?
R: No, esto no es un antipatrón.

Se llama "conectar" su componente o "hacerlo inteligente". Es por diseño.

Le permite desacoplar su componente de su estado una vez más, lo que aumenta la modularidad de su código. También le permite simplificar el estado de su componente como un subconjunto del estado de su aplicación que, de hecho, lo ayuda a cumplir con el patrón de Redux.

Piénselo de esta manera: se supone que una tienda debe contener todo el estado de su aplicación.
Para aplicaciones grandes, esto podría contener docenas de propiedades anidadas en muchas capas de profundidad.
No desea transportar todo eso en cada llamada (caro).

Sin mapStateToPropso algún análogo del mismo, estaría tentado a dividir su estado de otra manera para mejorar el rendimiento / simplificar.

Richard Strickland
fuente
66
No creo que dar acceso a todos y cada uno de los componentes a toda la tienda, por grande que sea, tenga algo que ver con el rendimiento. pasar objetos no ocupa memoria ya que siempre es el mismo objeto. La única razón para llevar a un componente las piezas que necesita es probablemente 2 razones: (1) -Acceso profundo más fácil (2) -Evitar errores en los que un componente podría estropear el estado que no le pertenece
vsync
@vsync ¿Podría explicar cómo eso permite un acceso profundo más fácil? ¿Quiere decir que los accesorios locales ahora se pueden usar en lugar de tener que referirse al estado global y, por lo tanto, es más legible?
Siddhartha
Además, ¿cómo podría un estado desordenado de un componente que no le pertenece cuando el estado se pasa como inmutable?
Siddhartha
si el estado es inmutable, entonces supongo que está bien, pero aún así, como buena práctica, es mejor exponer a los componentes solo las partes relevantes para ellos. Esto también ayuda a otros desarrolladores a comprender mejor qué partes (del objeto de estado ) son relevantes para ese componente. Con respecto al "acceso más fácil", es más fácil en el sentido de que el camino a algún estado profundo se pasa directamente al componente como accesorio, y ese componente es ciego al hecho de que Redux está detrás de escena. A los componentes no les debería importar qué sistema de gestión estatal se usa, y deberían funcionar solo con los accesorios que reciben.
vsync
119

Si es correcto. Es solo una función auxiliar para tener una forma más simple de acceder a sus propiedades de estado

Imagina que tienes una postsclave en tu aplicaciónstate.posts

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

Y componente Posts

Por defecto connect()(Posts), todos los accesorios de estado estarán disponibles para el Componente conectado

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

Ahora, cuando asigna el state.postscomponente a su componente, se vuelve un poco más agradable

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

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

mapDispatchToProps

normalmente tienes que escribir dispatch(anActionCreator())

con bindActionCreatorsusted puede hacerlo también más fácilmente como

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

Ahora puedes usarlo en tu Componente

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

Actualización sobre actionCreators ..

Un ejemplo de un actionCreator: deletePost

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

Entonces, bindActionCreatorssolo tomará sus acciones, las envolverá en la dispatchllamada. (No leí el código fuente de redux, pero la implementación podría verse así:

const bindActionCreators = (actions, dispatch) => {
  return Object.keys(actions).reduce(actionsMap, actionNameInProps => {
    actionsMap[actionNameInProps] = (...args) => dispatch(actions[actionNameInProps].call(null, ...args))
    return actionsMap;
  }, {})
}
webdeb
fuente
Creo que podría perderme algo, pero ¿de dónde se dispatch => bindActionCreators({fetchPosts, deletePost}, dispatch)obtienen las acciones fetchPostsy se deletePostpasan?
ilyo
@ilyo estos son tus creadores de acción, tienes que importarlos
webdeb
2
¡Buena respuesta! Creo que también es bueno enfatizar que este fragmento de código state => state.posts(la mapStateToPropsfunción) le dirá a React qué estados desencadenarán una nueva representación del componente cuando se actualice.
Miguel Péres
38

Tienes la primera parte correcta:

mapStateToPropstiene el estado Store como argumento / parámetro (proporcionado por react-redux::connect) y se usa para vincular el componente con cierta parte del estado store.

Al vincular me refiero al objeto devuelto por mapStateToPropsse proporcionará en el momento de la construcción como accesorios y cualquier cambio posterior estará disponible a través de componentWillReceiveProps.

Si conoce el patrón de diseño de Observer, es exactamente eso o una pequeña variación del mismo.

Un ejemplo ayudaría a aclarar las cosas:

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);

Puede haber otro componente de reacción llamado itemsFiltersque maneja la pantalla y persiste el estado del filtro en el estado de Redux Store, el componente de demostración está "escuchando" o "suscrito" a los filtros de estado de Redux Store, por lo que cada vez que los filtros almacenan cambios de estado (con la ayuda de filtersComponent) -redux detecta que hubo un cambio y notifica o "publica" todos los componentes de escucha / suscritos enviando los cambios a ellos, lo componentWillReceivePropsque en este ejemplo activará un refiltro de los elementos y actualizará la pantalla debido a que el estado de reacción ha cambiado .

Avíseme si el ejemplo es confuso o no es lo suficientemente claro como para proporcionar una mejor explicación.

En cuanto a: Esto significa que el estado consumido por su componente de destino puede tener una estructura muy diferente del estado tal como se almacena en su tienda.

¡No recibí la pregunta, pero solo sé que el estado de reacción ( this.setState) es totalmente diferente del estado de Redux Store!

El estado de reacción se utiliza para manejar el rediseño y el comportamiento del componente de reacción. El estado de reacción está contenido exclusivamente en el componente.

El estado de Redux Store es una combinación de estados de reductores de Redux, cada uno es responsable de administrar una pequeña porción de la lógica de la aplicación. ¡Se puede acceder a esos atributos reductores con la ayuda de react-redux::connect@mapStateToPropscualquier componente! Lo que hace que el estado de la tienda Redux sea accesible en toda la aplicación, mientras que el estado del componente es exclusivo para sí mismo.

Mohamed Mellouki
fuente
5

Este ejemplo de reacción y reducción se basa en el ejemplo de Mohamed Mellouki. Pero valida usando reglas de prettify y linting . Tenga en cuenta que definimos nuestros accesorios y métodos de envío utilizando PropTypes para que nuestro compilador no nos grite. Este ejemplo también incluyó algunas líneas de código que faltaban en el ejemplo de Mohamed. Para usar connect necesitará importarlo desde react-redux . Este ejemplo también vincula el método filterItems, esto evitará problemas de alcance en el componente . Este código fuente ha sido formateado automáticamente usando 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 ejemplo es una buena plantilla como punto de partida para su componente.

Patrick W. McMahon
fuente
2

React-Redux connect se utiliza para actualizar la tienda para cada acción.

import { connect } from 'react-redux';

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

export default AppContainer;

Es muy simple y claramente explicado en este blog .

Puede clonar el proyecto github o copiar y pegar el código de ese blog para comprender la conexión de Redux.

ArunValaven
fuente
buen manual formapStateToProps thegreatcodeadventure.com/…
zloctb
1

Aquí hay un esquema / plantilla para describir el comportamiento de mapStateToProps:

(Esta es una implementación enormemente simplificada de lo que hace un contenedor Redux).

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} />;
  }
}

y después

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
fuente
-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
fuente