【问题标题】:Refresh or manual URL typing in SSR React App changes a className在 SSR React App 中刷新或手动输入 URL 会更改类名
【发布时间】:2020-06-10 22:17:07
【问题描述】:

该应用最初是使用 CRA 制作并转换为 SSR。该应用程序运行良好,但仅当我刷新应用程序或手动输入特定页面的 URL 时才会出现问题。然后将中间 div 的类名覆盖为基本路由的中间 div 的类名。

例如从这个改变:

<div id="root">
   <div class="header">..</div>
   <div class="newlist">..</div>
   <div class="footer">..</div>   
</div>

到这里:

<div id="root">
   <div class="header">..</div>
   <div class="main-flex-container">..</div>
   <div class="footer">..</div>   
</div>

更多信息在这里是我的 src/index.js:

import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import configureStore, { history } from './redux/store/index';
import AppRouter from './routers/AppRouter';
import './styles/styles.scss';

const store = configureStore();

require('dotenv').config();

const isServer = typeof window !== 'undefined';

if (isServer) {
  ReactDOM.hydrate(
    <Provider store={store}>
      <BrowserRouter>
        <AppRouter />
      </BrowserRouter>
    </Provider>,
    document.getElementById('root'),
  );
}

这是我的 server.js:

import express from 'express';
import fs from 'fs';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { Provider } from 'react-redux';
import configureStore from '../src/redux/store/index';
import AppRouter from '../src/routers/AppRouter';
import { StaticRouter } from 'react-router-dom';

const app = express();
const store = configureStore();

app.use(express.static(path.resolve(__dirname, '..', 'build')));

app.use('/*', (req, res, next) => {
  fs.readFile(path.resolve('./build/index.html'), 'utf-8', (err, data) => {
    if (err) {
      console.log(err);
      return res.send(500).send('Error happened!');
    }
    return res.send(
      data.replace(
        '<div id="root"></div>',
        `<div id="root">${ReactDOMServer.renderToString(
          <Provider store={store}>
            <StaticRouter location={req.url} context={{}}>
              <AppRouter />
            </StaticRouter>
          </Provider>,
        )}</div>`,
      ),
    );
  });
});

app.listen(3000, () => {
  console.log('Listening...');
});

如果这还不够信息,这里是 GitHub 上项目的 link

【问题讨论】:

    标签: reactjs sass react-router server-side-rendering


    【解决方案1】:

    所以这里问题的症结似乎是,当在服务器上渲染时,路由器没有像我们预期的那样根据 url 渲染正确的组件。

    发生的不是类名被更改,而是我们实际上总是在渲染根页面,然后在你有机会查看之前 react 用客户端上的“新任务”页面替换内容它在检查器中。

    总结:当在 express 中使用app.use 和路径时,我们在指定路径上挂载了一个 express 中间件,并且 express 从提供给中间件的req.url 中删除了“挂载点”。在其他解决方案中,我们可以使用req.originalUrl 来获取实际的 url。 req.originalUrl 是 express 提供的一个属性,它提供了任何中间件更改之前的 url。


    让我们调查一下!

    如果我们将呈现您的应用程序的代码提取到一个单独的变量并记录它,我们将看到记录的 HTML 字符串是带有“新列表”按钮的页面。

    const renderedApp = ReactDOMServer.renderToString(
      <Provider store={store}>
        <StaticRouter location={req.url} context={{}}>
          <AppRouter />
        </StaticRouter>
      </Provider>,
    );
    console.log('Rendered app', renderedApp);
    return res.send(
      data.replace(
        '<div id="root"></div>',
        `<div id="root">${renderedApp}</div>`,
      ),
    );
    

    访问localhost:3000/l/new并查看日志:

    Rendered app <div class="header"><a class="header__button" id="header__colabico" href="/">COLABI.CO</a><div class="header__right"><button class="header__button" id="header__tweet">TWEET</button><button class="header__button" id="header__logout">LOGOUT</button></div></div><div class="main-flex-container"><a class="home__button" href="/l/new"> <!-- -->NEW LIST<!-- --> </a><p class="home__infotext"> <!-- -->Start by pressing that big button up there!<!-- --> </p></div><div class="footer"><div class="footer__left"><a class="footer__button" href="/privacy">PRIVACY</a><a class="footer__button" href="/terms">TERMS</a></div><p class="footer__colabico"> © colabi.co</p></div>
    

    这看起来很像根页面,而不是新的列表页面。

    另外:将渲染放在单独的行而不是模板字符串中的内联也有助于语法突出显示,这是一个很好的奖励。

    旁白 2:通过添加此日志记录,您还将看到您的代码在访问根 url 时没有运行。造成这种情况的原因可能是因为一开始的静态中间件,它将在您的构建文件夹中的index.html 页面上匹配。

    现在我们已经相当确定问题出在哪里,让我们试着看看它可能是什么原因。

    StaticRouter 接受位置作为道具,我们为此传入req.url。让我们验证一下,这就是我们的想法。

    console.log('URL:', req.url);
    

    访问localhost:3000/l/new时,会记录以下内容:

    URL: /
    

    这肯定不是我们所期望的!

    我去查找req.url的文档,看看可能是什么问题,找到了这个方法:

    req.originalUrl

    我看不到req.url 的文档,但这里有一个有用的框来解释为什么会这样(req.url 来自 Node 的 http 模块)。

    登录req.originalUrl 也只是为了看看这对我们有什么好处,果然:

    original URL: /l/new
    URL: /
    

    req.originalUrl 有我们需要的东西!

    此时我四处寻找参考资料来解释为什么在我们的例子中req.url 可能是/,结果证明是因为这个:

    当我们使用app.use 时,我们在指定路径上挂载了一个快速中间件,而不是设置一个路由处理程序(可能是app.get 或类似的),并且中间件从他们自己的网址,因为他们认为他们的root。当我们指定要挂载在/* 的中间件时,通配符匹配所有路由并删除整个url。

    换句话说,我们可以通过几种不同的方式解决这个问题:

    1. 保持原样,但改用req.originalUrl
    2. app.use (app.use((req, res, next) =&gt; {/* your code */})) 中删除路径
    3. app.use 更改为 app.get

    希望有帮助!

    【讨论】:

    猜你喜欢
    • 2021-08-20
    • 2020-10-06
    • 1970-01-01
    • 2019-02-14
    相关资源
    最近更新 更多