工程中遇到的问题,所以记录下来。
假设有下面这样的异步路由:
<Route path="/" component={App}>
...
<Route path="dashboard" onEnter={requireAuthentication} component={Dashboard} />
<Route path="login" component={Login} />
</Route>
访问 /dashboard
需要看看用户是否登录过,这个过程可能是一个异步的 API 调用,大概样子如下:
const requireAuthentication = (nextState, replace, cb) => {
loadUserInfo(store).then((user) => {
if (!user) replace('/login')
cb()
})
.catch(err) { cb(err) }
}
在服务器端渲染的时候,代码片段是这样的:
match({
history,
routes: getRoutes(store),
location: req.originalUrl
},
(error, redirectLocation, renderProps) => {
if (redirectLocation) {
res.redirect(redirectLocation.pathname + redirectLocation.search)
} else if (error) {
console.error('ROUTER ERROR:', pretty.render(error))
res.status(500)
req.send('You'd like to say sorry for this exception.')
} else if (renderProps) {
res.send('<!doctype html>\n' +
ReactDOM.renderToString(
YourRootComponent,
store={store} // render the Redux initial data into the server markup
/>))
}
})
macth 确保之前路由定义的一系列异步操作都已经完成。但是,大家如果看 react-router 的文档,则往往不知道其实也需要在 Client 端进行同样的操作:
match({ history, routes }, (error, redirectLocation, renderProps) => {
const { location } = renderProps
ReactDOM.render(
<Provider store={store} key="provider">
<Router history={history} {...renderProps}>
{YourRoutes}
</Router>
</Provider>,
YourContainerElement
)
})
如果不使用 match
,那么你在直接访问 /login
页面的时候,会收到下面警告:
Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) <!-- react-empty: 1 -
(server) <div data-reactroot="warning @ main-c9fe607e216eeb4cd005.js:11144
Server-side React render was discarded. Make sure that your initial render does notcontain any client-side code.(anonymous function) @ main-c9fe607e216eeb4cd005.js:1992
虽然页面仍然可以出来,但是实际上渲染了两次。第一次是服务器端返回了页面,但是客户端由于没有等待异步调用完成就渲染了一次(空页面),因此发现和服务器端“渲染出”的页面不一致,果断丢弃了服务器返回的任何信息(页面+Redux Store),然后在异步调用完成后,又渲染了一次,这显然不是我们要的。
这个问题我花了两个小时才找到原因,因此要记录一下。