使用 react-router v4 和 express.js 进行服务器渲染
Posted
技术标签:
【中文标题】使用 react-router v4 和 express.js 进行服务器渲染【英文标题】:server rendering with react-router v4 and express.js 【发布时间】:2017-05-20 02:25:08 【问题描述】:我正在尝试使用最新版本的 react-router v.4 设置服务器端渲染。我遵循了本教程https://react-router.now.sh/ServerRouter。
刷新浏览器时出现以下错误:Invariant Violation: React.Children.only 预计会收到单个 React 元素子元素。
我的 routes.jsx 文件:
export default () =>
<div>
<Header />
<Match pattern="/" component=Home />
<Match pattern="/about" component=About />
<Miss component=NotFound />
</div>;
在 index.jsx 我正在渲染应用程序
import BrowserRouter from 'react-router';
import Routes from './routes';
ReactDOM.render(<BrowserRouter> <Routes /> </BrowserRouter>, document.getElementById('app'));
现在作为服务器,我使用 express.js。这是我的配置:
import Routes from '../routes';
server.use((req, res) =>
const context = createServerRenderContext();
let markup = renderToString(
<ServerRouter location=req.url context=context > <Routes /> </ServerRouter>);
const result = context.getResult();
if (result.redirect)
res.writeHead(301,
Location: result.redirect.pathname,
);
res.end();
else
if (result.missed)
res.writeHead(404);
markup = renderToString(
<ServerRouter location=req.url context=context> <Routes /> </ServerRouter>);
res.write(markup);
res.end();
);
除了官方的,我没有找到任何使用这个版本的 react-routes 进行服务器渲染的教程。 谁能帮助我我做错了什么?谢谢。
【问题讨论】:
【参考方案1】:最大的问题是<BrowserRouter>
预计只有一个孩子,所以你应该将它的孩子包装在div
中。这样做是为了让 React Router 与环境无关(您不能在 React Native 中渲染 div
,因此 RR 希望您包含适当的包装器)。
export default () =>
<BrowserRouter>
<div>
<Header />
<Match pattern="/" component=Home />
<Match pattern="/about" component=About />
<Miss component=NotFound />
</div>
</BrowserRouter>;
作为次要问题,您将<BrowserRouter>
包含在您的<App>
组件中,因此它将在服务器上呈现。你不想要这个。只有<ServerRouter>
应该在服务器上呈现。您应该将<BrowserRouter>
进一步向上移动客户端组件层次结构以避免这种情况。
// App
export default () =>
<div>
<Header />
<Match pattern="/" component=Home />
<Match pattern="/about" component=About />
<Miss component=NotFound />
</div>;
// index.js
render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('app'))
【讨论】:
感谢您的回答。我解决了这个问题,但仍然遇到同样的错误。 您能用代码的当前状态更新您的问题吗? 我更新了我的代码并删除了 App.jsx 文件,因为不再需要使用它了。 完整的错误是什么?它是否引用了代码中的特定组件或行? 删除<Routes />
组件周围的空格。当您的 JSX 被转换为 React.createElement
表达式时,这些被解释为空格。【参考方案2】:
解决了!
第一个问题是我在<Routes />
标签周围有空格。
正确的解决方案:
<ServerRouter location=req.url context=context><Routes /></ServerRouter>);
第二个问题是在 routes.jsx 文件中包含 <Header />
标签。
我有以下错误(不变违规:元素类型无效:期望字符串(对于内置组件)或类/函数(对于复合组件)但得到:未定义。检查渲染方法StatelessComponent
)
文件 Header.jsx 包含以下代码行:
import Link from 'react-router';
正确的解决方案:(我忘了放大括号):
import Link from 'react-router';
【讨论】:
【参考方案3】:因为react-router
上不存在BrowserRouter
,请尝试从react-router-dom
安装并导入它
【讨论】:
【参考方案4】:对于以后来的人,Ryan Florence 添加了一个关于如何完成此任务的 sn-p。
s-s-r in React Router v4
// routes.js
const routes = [
path: '/',
component: Home,
exact: true
,
path: '/gists',
component: Gists
,
path: '/settings',
component: Settings
]
// components
class Home extends React.Component
// called in the server render, or in cDM
static fetchData(match)
// going to want `match` in here for params, etc.
return fetch(/*...*/)
state =
// if this is rendered initially we get data from the server render
data: this.props.initialData || null
componentDidMount()
// if rendered initially, we already have data from the server
// but when navigated to in the client, we need to fetch
if (!this.state.data)
this.constructor.fetchData(this.props.match).then(data =>
this.setState( data )
)
// ...
// App.js
const App = ( routes, initialData = [] ) => (
<div>
routes.map((route, index) => (
// pass in the initialData from the server for this specific route
<Route ...route initialData=initialData[index] />
))
</div>
)
// server.js
import matchPath from 'react-router'
handleRequest((req, res) =>
// we'd probably want some recursion here so our routes could have
// child routes like ` path, component, routes: [ route, route ] `
// and then reduce to the entire branch of matched routes, but for
// illustrative purposes, sticking to a flat route config
const matches = routes.reduce((matches, route) =>
const match = matchPath(req.url, route.path, route)
if (match)
matches.push(
route,
match,
promise: route.component.fetchData ?
route.component.fetchData(match) : Promise.resolve(null)
)
return matches
, [])
if (matches.length === 0)
res.status(404)
const promises = matches.map((match) => match.promise)
Promise.all(promises).then((...data) =>
const context =
const markup = renderToString(
<StaticRouter context=context location=req.url>
<App routes=routes initialData=data/>
</StaticRouter>
)
if (context.url)
res.redirect(context.url)
else
res.send(`
<!doctype html>
<html>
<div id="root">$markup</div>
<script>DATA = $escapeBadStuff(JSON.stringify(data))</script>
</html>
`)
, (error) =>
handleError(res, error)
)
)
// client.js
render(
<App routes=routes initialData=window.DATA />,
document.getElementById('root')
)
【讨论】:
【参考方案5】:我相信上面列出的答案已经过时了。截至今天,官方 react-router 文档建议使用 StaticRouter 而不是 ServerRouter 用于服务器端呈现的应用程序。
可以在here.找到一个很棒的文档
【讨论】:
以上是关于使用 react-router v4 和 express.js 进行服务器渲染的主要内容,如果未能解决你的问题,请参考以下文章
react-router v4 使用 history 控制路由跳转