最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Performances and mergeProps in Redux - Stack Overflow

programmeradmin5浏览0评论

In my redux containers, I have to dispatch pretty plex actions taking a lot of properties from the store. I cannot find the right pattern to tackle the problem without crushing the performances.

Let's take the example of a container that would only contain a Send button to send a message:

(For such a small example, any of the following approach would work well, I am just trying to illustrate a problem I encounter in way bigger containers.)

Naive approach - Pass all arguments to the ponent

function mapStateToProps(state) {
  return {
    user:           selectors.selectedUser(state),
    title:          selectors.title(state),
    message:        selectors.message(state),
  };
}

function dispatchProps(state) {
  return {
    onClickSend: function(user, title, message) {
      actions.sendMessage({user, title, message});
    }
  };
}

If I do that, my simple Send button will have to be aware of a lot of useless properties:

SendBtn.propTypes = {
  user:               PropTypes.string,
  title:              PropTypes.string,
  message:            PropTypes.string,
  onClickSend:        PropTypes.func,
}

And also to call onClickSend with all those props:

  onClickSend(user, title, message)

That's way too much knowledge for the Send button. The button should only know it has to call onClickSend when we click on it, this ponent is a simple button. It shouldn't know about any other props. Also, in a more plex case, it could be not just 2 props (title and message) that are needed but 5 or 10.

Another problem is performances, everytime I am going to modify the message or the title (=== on every keystroke), the send button is going to be re-rendered.

My current approach - Use mergeProps to pass arguments to the actions

The current approach of my app is currently using is relying on mergeProps:

function mapStateToProps(state) {
  return {
    user:           selectors.selectedUser(state),
    title:          selectors.title(state),
    message:        selectors.message(state),
  };
}

function mergeProps(stateProps, dispatchProps, ownProps) {

  const {user, title, message} = stateProps;

  const newProps =  {
    onClickSend: actions.sendMessage.bind(null, {
      user,
      title,
      message
    });
  };

  return R.mergeAll([stateProps, dispatchProps, ownProps, newProps]);
}

I find this approach way better because the Send button only has to know that it must fire its unique property: onClickSend when clicked. No need to worry about user, title or message.

However, this approach has a huge drawback: the reference of onClickSend is going to change everytime the store changes, which would lead to really poor performances on larger real-life cases.

A third approach - Access state from redux-thunk

A solution to the performance issue could be to use redux-thunk and access the state directly in the action.

// Action file
var sendMessageAction = createAction("SEND_MESSAGE", function(dispatch, getState) {
  // executed by redux thunk
  const state = getState();

  const args = {
    user:    selectors.selectedUser(state),
    title:   selectors.title(state),
    message: selectors.message(state),
  }
  dispatch(sendMessage(args))
})

// Container file
function mapDispatchToProps(dispatch) {
  onClickSend: dispatch(sendMessageAction())
}

But I don't like this approach because:

  • actions would access the store - doesn't it break the unidirectional flow?
  • actions would be aware of the selectors - bad separation of concern?
  • actions would bee plex objects

Your approach

I have worked for a while on a big redux app now and it is by far the biggest pain I have with Redux. Surprisingly, I don't find too much information about how to solve it so I'm wondering if I'm missing something elementary.

What is your approach of that problem?

In my redux containers, I have to dispatch pretty plex actions taking a lot of properties from the store. I cannot find the right pattern to tackle the problem without crushing the performances.

Let's take the example of a container that would only contain a Send button to send a message:

(For such a small example, any of the following approach would work well, I am just trying to illustrate a problem I encounter in way bigger containers.)

Naive approach - Pass all arguments to the ponent

function mapStateToProps(state) {
  return {
    user:           selectors.selectedUser(state),
    title:          selectors.title(state),
    message:        selectors.message(state),
  };
}

function dispatchProps(state) {
  return {
    onClickSend: function(user, title, message) {
      actions.sendMessage({user, title, message});
    }
  };
}

If I do that, my simple Send button will have to be aware of a lot of useless properties:

SendBtn.propTypes = {
  user:               PropTypes.string,
  title:              PropTypes.string,
  message:            PropTypes.string,
  onClickSend:        PropTypes.func,
}

And also to call onClickSend with all those props:

  onClickSend(user, title, message)

That's way too much knowledge for the Send button. The button should only know it has to call onClickSend when we click on it, this ponent is a simple button. It shouldn't know about any other props. Also, in a more plex case, it could be not just 2 props (title and message) that are needed but 5 or 10.

Another problem is performances, everytime I am going to modify the message or the title (=== on every keystroke), the send button is going to be re-rendered.

My current approach - Use mergeProps to pass arguments to the actions

The current approach of my app is currently using is relying on mergeProps:

function mapStateToProps(state) {
  return {
    user:           selectors.selectedUser(state),
    title:          selectors.title(state),
    message:        selectors.message(state),
  };
}

function mergeProps(stateProps, dispatchProps, ownProps) {

  const {user, title, message} = stateProps;

  const newProps =  {
    onClickSend: actions.sendMessage.bind(null, {
      user,
      title,
      message
    });
  };

  return R.mergeAll([stateProps, dispatchProps, ownProps, newProps]);
}

I find this approach way better because the Send button only has to know that it must fire its unique property: onClickSend when clicked. No need to worry about user, title or message.

However, this approach has a huge drawback: the reference of onClickSend is going to change everytime the store changes, which would lead to really poor performances on larger real-life cases.

A third approach - Access state from redux-thunk

A solution to the performance issue could be to use redux-thunk and access the state directly in the action.

// Action file
var sendMessageAction = createAction("SEND_MESSAGE", function(dispatch, getState) {
  // executed by redux thunk
  const state = getState();

  const args = {
    user:    selectors.selectedUser(state),
    title:   selectors.title(state),
    message: selectors.message(state),
  }
  dispatch(sendMessage(args))
})

// Container file
function mapDispatchToProps(dispatch) {
  onClickSend: dispatch(sendMessageAction())
}

But I don't like this approach because:

  • actions would access the store - doesn't it break the unidirectional flow?
  • actions would be aware of the selectors - bad separation of concern?
  • actions would bee plex objects

Your approach

I have worked for a while on a big redux app now and it is by far the biggest pain I have with Redux. Surprisingly, I don't find too much information about how to solve it so I'm wondering if I'm missing something elementary.

What is your approach of that problem?

Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Jun 20, 2017 at 0:24 MathieuMathieu 1431 silver badge7 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 5

You're missing a fourth approach (similar to your mergeProps option): have the parent ponent pass a bound-up function or callback closure that captures those values, like:

// using bind()
<SendButton onClick={sendMessage.bind(null, user, title, message)}

// or, as an inline arrow function
SendButton onClick={() => this.sendMessage(user, title, message)}

I suppose in theory a fifth approach might be to connect the parent of the SendButton, have it pass a very simple click handler to the button, and let the parent worry about the arguments from its own props (ie, keep the button itself pletely presentational, and put the logic in the parent of the button). Actually, I guess that's pretty much the suggestion that @samanime had.

Which approach you use is really up to you. I personally would probably lean towards that last one, just so that it doesn't re-create a function every time there's a re-render. (In particular, I would avoid the mergeProps approach, as that will re-create a function every time the store updates, which will be even more often than the ponent re-renders.)

I actually addressed your questions about whether accessing state in thunks breaks "uni-directional data flow" in my blog post Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability. As a summary, I don't think it actually does break that unidirectional flow, and my opinion is that accessing state in thunks is a pletely valid approach.

As a side note, I generally remend that people use the object shorthand argument for binding methods with connect, instead of writing an actual mapDispatchToProps function:

const mapDispatch = {
    onClickSend : actions.sendMessage
};

export default connect(mapState, mapDispatch)(MyComponent);

To me, there's almost never a reason to actually write out a separate mapDispatch function (which I also talk about in my post Idiomatic Redux: Why Use Action Creators?).

Instead of trying to use Redux to solve everything, you might be better off passing down properties from your containers. You can then also bubble up events (via callbacks).

I might implement it like this:

class MessageSender extends React.Component {
  // other stuff

  handleSend() { this.props.sendMessage(this.props); // from mapActionsToProps }

  render() {
      // from mapStateToProps at an appropriate level
      const { user, title, body } = this.props;

      return (
        <div>
          { /* other stuff */ }
          <SendButton onClick={ this.handleSend } />
        </div>
      );
  }
}

If you send the values to the more appropriate level, then let things like buttons just trigger events, you can get a somewhat cleaner implementation.

It's hard to give super specific advice with a somewhat theoretical example, but I do want to point out that is definitely possible to over-use Redux.

I remend looking at where you need the data for the app (ignoring the actions), then see where you'll already have the data you need, then see where it makes the most sense to dispatch the action from.

Why not pass both the state and dispatch into mergeProps:

const mapStateToProps = (state) => ({state});
const mapDispatchToProps = (dispatch) => ({dispatch});

const mergeProps = (stateProps, dispatchProps) => {
  const {state} = stateProps;
  const {dispatch} = dispatchProps;

  return {
    onClickSend: () => {
      dispatch(
        actions.sendMessage({
          user: selectors.selectedUser(state),
          title: selectors.title(state),
          message: selectors.message(state),
        })
      );
    }
  };
}

I think the only way to do this is to wrap your ponent like in my question.

At the moment your mapStateToProps returns a new object that may have the same values as previous render.

That would cause your render function (or if you have a functional ponent it would be the function itself) to be called and React do a DOM pare on the result because previous props does not have the same reference as current props.

So connect would not return a ponent for you that is a pure ponent.

To solve that you can memoize mapStateToProps, assuming your selectors.selectedUser and other selector functions are created with reselect, are memoized or return primitive values:

import { createSelector, defaultMemoize } from 'reselect';
const createMemoizedState = () =>
  //return a memoized function that will return previous
  // result if user, title and message are same as last time
  defaultMemoize((user, title, message) => ({
    user,
    title,
    message,
  }));
function mapStateToProps() {
  //initialize memoized function for creating state
  const createState = createMemoizedState();
  //return a function that will call memoized crate state
  //  function. if user, title or message doesn't change
  //  then returned object won't have a different reference
  //  {user:'hi'}!=={user:'hi'} is true but
  //  crateState('hi') !== createState('hi') is false
  return state =>
    createState(
      //assuming the following functions return primitive
      //or are memoized as well (createSelector from reselect)
      selectors.selectedUser(state),
      selectors.title(state),
      selectors.message(state)
    );
}

Also use React.memo if you have a functional ponent

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(React.memo(functionalComponent));
发布评论

评论列表(0)

  1. 暂无评论