警告:React尝试在容器中重用标记,但校验和无效

时间:2015-11-04 11:46:59

标签: node.js express reactjs webpack isomorphic-javascript

我试图让同构的Node.js,Express,Webpack,React app正常工作。我收到以下错误。有关如何修复它的任何建议吗?

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>
);

7 个答案:

答案 0 :(得分:109)

注意:这适用于旧版本的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校验和会有所不同。

答案 1 :(得分:17)

对于那些谷歌搜索和来到这里,一个奇怪的方式来结束这个问题是你甚至不使用同构渲染(即不在服务器端渲染任何东西)。在使用HtmlWebpackPlugin的模板处理index.html文件时,我发生了这种情况。

在我的index.html文件中,我自己已经包含了bundle.js文件,上面的插件还通过script-src包含了另一个bundle.js。请务必将inject: false设置为HtmlWebpackPlugin构造函数。

答案 2 :(得分:13)

我完全杀死了nodejs并重新启动了

答案 3 :(得分:8)

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

您的问题似乎与CSS有关 - 如果您使用的是autoprefixer和内联样式,可以解释您的不同之处。

服务器端已经渲染了align-items:center,客户端已经在webkit浏览器中实现了它,并自动为它添加了前缀-webkit-align-items。

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

答案 4 :(得分: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)
})

<强> HtmlLayout:

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

答案 5 :(得分:2)

在我的情况下,问题是由于我使用了来自&#39; react-responsive&#39;没有通过&#39;值&#39;当组件无法访问屏幕宽度时(例如,在服务器上),组件使用的属性。

答案 6 :(得分:1)

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