React Router导致Redux容器组件重新渲染不必要

时间:2017-11-19 10:47:40

标签: reactjs redux react-router react-redux

这是我的主要代码,App组件连接到Redux的商店:

class App extends Component {
  render() {
    const { requestQuantity } = this.props;
    return (
      <div>
        <Router>
          <Switch>
            <Route exact path="/" component={PostList} />
            <Route path="/login" component={Login} />
            <Route path="/topics" component={PostList} />
          </Switch>
        </Router>
        {requestQuantity > 0 && <Loading />}
      </div>
    );
  }
}

const mapStateToProps = (state, props) => {
  return {
    requestQuantity: getRequestQuantity(state)
  };
};

export default connect(mapStateToProps)(App);

PostList组件也连接到Redux的商店:

class PostList extends Component {
  componentDidMount() {
    this.props.fetchAllPosts();
  }

  render() {
    const { posts} = this.props;
    return (
      // ...
    );
  }

  //...
}

const mapStateToProps = (state, props) => {
  return {
    posts: getPostList(state),
  };
};

const mapDispatchToProps = dispatch => {
  return {
    ...bindActionCreators(postActions, dispatch),
  };
};

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

调用this.props.fetchAllPosts()时,全局状态中的requestQuantity将从0更改为1(请求开始),然后更改为0(请求结束)。因此App将重新渲染两次。但是,App的每次重新呈现也会导致PostList重新呈现,这是我不期望的,因为PostList仅取决于全局posts状态和posts在这两次重新渲染中没有变化。

我检查了React Router的源代码,发现Route的componentWillReceiveProps总是调用setState,它设置了一个新的match对象:

  componentWillReceiveProps(nextProps, nextContext) {
    warning(
      !(nextProps.location && !this.props.location),
      '<Route> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.'
    )

    warning(
      !(!nextProps.location && this.props.location),
      '<Route> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.'
    )

    //the official always set a new match object ignoring whether the nextProps change or not
    this.setState({
      match: this.computeMatch(nextProps, nextContext.router)
    })
  }

传递给match的新PostList道具导致Redux的浅层比较失败并重新渲染。我希望React Router的团队可以在setState之前做一些简单的逻辑,例如使用(===)比较nextProps和this.props中的每个prop,如果没有发生变化,请跳过setState。不幸的是,他们认为这不是什么大不了的事情并且关闭了我的问题。

现在我的解决方案是创建一个HOC:

// connectRoute.js
export default function connectRoute(WrappedComponent) {
  return class extends React.Component {
    shouldComponentUpdate(nextProps) {
      return nextProps.location !== this.props.location;
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

然后使用connectRoute来包装Route中使用的容器:

const PostListWrapper = connectRoute(PostList);
const LoginWrapper = connectRoute(Login);

class App extends Component {
  render() {
    const { requestQuantity } = this.props;
    return (
      <div>
        <Router>
          <Switch>
            <Route exact path="/" component={PostListWrapper} />
            <Route path="/login" component={LoginWrapper} />
            <Route path="/topics" component={PostListWrapper} />
          </Switch>
        </Router>
        {requestQuantity > 0 && <Loading />}
      </div>
    );
  }
}

此外,当React Router与Mobx一起使用时,这个问题也很容易满足。

希望有人能提供更好的解决方案。一个很长的问题。谢谢你的耐心等待。

0 个答案:

没有答案