刷新或手动 URL 在 SSR React App 中键入会更改类名

Refresh or manual URL typing in SSR React App changes a className

App最初是用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

所以你的问题的关键似乎是当在服务器上呈现时,路由器没有像我们预期的那样根据 url 呈现正确的组件。

发生的事情并不是 class 名称被更改,而是我们实际上一直在渲染根页面,然后在您获取之前用客户端上的 "new task" 页面反应替换内容有机会在检查器中查看它。

总结:当在 express 中使用带有路径的 app.use 时,我们在指定的路径上安装了一个 express 中间件,并且 express 从 req.url 提供给中间件。在其他解决方案中,我们可以使用 req.originalUrl 来获得实际的 url。 req.originalUrl 是由 express 提供的 属性,它给出 url 任何中间件更改之前的状态。


让我们调查一下!

如果我们将呈现您的应用程序的代码提取到一个单独的变量中并记录下来,我们将看到记录的 HTML 字符串是带有 "New List" 按钮的页面。

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 或类似的),并且中间件删除了他们从他们自己的 url 安装的路径,因为他们认为他们的 root。当我们指定在/*挂载中间件时,通配符匹配所有路由,整个url被移除。

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

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

希望对您有所帮助!