【问题标题】:Performances and mergeProps in ReduxRedux 中的性能和 mergeProps
【发布时间】:2017-11-22 08:04:28
【问题描述】:

在我的 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 时遇到的最大痛苦。令人惊讶的是,我没有找到太多关于如何解决它的信息,所以我想知道我是否遗漏了一些基本的东西。

你解决这个问题的方法是什么?

【问题讨论】:

    标签: javascript reactjs redux


    【解决方案1】:

    我认为这样做的唯一方法是像 my question 一样包装你的组件。

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

    这将导致你的渲染函数(或者如果你有一个函数组件,它就是函数本身)被调用,并且 React 对结果进行 DOM 比较,因为以前的道具与当前道具没有相同的引用。

    因此,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));
    

    【讨论】:

      【解决方案2】:

      为什么不将 state 和 dispatch 都传递给 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】:

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

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

        我想理论上第五种方法可能是连接SendButtonparent,让它传递一个非常简单的点击处理程序给按钮,然后让parent em> 担心来自它自己的 props 的参数(即,保持按钮本身完全呈现,并将逻辑放在按钮的父级中)。实际上,我想这几乎就是 @samanime 的建议。

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

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

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

        const mapDispatch = {
            onClickSend : actions.sendMessage
        };
        
        export default connect(mapState, mapDispatch)(MyComponent);
        

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

        【讨论】:

        • 因此,为了将第五种方法推广到我的整个应用程序,我可以在我的每个容器文件中都有一个“通用包装器”组件,它将替换并充当 mergeProps 并呈现实际组件。感谢您的帮助!
        【解决方案4】:

        与其尝试使用 Redux 解决所有问题,不如从容器中传递属性。然后,您还可以冒泡事件(通过回调)。

        我可能会这样实现它:

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

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

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

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

        【讨论】:

          猜你喜欢
          • 2019-11-22
          • 2019-02-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-05-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多