4 react routersRoute
As a common react component, it can be used in any componentRoute
Instead of having to define all of them in one place, as in previous versionsRoute
. Therefore, in projects using react router 4, there are oftenRoute
And other components in the same component. For example, the following code:
class App extends Component {
render() {
const { isRequesting } = this.props;
return (
<div>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/home" component={Home} />
</Switch>
</Router>
{isRequesting && <Loading />}
</div>
);
}
}
Page loading effect componentLoading
andRoute
At the same level, so,Home
、Login
And other page components share the outer loading component. When used with Redux, isrequesting is stored in the store of redux,App
As container components in Redux, isrequesting is obtained from the store.Home
、Login
As a container component, the root component of the page will get the required state from the store to render the component. The code evolved as follows:
class App extends Component {
render() {
const { isRequesting } = this.props;
return (
<div>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/home" component={Home} />
</Switch>
</Router>
{isRequesting && <Loading />}
</div>
);
}
}
const mapStateToProps = (state, props) => {
return {
isRequesting: getRequestingState(state)
};
};
export default connect(mapStateToProps)(App);
class Home extends Component {
componentDidMount() {
this.props.fetchHomeDataFromServer();
}
render() {
return (
<div>
{homeData}
</div>
);
}
}
const mapStateToProps = (state, props) => {
return {
homeData: getHomeData(state)
};
};
const mapDispatchToProps = dispatch => {
return {
...bindActionCreators(homeActions, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Home
After the component is mounted, callthis.props.fetchHomeDataFromServer()
This asynchronous action gets the data needed for the page from the server.fetchHomeDataFromServer
The general structure is as follows:
const fetchHomeDataFromServer = () => {
return (dispatch, getState) => {
dispatch(REQUEST_BEGIN);
return fetchHomeData().then(data => {
dispatch(REQUEST_END);
dispatch(setHomeData(data));
});
}
In this way, in thedispatch
setHomeData(data)
Before, willdispatch
The other two actions change isrequesting to controlApp
inLoading
Display and hide of. Normally, a change in isrequesting should only lead toApp
Component rerender without affectingHome
Components. Because after Redux connectHome
Component. In the update phase, shallow comparison will be used to determine whether the received props have changed. If not, the component will not be rerendered.Home
The component does not rely on isrequesting, and the render method should not be triggered.
But the actual result is, every timeApp
The re render of, are accompanied byHome
Rerender of. The optimization done by Redux is wasted!
What is the reason? Finally, I’m at react routerRoute
The culprit is found in the source code of
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.'
)
//Note here that computematch returns a new object each time. In this way, every time route is updated, setstate will reset a new match object
this.setState({
match: this.computeMatch(nextProps, nextContext.router)
})
}
render() {
const { match } = this.state
const { children, component, render } = this.props
const { history, route, staticContext } = this.context.router
const location = this.props.location || route.location
//Notice here that this is the property passed to the component in the route
const props = { match, location, history, staticContext }
if (component)
return match ? React.createElement(component, props) : null
if (render)
return match ? render(props) : null
if (typeof children === 'function')
return children(props)
if (children && !isEmptyChildren(children))
return React.Children.only(children)
return null
}
Route
OfcomponentWillReceiveProps
ThesetState
Set match, which is controlled bycomputeMatch
By calculation,computeMatch
A new object is returned each time. So, every timeRoute
Update (componentwillreceiveprops is called), a new match will be created, and this match will be passed as props to byRoute
In this case, theHome
)。 therefore,Home
Components always receive a new request during the update phasematch
Attribute, which causes the shallow comparison failure of Redux, and then triggers the re rendering of components. In fact, in the case above,Route
Pass on toHome
The other attributes of location, history and staticcontext have not changed. Although match is a new object, the content of the object has not changed (it is always on the same page, the URL has not changed, and the calculation result of match has not changed).
If you think this problem is only encountered with Redux, it’s a big mistake. Take two scenarios without Redux:
-
App
The structure is basically unchanged, except that isrequesting is no longer obtained through Redux, but is maintained as the state of the component itself.Home
Inherited fromReact.PureComponent
,Home
adoptApp
To change isrequesting,App
Re render, for the same reason,Home
It will also be renewed.React.PureComponent
And it’s wasted. - In combination with mobx,
App
andHome
Component passed@observer
modification,App
Listen to isrequesting change rerender, for the same reason,Home
Components are also rerendered.
OneRoute
The optimization of state management library is greatly reduced! It hurts!
I have mentioned this to react router on GitHubissueWe hope to be able tocomponentWillReceiveProps
Make some simple judgments first, and then decide whether to reconsidersetState
. However, it is disappointing that this issue was quickly closed by a collaborator.
Well, it’s better to ask others than yourself, and find a solution by yourself.
There are several ideas
- since
Loading
Put in andRoute
If this problem occurs in components at the same level, thenLoading
Put it in a lower level component,Home
、Login
No matter how many timesLoading
Components. But this method is not a cure,Home
There may still be other definitions in the componentRoute
,Home
The update of dependency state will also lead to these problemsRoute
Re rendering of internal components. In other words, as long as theRoute
This problem cannot be solved. But in react router 4Route
It is impossible to avoid using it completely in container componentsRoute
Yes. - Rewriting of container components
shouldComponentUpdate
Method, the method is feasible, but each component rewrites again, heart tired. -
Next, the idea of 2 is to create a high-order component and rewrite it in the high-order component
shouldComponentUpdate
, ifRoute
If the location property passed has not changed (indicating that it is on the same page), false is returned. Then use this high-level component to wrap each one in theRoute
Components used in.Create a new high-level component
connectRoute
:import React from "react"; export default function connectRoute(WrappedComponent) { return class extends React.Component { shouldComponentUpdate(nextProps) { return nextProps.location !== this.props.location; } render() { return <WrappedComponent {...this.props} />; } }; }
use
connectRoute
packageHome
、Login
:const HomeWrapper = connectRoute(Home); const LoginWrapper = connectRoute(Login); class App extends Component { render() { const { isRequesting } = this.props; return ( <div> <Router> <Switch> <Route exact path="/" component={HomeWrapper} /> <Route path="/login" component={LoginWrapper} /> <Route path="/home" component={HomeWrapper} /> </Switch> </Router> {isRequesting && <Loading />} </div> ); } }
This will solve the problem once and for all.
Let’s think about another scenario, ifApp
The state of use also affectsRoute
For exampleisRequesting
When true, the thirdRoute
The path will also change, assuming that<Route path="/home/fetching" component={HomeWrapper} />
, andHome
It will be used internallyRoute
The path passed (actually through thematch.path
At this time, we need toHome
Component rerender. But because of the complexity of higher-order componentsshouldComponentUpdate
We just make a judgment according to the location, and the location has not changed at this timeHome
It does not re render. It’s a very special scenario, but you want to tell you through this scenario that higher-level componentsshouldComponentUpdate
The decision-making should be made according to the actual business scenario. In most scenarios, the high-level components above are enough.
Route
The use of posture is not simple, and line and cherish it!
Welcome to my official account: the front end of veteran cadres, and receive 21 books from the front end.