34

我正在尝试让同构的 Node.js、Express、Webpack、React 应用程序正常工作。我收到以下错误。关于如何修复它的任何建议?

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
 (client) rgin:0;display:flex;-webkit-align-items:
 (server) rgin:0;display:flex;align-items:center;j

warning @   warning.js:45
ReactMount._mountImageIntoNode  @   ReactMount.js:807
wrapper @   ReactPerf.js:66
mountComponentIntoNode  @   ReactMount.js:268
Mixin.perform   @   Transaction.js:136
batchedMountComponentIntoNode   @   ReactMount.js:282
Mixin.perform   @   Transaction.js:136
ReactDefaultBatchingStrategy.batchedUpdates @   ReactDefaultBatchingStrategy.js:62
batchedUpdates  @   ReactUpdates.js:94
ReactMount._renderNewRootComponent  @   ReactMount.js:476
wrapper @   ReactPerf.js:66
ReactMount._renderSubtreeIntoContainer  @   ReactMount.js:550
ReactMount.render   @   ReactMount.js:570
wrapper @   ReactPerf.js:66
(anonymous function)    @   client.jsx:14
(anonymous function)    @   iso.js:120
each    @   iso.js:21
bootstrap   @   iso.js:111
(anonymous function)    @   client.jsx:12
__webpack_require__ @   bootstrap d56606d95d659f2e05dc:19
(anonymous function)    @   bootstrap d56606d95d659f2e05dc:39
(anonymous function)    @   bootstrap d56606d95d659f2e05dc:39

这是服务器最初传递给浏览器的内容:

<!doctype html>
<html lang="">

    <head>
        <title>my title</title>

        <meta name="apple-mobile-web-app-title" content="my title" data-react-helmet="true" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" data-react-helmet="true" />
<meta name="apple-mobile-web-app-capable" content="yes" data-react-helmet="true" />
<meta name="mobile-web-app-capable" content="yes" data-react-helmet="true" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" data-react-helmet="true" />
<meta name="description" content="my description." data-react-helmet="true" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" data-react-helmet="true" />
<meta charset="utf-8" data-react-helmet="true" />

        <link rel="stylesheet" href="/assets/styles/reset.css" data-react-helmet="true" />
<link rel="stylesheet" href="/assets/styles/base.css" data-react-helmet="true" />
<link rel="stylesheet" href="/assets/styles/Carousel.css" data-react-helmet="true" />
<link rel="stylesheet" href="/assets/styles/main.css" data-react-helmet="true" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Condensed" type="text/css" data-react-helmet="true" />
<link rel="icon" href="/assets/185bb6f691241307862b331970a6bff1.ico" type="image/x-icon" data-react-helmet="true" />

        SCRIPT

    </head>
    <body>
        <script src="https://cdn.firebase.com/js/client/2.2.7/firebase.js"></script>
        <script src="https://cdn.firebase.com/libs/reactfire/0.4.0/reactfire.min.js"></script>

        <div class="app">
<div class="___iso-html___" data-key="_0"><div data-reactid=".1hkqsbm9n9c" data-react-checksum="794698749"><div data-reactid=".1hkqsbm9n9c.0"><div data-reactid=".1hkqsbm9n9c.0.$=10"></div><div style="position:fixed;z-index:2;top:0;left:0;right:0;height:60px;color:rgb(219,219,219);font-family:mainnextcondensed_ultralight;font-size:17px;overflow:hidden;" data-reactid=".1hkqsbm9n9c.0.$/=11"><div style="position:absolute;left:0;top:0;background-color:rgba(27,27,27,0.92);padding-right:35px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=10"><div style="float:left;height:60px;width:13px;border-left:5px solid rgb(210,45,164);" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=10.$/=10"></div><div style="float:left;height:60px;width:227px;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=10.$/=11"></div><div style="display:none;width:0;height:0;border-style:solid;border-width:6px 6px 0 6px;border-color:rgb(117,117,117) transparent transparent transparent;-webkit-transform:rotate(360deg);float:left;margin-left:6px;margin-top:26px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=10.$/=12"></div></div><div style="position:absolute;top:0px;left:280px;width:340px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11"><div style="background-color:rgba(27,27,27,0.92);height:10px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=10"></div><div style="background-color:rgba(53,53,53,0.84);height:40px;position:relative;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=11"><div style="position:absolute;top:0;bottom:0;left:0;right:0;padding:0;margin:0;display:flex;align-items:center;justify-content:center;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=11.$=10"><div style="background-image:url(&#x27;/assets/3bec3e57cb5ee05658440d21984fb7b7.png&#x27;);background-repeat:no-repeat;background-position:-58px -194px;width:23px;height:22px;position:absolute;top:50%;left:10px;margin-top:-11px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=11.$=10.$icon"></div></div><div style="position:absolute;left:40px;right:40px;top:0px;bottom:0px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=11.$/=12"><input type="text" style="width:100%;height:100%;font-size:14px;font-family:mainnext_regular;background-color:transparent;color:#ffffff;" placeholder="SEARCH ARTISTS, TRACKS, ALBUMS" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=11.$/=12.0"/></div></div><div style="background-color:rgba(27,27,27,0.92);height:10px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=11.$/=12"></div></div><div style="position:absolute;top:0px;left:620px;right:0px;background-color:rgba(27,27,27,0.92);height:60px;line-height:60px;overflow:hidden;min-width:500px;padding-left:10px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12"><div style="position:absolute;top:0px;bottom:0px;right:0px;width:357px;padding-left:141px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0"><a class="" href="/import" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$/=10"><div style="padding-left:40px;position:absolute;left:0px;top:10px;bottom:10px;cursor:pointer;line-height:40px;color:rgb(255,255,255);" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$/=10.$import"><div style="position:absolute;top:0;bottom:0;left:0;right:0;padding:0;margin:0;display:flex;align-items:center;justify-content:center;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$/=10.$import.$=10"><div style="background-image:url(&#x27;/assets/3bec3e57cb5ee05658440d21984fb7b7.png&#x27;);background-repeat:no-repeat;background-position:0px -194px;width:28px;height:28px;position:absolute;top:50%;left:0px;margin-top:-14px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$/=10.$import.$=10.$icon"></div></div><span data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$/=10.$import.1">Import Playlists</span></div></a><div style="margin-left:10px;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$admin/=1$admin"><div style="cursor:pointer;float:left;" data-reactid=".1hkqsbm9n9c.0.$/=11.$/=12.0.$admin/=1$admin.$login">Login</div></div></div></div></div></div><noscript data-reactid=".1hkqsbm9n9c.1"></noscript></div></div>
<div class="___iso-state___" data-key="_0" data-meta="{}" data-state="&quot;{\&quot;UserStore\&quot;:{\&quot;user\&quot;:{\&quot;authenticated\&quot;:false,\&quot;isWaiting\&quot;:false}},\&quot;SearchStore\&quot;:{\&quot;focused\&quot;:false,\&quot;input\&quot;:\&quot;\&quot;,\&quot;timeout\&quot;:null,\&quot;searchRequests\&quot;:[],\&quot;artists\&quot;:null,\&quot;artistsFailed\&quot;:false,\&quot;artistsLoading\&quot;:false,\&quot;tracks\&quot;:null,\&quot;tracksFailed\&quot;:false,\&quot;tracksLoading\&quot;:false,\&quot;albums\&quot;:null,\&quot;albumsFailed\&quot;:false,\&quot;albumsLoading\&quot;:false,\&quot;playlists\&quot;:null,\&quot;playlistsFailed\&quot;:false,\&quot;playlistsLoading\&quot;:false,\&quot;youtubes\&quot;:null,\&quot;youtubesFailed\&quot;:false,\&quot;youtubesLoading\&quot;:false,\&quot;soundclouds\&quot;:null,\&quot;soundcloudsFailed\&quot;:false,\&quot;soundcloudsLoading\&quot;:false},\&quot;PlayerStore\&quot;:{\&quot;player\&quot;:null,\&quot;playerSecond\&quot;:null,\&quot;playingTrack\&quot;:null,\&quot;playingTrackSecond\&quot;:null,\&quot;videoId\&quot;:null,\&quot;videoIdSecond\&quot;:null,\&quot;makingPlayingTrackPlayable\&quot;:false,\&quot;radio\&quot;:false,\&quot;startSeconds\&quot;:0,\&quot;current\&quot;:0,\&quot;total\&quot;:0,\&quot;perc\&quot;:0,\&quot;currentSecond\&quot;:0,\&quot;totalSecond\&quot;:0,\&quot;percSecond\&quot;:0,\&quot;playing\&quot;:false,\&quot;playingSecond\&quot;:false,\&quot;secondsListened\&quot;:0,\&quot;secondsListenedSecond\&quot;:0,\&quot;expand\&quot;:false,\&quot;source\&quot;:null,\&quot;tracksQueue\&quot;:[],\&quot;tracksPrevQueue\&quot;:[],\&quot;favorite\&quot;:false,\&quot;random\&quot;:false,\&quot;repeat\&quot;:false,\&quot;mute\&quot;:false,\&quot;volume\&quot;:100,\&quot;mode\&quot;:\&quot;standard\&quot;},\&quot;ImportStore\&quot;:{\&quot;url\&quot;:\&quot;\&quot;,\&quot;error\&quot;:false,\&quot;focused\&quot;:false,\&quot;loading\&quot;:false,\&quot;loaded\&quot;:false,\&quot;playlist\&quot;:null}}&quot;"></div>
</div>

        <!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
        <!--
        <script>
            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
                (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
                    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
            ga('create', 'UA-XXXXX-X', 'auto');
            ga('send', 'pageview');
        </script>
        -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.3/fastclick.min.js"></script>
        <script type="text/javascript">
          if ('addEventListener' in document) {
            document.addEventListener('DOMContentLoaded', function() {
                FastClick.attach(document.body);
            }, false);
          }
        </script>
        <script type="text/javascript" charset="utf-8" src="/assets/app.js"></script>
    </body>
</html>

这是我的 server.jsx:

import Iso from 'iso';
import React from 'react';
import ReactDomServer from 'react-dom/server';
import { RoutingContext, match } from 'react-router'
import createLocation from 'history/lib/createLocation';

import alt from 'altInstance';
import routes from 'routes.jsx';
import html from 'base.html';

/*
 * @param {AltObject} an instance of the Alt object
 * @param {ReactObject} routes specified in react-router
 * @param {Object} Data to bootstrap our altStores with
 * @param {Object} req passed from Express/Koa server
 */
const renderToMarkup = (alt, state, req, res) => {
  let markup, content;
  let location = new createLocation(req.url);
  alt.bootstrap(state);

  match({ routes, location }, (error, redirectLocation, renderProps) => {
    if (redirectLocation)
      res.redirect(301, redirectLocation.pathname + redirectLocation.search)
    else if (error)
      res.status(500).send(error.message)
    else if (renderProps == null)
      res.status(404).send('Not found')
    else
      content = ReactDomServer.renderToString(<RoutingContext {...renderProps} />);
      markup = Iso.render(content, alt.flush());
  });

  return markup;
};

/* 
 * Export render function to be used in server/config/routes.js
 * We grab the state passed in from the server and the req object from Express/Koa
 * and pass it into the Router.run function.
 */
export default function render(state, req, res) {
  const markup = renderToMarkup(alt, state, req, res);
  return html.replace('CONTENT', markup);
};

这是我的client.jsx:

import React from 'react';
import ReactDOM from 'react-dom';
import Iso from 'iso';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import { Router } from 'react-router';
import alt from 'altInstance';
import routes from 'routes.jsx';

/*
 * Client side bootstrap with iso and alt
 */
Iso.bootstrap((state, _, container) => {
  alt.bootstrap(state);
  ReactDOM.render(<Router history={createBrowserHistory()} children={routes} />, container);
});

还有我的 routes.jsx:

import React from 'react';
import Route from 'react-router';
import App from 'components/App';
import ImportPlaylist from 'components/ImportPlaylist';
import Login from 'components/Login';
import Logout from 'components/Logout';
import Player from 'components/Player/Player';
import Test from 'components/Test';

export default (
  <Route path="/" component={App}>
    <Route path="login" component={Login} />
    <Route path="logout" component={Logout} />
    <Route name="test" path="test" component={Test} />        
    <Route name="import" path="import" component={ImportPlaylist} />
    <Route name="player" path="/:playlist" component={Player} />
  </Route>
);
4

7 回答 7

108

注意:这适用于旧版本的 React。如果你使用 React 16,你应该使用ReactDOM.hydrate()

此外,以下建议将导致客户端重新渲染,如以下答案之一所建议的那样。


这听起来可能非常简单,但是在您的服务器端模板中,将您的 React 标记包装在一个额外的<div>

<!-- hypothetical handlebars template -->
<section role="main" class="react-container"><div>{{{reactMarkup}}}</div></section>

为什么这行得通?在客户端,React 倾向于用多余的 div 包装它对根组件的渲染。ReactDOMServer.render似乎没有以这种方式表现,因此当一个同构渲染到同一个容器中时,您的 DOM 的 Adler-32 校验和会有所不同。

于 2015-11-04T11:52:11.777 回答
17

对于那些在谷歌上搜索并来到这里的人,解决此问题的一种奇怪方式是您甚至不使用同构渲染(即不在服务器端渲染任何东西)。当我使用带有HtmlWebpackPlugin处理index.html文件的模板时,这发生在我身上。

在我index.html的文件中,我自己已经包含了该bundle.js文件,并且上面的插件还bundle.js通过 script-src 包含了另一个。确保您正在设置inject: false您的HtmlWebpackPlugin构造函数。

于 2016-02-12T13:01:46.053 回答
13

对我来说完全杀死nodejs并重新启动工作

于 2016-05-12T08:26:14.547 回答
9

警告这里流行的答案是不正确的。它所做的是完全删除现有的 DOM,并在客户端上用新的渲染替换它。这意味着你失去了 React 的快速浅层渲染并浪费了性能,因此它也会吞噬 OP 错误以及你可能遇到的任何其他错误。

您的问题似乎与 CSS 相关 - 如果您使用的是 autoprefixer 和 inline-styles 可以解释您的区别。

服务器端已经渲染了 align-items:center 并且客户端已经意识到它在 webkit 浏览器中并自动为您添加前缀 -webkit-align-items。

请发布有关 CSS 设置的更多信息,以及使用内联样式或类似样式的任何组件。

于 2016-11-28T15:00:58.307 回答
6

如果您在布局组件内渲染主要内容,则需要将布局渲染为静态标记(无反应属性),以便客户端和服务器之间的内容校验和匹配。

服务器:

app.get('/', (req, res) => {
  // render the content to a string so it has the same checksum as on the client
  // render the layout to static markup so that it does affect the checksum
  // this ensures that rendering is isomorphic and client doesn't override server markup
  const content = reactDomServer.renderToString(<MyContent />)
  const html = '<!DOCTYPE html>' + reactDomServer.renderToStaticMarkup(<HtmlLayout content={content} />)
  res.send(html)
})

html布局:

export default class HtmlLayout extends React.Component<any, any> {
  public render () {
    return (
      <html lang='en'>
      <head>
        <meta charSet='utf-8' />
        <meta name='viewport' content='width=device-width, initial-scale=1' />
        <title>Untitled</title>
        <link rel='stylesheet' href='/style/bundle.css' />
      </head>
      <body>
        { /* insert the content as a string so that it can be rendered separate with its own checksum for proper server-side rendering */ }
        <div id='content' dangerouslySetInnerHTML={ {__html: this.props.content} } />
        <script src='scripts/bundle.js'></script>
      </body>
      </html>
    )
  }
}

客户:

const root = document.getElementById('content')    
DOM.render(<MyContent />, root)

参考:http: //jeffhandley.github.io/QuickReactions/20-final-cleanup

于 2016-06-03T19:13:53.077 回答
2

在我的情况下,问题是由于我使用“react-responsive”中的 MediaQuery 组件而没有传递组件在无法访问屏幕宽度时使用的“value”属性(例如在服务器上)。

于 2016-10-28T09:51:29.093 回答
1

我在我正在开发的 Isomorphic 应用程序上遇到了这个问题。不管你信不信,对我有用的是清空缓存并在 Chrome 上重新加载应用程序。看起来旧的 DOM 以某种方式缓存在浏览器上:)

于 2017-07-20T15:08:11.000 回答