我正在尝试使用最新版本的react-router v.4设置服务器端渲染。我按照本教程https://react-router.now.sh/ServerRouter进行了操作。
刷新浏览器时出现以下错误:不变违规: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进行服务器渲染的教程。 谁能帮助我,我做错了什么?谢谢。
答案 0 :(得分:3)
解决了!
第一个问题是<Routes />
标记周围有空格。
正确的解决方案:
<ServerRouter location={req.url} context={context}><Routes /></ServerRouter>);
第二个问题是在routes.jsx文件中包含<Header />
标记。
我遇到以下错误(不变违规:元素类型无效:期望字符串(用于内置组件)或类/函数(用于复合组件)但得到:undefined。检查render方法StatelessComponent
)
File Header.jsx包含以下代码行:
import Link from 'react-router';
正确的解决方案:(我忘了放大括号):
import { Link } from 'react-router';
答案 1 :(得分: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'))
答案 2 :(得分:1)
因BrowserRouter
上react-router
不存在,请尝试安装并从react-router-dom
答案 3 :(得分:0)
我相信上面列出的答案已经过时了。截至今天,官方 react-router 文档建议使用StaticRouter代替 ServerRouter 作为服务器端呈现的应用。
可以找到精彩的文档here.
答案 4 :(得分:-1)
对于任何后来的人来说,Ryan Florence已经添加了一个如何实现这一目标的片段。
// 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')
)