【问题标题】:server rendering with react-router v4 and express.js使用 react-router v4 和 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 进行服务器渲染的教程。 谁能帮助我我做错了什么?谢谢。

【问题讨论】:

    标签: javascript node.js reactjs express react-router


    【解决方案1】:

    最大的问题是&lt;BrowserRouter&gt; 预计只有一个孩子,所以你应该将它的孩子包装在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>;
    

    作为次要问题,您将&lt;BrowserRouter&gt; 包含在您的&lt;App&gt; 组件中,因此它将在服务器上呈现。你不想要这个。只有&lt;ServerRouter&gt; 应该在服务器上呈现。您应该将&lt;BrowserRouter&gt; 进一步向上移动客户端组件层次结构以避免这种情况。

    // 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 文件,因为不再需要使用它了。
    • 完整的错误是什么?它是否引用了代码中的特定组件或行?
    • 删除&lt;Routes /&gt; 组件周围的空格。当您的 JSX 被转换为 React.createElement 表达式时,这些被解释为空格。
    【解决方案2】:

    解决了!

    第一个问题是我在&lt;Routes /&gt; 标签周围有空格。

    正确的解决方案:

    <ServerRouter location={req.url} context={context}><Routes /></ServerRouter>);
    

    第二个问题是在 routes.jsx 文件中包含 &lt;Header /&gt; 标签。

    我有以下错误(不变违规:元素类型无效:期望字符串(对于内置组件)或类/函数(对于复合组件)但得到:未定义。检查渲染方法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。

        SSR 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.找到一个很棒的文档

          【讨论】:

            猜你喜欢
            • 2017-06-06
            • 2015-08-18
            • 2017-11-12
            • 2016-08-06
            • 2016-04-11
            • 2016-01-27
            • 1970-01-01
            • 2018-01-06
            • 2017-04-06
            相关资源
            最近更新 更多