Comprensión de React-Redux y mapStateToProps ()

247

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 (ya que vive en la tienda), cuyas claves se pasarán a su componente de destino (se aplica el componente de conexión) como accesorios.

Esto significa que el estado consumido por su componente de destino puede tener una estructura tremendamente diferente del estado almacenado en su tienda.

P: ¿Esto está bien?
P: ¿Es esto esperado?
P: ¿Es esto un anti-patrón?

7
  • 14
    No quiero agregar otra respuesta a la mezcla ... pero me doy cuenta de que nadie responde a tu pregunta ... en mi opinión, NO es un anti-patrón. La clave está en el nombre mapStateTo Props que está pasando propiedades de solo lectura para que un componente las consuma. A menudo uso los componentes de mi contenedor para tomar el estado y cambiarlo antes de pasarlo al componente de presentación. 1 de septiembre de 2017 a las 2:32
  • 3
    De esta manera, mi componente de presentación es mucho más simple ... podría estar renderizando this.props.someDataen lugar de this.props.someKey[someOtherKey].someData... ¿tiene sentido? 1 de septiembre de 2017 a las 2:35
  • 6
    Este tutorial lo explica lo suficientemente bien: learn.co/lessons/map-state-to-props-readme
    Ayan
    25 feb 2018 a las 6:06
  • Hola Pablo, reconsidera tu respuesta elegida.
    vsync
    15 de septiembre de 2018 a las 7:45
  • ¿Volver a considerar cómo? 16/09/18 a las 13:01
125

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

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

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

Y componente Posts

De forma predeterminada 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.postsa su componente, se vuelve un poco mejor

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 actionCreator: deletePost

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

Entonces, bindActionCreatorssolo tomará sus acciones, las envolverá en una 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;
  }, {})
}
3
  • Creo que podría perderme algo, pero ¿de dónde dispatch => bindActionCreators({fetchPosts, deletePost}, dispatch)se transmiten las acciones fetchPostsy deletePost?
    ilyo
    21/10/2017 a las 11:10
  • @ilyo estos son tus creadores de acciones, tienes que importarlos
    webdeb
    21/10/2017 a las 13:58
  • 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 activarán una re-renderización del componente cuando se actualice. 5 de diciembre de 2017 a las 12:45
73

Q: Is this ok?
A: si

P: Is this expected?
Sí, esto es lo esperado (si está usando react-redux).

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

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

Le permite desacoplar su componente de su estado un tiempo adicional, 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, le ayuda a cumplir con el patrón Redux.

Piénselo de esta manera: se supone que una tienda contiene todo el estado de su aplicación.
Para aplicaciones grandes, esto podría contener docenas de propiedades anidadas en muchas capas de profundidad.
No querrás cargar con 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.

5
  • 8
    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 de un lado a otro no ocupa memoria, ya que siempre es el mismo objeto. La única razón por la que 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 un estado que no pertenece a él
    vsync
    15 de septiembre de 2018 a las 7:44
  • @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 hacer referencia al estado global y, por lo tanto, es más legible? 22 de mayo de 2019 a las 15:22
  • Además, ¿cómo podría un componente estropear el estado que no pertenece a él cuando el estado se pasa como inmutable? 22 de mayo de 2019 a las 15:23
  • si el estado es inmutable, 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 un sentido que la ruta a algún estado profundo se pasa directamente al componente como un accesorio, y ese componente es ciego al hecho de que hay Redux detrás de escena. A los componentes no debería importarles qué sistema de gestión estatal se utilice, y deberían funcionar solo con los accesorios que reciben.
    vsync
    22 de mayo de 2019 a las 15:42
  • @vsync Yo agregaría (3). Ser capaz de rastrear y comprender fácilmente el propósito de los códigos al ver fácilmente qué parámetros son importantes
    Dean P
    7 de septiembre a las 8:20
42

Tienes bien la primera parte:

Yes 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 de la tienda.

Al vincular me refiero a que el 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.

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 [email protected]
            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) reaccione -redux detecta que hubo un cambio y notifica o "publica" todos los componentes que están escuchando / suscritos enviando los cambios a sus componentWillReceivePropsque, en este ejemplo, activarán un nuevo filtro de los elementos y actualizarán la pantalla debido al hecho de 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 tremendamente diferente del estado almacenado en su tienda.

No recibí la pregunta, ¡pero 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 en el componente exclusivamente.

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 de reductores con la ayuda de react-redux::[email protected]cualquier componente! Lo que hace que la aplicación de acceso al estado de la tienda Redux sea amplia, mientras que el estado del componente es exclusivo para sí mismo.

5

Este ejemplo de react & redux se basa en el ejemplo de Mohamed Mellouki. Pero valida el uso de reglas de embellecimiento y deshilachado . 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, deberá importarlo desde react-redux . Este ejemplo también vincula el método filterItems, lo que evitará problemas de alcance en el componente . Este código fuente se ha formateado automáticamente con 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.

2

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

import { connect } from 'react-redux';

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

export default AppContainer;

Se explica de manera muy simple y clara 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.

1
2

Es un concepto simple. Redux crea un objeto de estado ubicuo (una tienda) a partir de las acciones en los reductores. Al igual que un componente de React, este estado no tiene que codificarse explícitamente en ningún lugar, pero ayuda a los desarrolladores a ver un objeto de estado predeterminado en el archivo reductor para visualizar lo que está sucediendo. Importa el reductor en el componente para acceder al archivo. Luego, mapStateToProps selecciona solo los pares clave / valor en la tienda que necesita su componente. Piense en ello como Redux creando una versión global de un componente de React

this.state = ({ 
cats = [], 
dogs = []
})

Es imposible cambiar la estructura del estado usando mapStateToProps (). Lo que está haciendo es elegir solo los pares clave / valor de la tienda que el componente necesita y pasar los valores (de una lista de clave / valores en la tienda) a los accesorios (claves locales) en su componente. Haz esto un valor a la vez en una lista. No se pueden producir cambios de estructura en el proceso.

PD: La tienda es un estado local. Los Reductores generalmente también pasan el estado a la base de datos con Action Creators ingresando a la mezcla, pero primero comprenda este concepto simple para esta publicación específica.

PPS Es una buena práctica separar los reductores en archivos separados para cada uno e importar solo el reductor que necesita el componente.

1

Aquí hay un esquema / texto estándar para describir el comportamiento de mapStateToProps:

(Esta es una implementación muy 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} />;
    }
  }
}
1

Sí, usted puede hacer esto. También puede procesar el estado y devolver el objeto.

function mapStateToProps(state){  
  let completed = someFunction (state);
   return {
     completed : completed,  
   }
}
 

Esto sería útil si desea cambiar la lógica relacionada con el estado de la función de renderizado a fuera de ella.

0

Me gustaría reestructurar la declaración que mencionaste, que es:

This means that the state as consumed by your target component can have a wildly different structure from the state as it is stored on your store

Puede decir que el estado consumido por su componente de destino tiene una pequeña parte del estado que se almacena en la tienda redux. En otras palabras, el estado consumido por su componente sería el subconjunto del estado de la tienda redux.

En lo que respecta a la comprensión del método connect (), ¡es bastante simple! El método connect () tiene el poder de agregar nuevos accesorios a su componente e incluso anular los accesorios existentes. Es a través de este método de conexión que también podemos acceder al estado de la tienda redux que nos envía el Proveedor. Una combinación de las cuales funciona a su favor y puede agregar el estado de su tienda redux a los accesorios de su componente.

Arriba hay algo de teoría y le sugiero que mire este video una vez para comprender mejor la sintaxis.

1
  • Lo que quiero decir con estructura diferente va más allá de la cuestión del subconjunto, que también es cierto. Puede, por ejemplo, almacenar una colección (es decir students:{...}) en la tienda que es un objeto con claves únicas, cada una de las cuales corresponde a objetos con propiedades comunes (es decir {[uuid]:{first_name, last_name, age, major, minor}}). Un componente puede convertir eso en una matriz de valores únicos de una de esas propiedades (es decir [...major]). Un componente diferente puede convertirlo de manera diferente (es decir, [... ${first_name} ${last_name}]) 1 sep.2020 a las 14:51
-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);