这是我的主要代码, 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)
})
}
传递给 PostList
的新 match
道具导致Redux 's shallow comparison fails and re-rendering occurs. I hope React Router'团队可以在 setState
之前做一些简单的逻辑,比如使用( ===
)比较nextProps和this.props中的每个道具,如果没有发生变化,跳过 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一起使用时,这个问题也很容易满足 .
希望有人能提供更好的解决方案一个很长的问题 . 谢谢你的耐心 .