ChatGPT解决这个技术问题 Extra ChatGPT

Understanding React-Redux and mapStateToProps()

I'm trying to understand the connect method of react-redux, and the functions it takes as parameters. In particular mapStateToProps().

The way I understand it, the return value of mapStateToProps will be an object derived from state (as it lives in the store), whose keys will be passed to your target component (the component connect is applied to) as props.

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.

Q: Is this OK? Q: Is this expected? Q: Is this an anti-pattern?

I don't want to add another answer to the mix...but I realise no one actually answers your question...in my opinion, it's NOT an anti-pattern. The key is in the name mapStateToProps you are passing read-only properties for a component to consume. I'll often use my container components to take the state and change it before passing it to the presentation component.
This way my presentational component is much simpler...I might be rendering this.props.someData as opposed to this.props.someKey[someOtherKey].someData... make sense?
This tutorial explains it well enough : learn.co/lessons/map-state-to-props-readme
Hi Pablo, please re-consider your chosen answer.
Re-consider how?

B
BenMorel

Yes, it is correct. Its just a helper function to have a simpler way to access your state properties

Imagine you have a posts key in your App state.posts

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

And component Posts

By default connect()(Posts) will make all state props available for the connected Component

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

Now when you map the state.posts to your component it gets a bit nicer

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

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

mapDispatchToProps

normally you have to write dispatch(anActionCreator())

with bindActionCreators you can do it also more easily like

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

Now you can use it in your Component

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

Update on actionCreators..

An example of an actionCreator: deletePost

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

So, bindActionCreators will just take your actions, wrap them into dispatch call. (I didn't read the source code of redux, but the implementation might look something like this:

const bindActionCreators = (actions, dispatch) => {
  return Object.keys(actions).reduce(actionsMap, actionNameInProps => {
    actionsMap[actionNameInProps] = (...args) => dispatch(actions[actionNameInProps].call(null, ...args))
    return actionsMap;
  }, {})
}

I think I might miss something, but where does dispatch => bindActionCreators({fetchPosts, deletePost}, dispatch) gets the fetchPosts and deletePost actions passed from?
@ilyo these are your action creators, you have to import them
Nice answer! I think it's also nice to emphasize that this chunk of code state => state.posts (the mapStateToProps function) will tell React what states will trigger a re-render of the component when updated.
R
Rob Bednark

Q: Is this ok?
A: yes

Q: Is this expected?
Yes, this is expected (if you are using react-redux).

Q: Is this an anti-pattern?
A: No, this is not an anti-pattern.

It's called "connecting" your component or "making it smart". It's by design.

It allows you to decouple your component from your state an additional time which increases the modularity of your code. It also allows you to simplify your component state as a subset of your application state which, in fact, helps you comply with the Redux pattern.

Think about it this way: a store is supposed to contain the entire state of your application. For large applications, this could contain dozens of properties nested many layers deep. You don't want to haul all that around on each call (expensive).

Without mapStateToProps or some analog thereof, you would be tempted to carve up your state another way to improve performance/simplify.


I don't think that giving each and every component access to the entire store, however large it might be, has anything to do with performance. passing objects around does not take up memory since it's always the same object. The only reason bring to a components the parts it needs is probably 2 reasons: (1) - Easier deep access (2) - Avoid bugs where a component might mess up state not belonging to it
@vsync Could you please explain how that enables easier deep access? Do you mean that the local props can now be used instead of having to refer to the global state and so it is more readable?
Also, how could a component mess up state not belonging to it when the state is passed in as immutable?
if the state is immutable then I guess that's fine, but still, as good practice, it's better to expose to components only the parts relevant to them. This also helps other developers better understand which parts (of the state object) are relevant to that component. Regarding "easier access", it is easier in a sense that the path to some deep state is directly passed to the component as a prop, and that component is blind to the fact there Redux behind the scenes. Components should not care which state management system is used, and they should work only with the props they receive.
@vsync I'd add (3). Being able to easily trace and understand the codes purpose by readily seeing which paramaters are important
H
HoldOffHunger

You got the first part right:

Yes mapStateToProps has the Store state as an argument/param (provided by react-redux::connect) and its used to link the component with certain part of the store state.

By linking I mean the object returned by mapStateToProps will be provided at construction time as props and any subsequent change will be available through componentWillReceiveProps.

If you know the Observer design pattern it's exactly that or small variation of it.

An example would help make things clearer:

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

There can be another react component called itemsFilters that handle the display and persisting the filter state into Redux Store state, the Demo component is "listening" or "subscribed" to Redux Store state filters so whenever filters store state changes (with the help of filtersComponent) react-redux detect that there was a change and notify or "publish" all the listening/subscribed components by sending the changes to their componentWillReceiveProps which in this example will trigger a refilter of the items and refresh the display due to the fact that react state has changed.

Let me know if the example is confusing or not clear enough to provide a better explanation.

As for: 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.

I didn't get the question, but just know that the react state (this.setState) is totally different from the Redux Store state!

The react state is used to handle the redraw and behavior of the react component. The react state is contained to the component exclusively.

The Redux Store state is a combination of Redux reducers states, each is responsible of managing a small portion app logic. Those reducers attributes can be accessed with the help of react-redux::connect@mapStateToProps by any component! Which make the Redux store state accessible app wide while component state is exclusive to itself.


P
Patrick W. McMahon

This react & redux example is based off Mohamed Mellouki's example. But validates using prettify and linting rules. Note that we define our props and dispatch methods using PropTypes so that our compiler doesn't scream at us. This example also included some lines of code that had been missing in Mohamed's example. To use connect you will need to import it from react-redux. This example also binds the method filterItems this will prevent scope problems in the component. This source code has been auto formatted using 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);

This example code is a good template for a starting place for your component.


A
ArunValaven

React-Redux connect is used to update store for every actions.

import { connect } from 'react-redux';

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

export default AppContainer;

It's very simply and clearly explained in this blog.

You can clone github project or copy paste the code from that blog to understand the Redux connect.


good manual formapStateToProps thegreatcodeadventure.com/…
c
coder9833idls

It's a simple concept. Redux creates a ubiquitous state object (a store) from the actions in the reducers. Like a React component, this state doesn't have to be explicitly coded anywhere, but it helps developers to see a default state object in the reducer file to visualise what is happening. You import the reducer in the component to access the file. Then mapStateToProps selects only the key/value pairs in the store that its component needs. Think of it like Redux creating a global version of a React component's

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

It is impossible to change the structure of the state by using mapStateToProps(). What you are doing is choosing only the store's key/value pairs that the component needs and passing in the values (from a list of key/values in the store) to the props (local keys) in your component. You do this one value at a time in a list. No structure changes can occur in the process.

P.S. The store is local state. Reducers usually also pass state along to the database with Action Creators getting into the mix, but understand this simple concept first for this specific posting.

P.P.S. It is good practice to separate the reducers into separate files for each one and only import the reducer that the component needs.


t
theUtherSide

Here's an outline/boilerplate for describing the behavior of mapStateToProps:

(This is a vastly simplified implementation of what a Redux container does.)

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

and next

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

D
David Buck

Yes, you can do this. You can also even process the state and return the object.

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

This would be useful if you want to shift the logic related to state from render function to outside of it.


R
Ravi Veliyat

I would like to re-structure the statement that you mentioned which is:

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

You can say that the state consumed by your target component has a small portion of the state that is stored on the redux store. In other words, the state consumed by your component would be the sub-set of the state of the redux store.

As far as understanding the connect() method is concerned, it's fairly simple! connect() method has the power to add new props to your component and even override existing props. It is through this connect method that we can access the state of the redux store as well which is thrown to us by the Provider. A combination of which works in your favor and you get to add the state of your redux store to the props of your component.

Above is some theory and I would suggest you look at this video once to understand the syntax better.


What I mean by different structure goes beyond the sub-set issue, which is also true. You may, for instance, store a collection (i.e. students:{...}) in the store that's an object with unique keys, each of them corresponding to objects with common properties (i.e. {[uuid]:{first_name, last_name, age, major, minor}}). A component may turn that into an array of unique values of one of those properties (i.e. [...major]). A different component may convert it differently (i.e. [...${first_name} ${last_name}])
S
SM Chinna
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);