Javascript 使用react router v4和express.js进行服务器渲染

Javascript 使用react router v4和express.js进行服务器渲染,javascript,node.js,reactjs,express,react-router,Javascript,Node.js,Reactjs,Express,React Router,我正在尝试使用最新版本的react router v.4设置服务器端渲染。我遵循了这个教程 刷新浏览器时,我收到以下错误:不变冲突:React.Children。仅应接收单个React元素子元素。 我的路由.jsx文件: export default () => <div> <Header /> <Match pattern="/" component={Home} /> <Match pattern="/about"

我正在尝试使用最新版本的react router v.4设置服务器端渲染。我遵循了这个教程

刷新浏览器时,我收到以下错误:不变冲突:React.Children。仅应接收单个React元素子元素。

我的路由.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'));
从“反应路由器”导入浏览器路由器;
从“./Routes”导入路由;
ReactDOM.render(,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();
  }
});
从“../Routes”导入路由;
服务器使用((请求、恢复)=>{
const context=createServerRenderContext();
让markup=renderToString(
);
const result=context.getResult();
if(result.redirect){
文书标题(301{
位置:result.redirect.pathname,
});
res.end();
}否则{
如果(结果未命中){
书面记录(404);
markup=renderToString(
);
}
res.write(标记);
res.end();
}
});
除了官方版本外,我没有找到任何关于此版本react routes的服务器渲染教程。
谁能帮我一下我做错了什么?谢谢

最大的问题是
只希望有一个孩子,因此您应该将它的孩子包装在
div
中。这样做是为了使React路由器不受环境影响(您不能在React Native中呈现
div
,因此RR希望您包含适当的包装器)

导出默认值()=>
;
作为第二个问题,您将
包含在
组件中,因此它将在服务器上呈现。你不想要这个。只有
应该在服务器上呈现。您应该将
进一步向上移动客户端组件层次结构,以避免出现这种情况

// 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'))
//应用程序
导出默认值()=>
;
//index.js
渲染((
),document.getElementById('app'))

已解决

第一个问题是,
标记周围有空格

正确的解决方案:

<ServerRouter location={req.url} context={context}><Routes /></ServerRouter>);
正确的解决方案:(我忘了放花括号):


由于
react-router
上不存在
BrowserRouter
,请尝试从
react-router-dom

中安装并导入它,以便以后的任何人使用,Ryan Florence添加了一段如何实现此目的的代码

//routes.js
常数路由=[
{
路径:“/”,
组成部分:家庭,
确切地说:是的
},
{
路径:'/gists',
组成部分:GIST
},
{
路径:'/settings',
组件:设置
}
]
//组成部分
类Home扩展了React.Component{
//在服务器渲染或cDM中调用
静态获取数据(匹配){
//我想在这里为params等做“匹配”。
返回获取(/*…*/)
}
状态={
//如果这是最初渲染的,我们将从服务器渲染中获取数据
数据:this.props.initialData | | null
}
componentDidMount(){
//如果最初呈现,我们已经有来自服务器的数据
//但是当在客户端中导航到时,我们需要获取
如果(!this.state.data){
this.constructor.fetchData(this.props.match)。然后(data=>{
this.setState({data})
})
}
}
// ...
}
//App.js
const App=({routes,initialData=[]})=>(
{routes.map((路由,索引)=>(
//为此特定路由从服务器传入initialData
))}
)
//server.js
从“反应路由器”导入{matchPath}
HandlerRequest((请求、回复)=>{
//我们可能需要一些递归,这样我们的路线就可以
//子路由,如`{path,component,routes:[{route,route}]}`
//然后减少到匹配路径的整个分支,但是
//出于说明目的,坚持平面布线配置
常量匹配=路由。减少((匹配,路由)=>{
常量匹配=匹配路径(req.url、route.path、route)
如果(匹配){
推({
路线,,
匹配,
承诺:route.component.fetchData?
route.component.fetchData(匹配):Promise.resolve(null)
})
}
复赛
}, [])
if(matches.length==0){
资源状态(404)
}
const promises=matches.map((match)=>match.promise)
承诺。所有(承诺)。然后(…数据)=>{
常量上下文={}
常量标记=renderToString(
)
if(context.url){
res.redirect(context.url)
}否则{
res.send(`
${markup}
DATA=${escapeBadStuff(JSON.stringify(DATA))}
`)
}
},(错误)=>{
handleError(res,error)
})
})
//client.js
渲染(
,
document.getElementById('root'))
)

我认为上面列出的答案已经过时了。截至今天,官方的文件建议使用服务器端渲染应用程序代替ServerRouter


您可以找到精彩的文档

谢谢您的回答。我修复了此问题,但仍然出现相同的错误。您能用代码的当前状态更新您的问题吗?我更新了代码并删除了App.jsx文件,因为不再需要使用它。完整错误是什么?它是否引用了代码中的特定组件或行?请删除
组件周围的空格。当JSX转换为
React.createElement
表达式时,这些将被解释为空格。
<ServerRouter location={req.url} context={context}><Routes /></ServerRouter>);
import Link  from 'react-router';
 import { Link } from 'react-router';
// 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')
)