我们已经熟悉React 服务端渲染(SSR)的基本步骤,现在让我们更进一步利用 React RouterV4 实现客户端和服务端的同构。毕竟大多数的应用都需要用到web前端路由器,所以要让SSR能够正常的运行,了解路由器的设置是十分有必要的
基本步骤
路由器配置
前言已经简单的介绍了React SSR,首先我们需要添加ReactRouter4到我们的项目中
$ yarn add react-router-dom # or, using npm $ npm install react-router-dom
接着我们会描述一个简单的场景,其中组件是静态的且不需要去获取外部数据。我们会在这个基础之上去了解如何完成取到数据的服务端渲染。
在客户端,我们只需像以前一样将我们的的App组件通过ReactRouter的BrowserRouter来包起来。
src/index.js
import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import App from './App'; ReactDOM.hydrate( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById('root') );
在服务端我们将采取类似的方式,但是改为使用无状态的 StaticRouter
server/index.js
app.get('/*', (req, res) => { const context = {}; const app = ReactDOMServer.renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); const indexFile = path.resolve('./build/index.html'); fs.readFile(indexFile, 'utf8', (err, data) => { if (err) { console.error('Something went wrong:', err); return res.status(500).send('Oops, better luck next time!'); } return res.send( data.replace('<div></div>', `<div>${app}</div>`) ); }); }); app.listen(PORT, () => { console.log(`😎 Server is listening on port ${PORT}`); });
StaticRouter组件需要 location和context属性。我们传递当前的url(Express req.url)给location,设置一个空对象给context。context对象用于存储特定的路由信息,这个信息将会以staticContext的形式传递给组件
运行一下程序看看结果是否我们所预期的,我们给App组件添加一些路由信息
src/App.js
import React from 'react'; import { Route, Switch, NavLink } from 'react-router-dom'; import Home from './Home'; import Posts from './Posts'; import Todos from './Todos'; import NotFound from './NotFound'; export default props => { return ( <div> <ul> <li> <NavLink to="https://www.jb51.net/">Home</NavLink> </li> <li> <NavLink to="/todos">Todos</NavLink> </li> <li> <NavLink to="/posts">Posts</NavLink> </li> </ul> <Switch> <Route exact path="https://www.jb51.net/" render={props => <Home {...props} />} /> <Route path="/todos" component={Todos} /> <Route path="/posts" component={Posts} /> <Route component={NotFound} /> </Switch> </div> ); };
现在如果你运行一下程序($ yarn run dev),我们的路由在服务端被渲染,这是我们所预期的。
利用404状态来处理未找到资源的网络请求
我们做一些改进,当渲染NotFound组件时让服务端使用404HTTP状态码来响应。首先我们将一些信息放到NotFound组件的staticContext
import React from 'react'; export default ({ staticContext = {} }) => { staticContext.status = 404; return <h1>Oops, nothing here!</h1>; };
然后在服务端,我们可以检查context对象的status属性是否是404,如果是404,则以404状态响应服务端请求。
server/index.js
// ... app.get('/*', (req, res) => { const context = {}; const app = ReactDOMServer.renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); const indexFile = path.resolve('./build/index.html'); fs.readFile(indexFile, 'utf8', (err, data) => { if (err) { console.error('Something went wrong:', err); return res.status(500).send('Oops, better luck next time!'); } if (context.status === 404) { res.status(404); } return res.send( data.replace('<div></div>', `<div>${app}</div>`) ); }); }); // ...
重定向
补充一下,我们可以做一些类似重定向的工作。如果我们有使用Redirect组件,ReactRouter会自动添加重定向的url到context对象的属性上。
server/index.js (部分)
if (context.url) { return res.redirect(301, context.url); }
读取数据
有时候我们的服务端渲染应用需要数据呈现,我们需要用一种静态的方式来定义我们的路由而不是只涉及到客户端的动态的方式。失去定义动态路由的定义是服务端渲染最适合所需要的应用的原因(译者注:这句话的意思应该是SSR不允许路由是动态定义的)。