How to create one store per instance in Redux?

2019-05-07 22:17发布

问题:

Sometimes it would be useful to create one store per instance in Redux applications. The creator of Redux itself created a Gist which describes how to achieve this: https://gist.github.com/gaearon/eeee2f619620ab7b55673a4ee2bf8400

I asked this question already in the Gist but I think StackOverflow would be a better place for this question:

I wonder how to dispatch actions to the component's own special store? Is there a way to access the store-prop of the <Provider /> for each <SubApp /> (and their child-components)?

For example: I have some API classes that call dispatch after fetching data from a remote server. But since I can't import the "normal" store, what would be the best way to deal with custom stores to make them available for other classes/files/services?

Update 1

So I got it working, but I think it's a really dirty way (mind the UGLY? comments inside the code):

Provider:

Create a store per instance by creating the store inside the constructor:

export default class OffersGridProvider extends React.Component {
  constructor(props) {
    super(props)
    this.store = createStore(reducers);
  }

  render() {
    return (
      <Provider store={this.store}>
        <OffersGridContainer offers={this.props.offers} />
      </Provider>
    );
  }
}

Container:

The Provider injects a dispatch method for this store to my OffersGridContainer which I can use to dispatch actions just to the store of this instance:

class OffersGridContainer extends React.Component {    
  componentDidMount() {

    // UGLY???
    const { dispatch } = this.props;

    let destinationIds = [];
    this.props.offers.forEach((offer) => {
      offer.to.forEach((destination) => {
        destinationIds.push(destination.destination);
      });
    });

    // MORE UGLY???
    destinationsApi.getDestinations(dispatch, destinationIds);
  }

  render() {
    return (
      <OffersGridLayout destinations={this.props.destinations} />
    );
  }
}

const mapStateToProps = function(store) {
  return {
    destinations: store.offersGridState.destinations
  };
}

export default connect(mapStateToProps)(OffersGridContainer);

API method:

Just use the dispatch-method that I've passed as argument to my API method:

export function getDestinations(dispatch, ids) {
  const url = $('meta[name="site-url"]').attr('content');

  const filter = ids.map((id) => {
    return `filter[post__in][]=${id}`;
  }).join('&');

  return axios.get(`${url}/wp-json/wp/v2/destinations?filter[posts_per_page]=-1&${filter}`)
    .then(response => {
      dispatch(getOffersGridSuccess(response.data));
      return response;
    });
}

Update 2

Just received a hint about mapDispatchToProps in the comments, so my Container now looks like this:

class OffersGridContainer extends React.Component {    
  componentDidMount() {
    let destinationIds = [];

    this.props.offers.forEach((offer) => {
      offer.to.forEach((destination) => {
        destinationIds.push(destination.destination);
      });
    });

    this.props.getDestinations(destinationIds);
  }

  render() {
    return (
      <OffersGridLayout destinations={this.props.destinations} />
    );
  }
}

const mapStateToProps = function(store) {
  return {
    destinations: store.offersGridState.destinations
  };
}

const mapDispatchToProps = function(dispatch) {
  return {
    getDestinations: function(ids) {
      return destinationsApi.getDestinations(dispatch, ids);
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(OffersGridContainer);

Update 3 (the final answer)

Now everything works! Here's to code:

Provider:

export default class OffersGridProvider extends React.Component {    
  constructor(props) {
    super(props)
    this.store = createStore(reducers, applyMiddleware(thunk));
  }

  render() {
    return (
      <Provider store={this.store}>
        <OffersGridContainer offers={this.props.offers} />
      </Provider>
    );
  }
}

Container:

class OffersGridContainer extends React.Component {
  componentDidMount() {
    const destinationIds = this.props.offers.reduce((acc, offer) => {
      return [...acc, ...offer.to.map(d => d.destination)];
    }, []);

    this.props.getDestinations(destinationIds);
  }

  render() {
    return (
      <OffersGridLayout destinations={this.props.destinations} />
    );
  }
}

const mapStateToProps = function(store) {
  return {
    destinations: store.offersGridState.destinations
  };
}

const mapDispatchToProps = function(dispatch) {
  return {
    getDestinations: function(ids) {
      return dispatch(destinationsApi.getDestinations(ids));
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(OffersGridContainer);

API method:

export function getDestinations(ids) {
  return function(dispatch) {
    const url = $('meta[name="site-url"]').attr('content');

    const filter = ids.map((id) => {
      return `filter[post__in][]=${id}`;
    }).join('&');

    return axios.get(`${url}/wp-json/wp/v2/destinations?filter[posts_per_page]=-1&${filter}`)
      .then(response => {
        return dispatch(getOffersGridSuccess(response.data));
      });
  }
}

回答1:

Instead of making api calls in components directly, I suggest you to use redux-thunk package.

Next, you should pass mapDispatchToProps function as second argument to connect function, to inject action creator functions to component:

import { getDestinations } from '../actions';

class OffersGridContainer extends React.Component {    
  componentDidMount() {
    // Tip.
    const destinationIds = this.props.offers.reduce((acc, offer) => {
      return [...acc, ...offer.to.map(d => d.destination)];
    }, []);

    // instead `destinationsApi.getDestinations(dispatch, destinationIds)`
    // call action creator function
    this.props.getDestinations(destinationIds);
  }

  render() {
    return (
      <OffersGridLayout destinations={this.props.destinations} />
    );
  }
}

const mapStateToProps = function(store) {
  return {
    destinations: store.offersGridState.destinations
  };
}

const mapDispatchToProps = function(dispatch) {
  return {
    // this function will be available in component as   
    // `this.props.getDestinations`.
    getDestinations: function(destinationIds) {
      dispatch(getDestinations(destinationIds));
    }
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(OffersGridContainer);