Redux中的性能和mergeProp

时间:2017-06-20 00:24:15

标签: javascript reactjs redux

在我的redux容器中,我必须从商店中调出很多复杂的动作。在没有破坏表演的情况下,我找不到合适的模式来解决问题。

我们以一个只包含发送按钮的容器为例发送消息:

(对于这样一个小例子,以下任何一种方法都可以正常工作,我只是想说明我遇到的更大容器的问题。)

天真的方法 - 将所有参数传递给组件

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

如果我这样做,我的简单发送按钮必须知道很多无用的属性:

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

还要用所有这些道具调用onClickSend:

  onClickSend(user, title, message)

对于“发送”按钮的了解太多了。按钮应该只知道当我们点击它时必须调用onClickSend,这个组件是一个简单的按钮。它不应该知道任何其他道具。此外,在更复杂的情况下,它可能不仅仅需要2个道具(标题和消息),而是5或10个。

另一个问题是表演,每次我要修改信息或标题(每次击键时===),发送按钮都将被重新渲染。

我当前的方法 - 使用mergeProps将参数传递给操作

我的应用目前正在使用的方法是依赖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]);
}

我发现这种方法更好,因为“发送”按钮只需知道它必须触发其唯一属性:单击时onClickSend。无需担心用户,标题或消息。

然而,这种方法有一个很大的缺点:onClickSend的引用会在每次商店更改时发生变化,这会导致在更大的现实案例中表现非常糟糕。

第三种方法 - 从redux-thunk访问状态

性能问题的解决方案可能是使用redux-thunk并直接在动作中访问状态。

// 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())
}

但我不喜欢这种方法,因为:

  • 操作会访问商店 - 它是否会破坏单向流?
  • 行动会意识到选择者 - 关注点分离不好?
  • 动作将成为复杂的对象

您的方法

我现在已经在一个大型的redux应用程序上工作了一段时间,这是迄今为止我对Redux的最大痛苦。令人惊讶的是,我没有找到太多关于如何解决它的信息,所以我想知道我是否遗漏了一些基本的东西。

你对这个问题有什么看法?

4 个答案:

答案 0 :(得分:4)

你错过了第四种方法(类似于你的mergeProps选项):让父组件传递一个捕获这些值的绑定函数或回调闭包,例如:

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

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

我认为理论上第五种方法可能是连接SendButton,让它通过一个非常简单的点击处理程序到按钮,并让担心来自其自身道具的参数(即,保持按钮本身完全呈现,并将逻辑放在按钮的父级中)。实际上,我猜这几乎是@samanime的建议。

您使用哪种方法取决于您。我个人可能会倾向于最后一个,只是为了每次重新渲染时它都不会重新创建一个函数。 (特别是,我会避免mergeProps方法,因为每次商店更新时都会重新创建一个函数,这比组件重新渲染更频繁。)

我实际上在我的博文Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability中解决了关于访问状态是否打破“单向数据流”的问题。总而言之,我认为它确实没有打破单向流,我认为以thunks方式访问状态是一种完全有效的方法。

作为旁注,我通常建议人们使用对象速记参数来绑定connect的方法,而不是编写实际的mapDispatchToProps函数:

const mapDispatch = {
    onClickSend : actions.sendMessage
};

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

对我而言,几乎没有理由真正写出单独的mapDispatch函数(我在帖子Idiomatic Redux: Why Use Action Creators?中也会谈到这一点)。

答案 1 :(得分:3)

不是尝试使用Redux来解决所有问题,而是最好从容器中传递属性。然后,您还可以冒泡(通过回调)。

我可能会这样实现:

one

如果您将值发送到更合适的级别,那么让按钮之类的东西只是触发事件,您可以获得更清晰的实现。

很难用一些理论上的例子给出超级具体的建议,但我想指出绝对有可能过度使用Redux。

我建议您查看应用程序数据的位置(忽略操作),然后查看您已经拥有所需数据的位置,然后查看从哪里发送最有意义的操作。

答案 2 :(得分:0)

为什么不同时将状态和调度传递到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),
        })
      );
    }
  };
}

答案 3 :(得分:0)

我认为唯一的方法就是像my question中那样包装组件。

此刻,您的mapStateToProps返回一个新对象,该对象可能具有与先前渲染相同的值。

这将导致您的渲染函数(或者如果您具有功能组件,它将是函数本身)被调用,并且React会对结果进行DOM比较,因为先前的道具与当前道具的引用不同。 / p>

因此connect不会为您返回纯组件。

要解决此问题,您可以记住mapStateToProps,假设您的selectors.selectedUser和其他选择器函数是用reselect创建的,则被记住或返回原始值:

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

如果您有功能组件,也可以使用React.memo

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(React.memo(functionalComponent));