React校验和无效的服务器呈现

时间:2016-02-12 17:04:24

标签: javascript reactjs

在我的反应应用中,我目前正在使用服务器端渲染。我目前得到的错误是:

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) <noscript data-reacti
 (server) <div data-reactid=".1

Server.js:

 import 'babel-polyfill';
 import path from 'path';
 import express from 'express';
 import React from 'react';
 import ReactDOM from 'react-dom/server';
 import { match, RouterContext } from 'react-router';
 import assets from './assets';
 import { port } from './config';
 import routes from './routes';
 import ContextHolder from './core/ContextHolder';
 import Html from './components/Html';

 const server = global.server = express();

 //
 // Register Node.js middleware
 // -----------------------------------------------------------------------------
 server.use(express.static(path.join(__dirname, 'public')));

 //
 // Register API middleware
 // -----------------------------------------------------------------------------
 server.use('/api/content', require('./api/content').default);

 //
 // Register server-side rendering middleware
 // -----------------------------------------------------------------------------
 server.get('*', async (req, res, next) => {
   try {
     match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
       if (error) {
         throw error;
       }
       if (redirectLocation) {
         const redirectPath = `${ redirectLocation.pathname }${ redirectLocation.search }`;
         res.redirect(302, redirectPath);
         return;
       }
       let statusCode = 200;
       const data = { title: '', description: '', css: '', body: '', entry: assets.main.js };
       const css = [];
       const context = {
         insertCss: styles => css.push(styles._getCss()),
         onSetTitle: value => data.title = value,
         onSetMeta: (key, value) => data[key] = value,
         onPageNotFound: () => statusCode = 404,
       };
       data.body = ReactDOM.renderToString(
         <ContextHolder context={context}>
           <RouterContext {...renderProps}/>
         </ContextHolder>
       );
       data.css = css.join('');
       const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);
       res.status(statusCode).send(`<!doctype html>\n${html}`);
     });
   } catch (err) {
     next(err);
   }
 });

 //
 // Launch the server
 // -----------------------------------------------------------------------------
 server.listen(port, () => {
   /* eslint-disable no-console */
   console.log(`The server is running at http://localhost:${port}/`);
 });

client.js:

import 'babel-polyfill';
import React from 'react';
import { match, Router } from 'react-router';
import { render } from 'react-dom';
import FastClick from 'fastclick';
import routes from './routes';
import Location from './core/Location';
import ContextHolder from './core/ContextHolder';
import { addEventListener, removeEventListener } from './core/DOMUtils';

let cssContainer = document.getElementById('css');
const appContainer = document.getElementById('app');
const context = {
  insertCss: styles => styles._insertCss(),
  onSetTitle: value => document.title = value,
  onSetMeta: (name, content) => {
    // Remove and create a new <meta /> tag in order to make it work
    // with bookmarks in Safari
    const elements = document.getElementsByTagName('meta');
    [].slice.call(elements).forEach((element) => {
      if (element.getAttribute('name') === name) {
        element.parentNode.removeChild(element);
      }
    });
    const meta = document.createElement('meta');
    meta.setAttribute('name', name);
    meta.setAttribute('content', content);
    document.getElementsByTagName('head')[0].appendChild(meta);
  },
};

function run() {
  const scrollOffsets = new Map();
  let currentScrollOffset = null;

  // Make taps on links and buttons work fast on mobiles
  FastClick.attach(document.body);

  const unlisten = Location.listen(location => {
    const locationId = location.pathname + location.search;
    if (!scrollOffsets.get(locationId)) {
      scrollOffsets.set(locationId, Object.create(null));
    }
    currentScrollOffset = scrollOffsets.get(locationId);
    // Restore the scroll position if it was saved
    if (currentScrollOffset.scrollY !== undefined) {
      window.scrollTo(currentScrollOffset.scrollX, currentScrollOffset.scrollY);
    } else {
      window.scrollTo(0, 0);
    }
  });

  const { pathname, search, hash } = window.location;
  const location = `${pathname}${search}${hash}`;

  match({ routes, location }, (error, redirectLocation, renderProps) => {
    render(
      <ContextHolder context={context}>
        <Router {...renderProps} children={routes} history={Location} />
      </ContextHolder>,
      appContainer
    );
    // Remove the pre-rendered CSS because it's no longer used
    // after the React app is launched
    if (cssContainer) {
      cssContainer.parentNode.removeChild(cssContainer);
      cssContainer = null;
    }
  });

  // Save the page scroll position
  const supportPageOffset = window.pageXOffset !== undefined;
  const isCSS1Compat = ((document.compatMode || '') === 'CSS1Compat');
  const setPageOffset = () => {
    if (supportPageOffset) {
      currentScrollOffset.scrollX = window.pageXOffset;
      currentScrollOffset.scrollY = window.pageYOffset;
    } else {
      currentScrollOffset.scrollX = isCSS1Compat ?
        document.documentElement.scrollLeft : document.body.scrollLeft;
      currentScrollOffset.scrollY = isCSS1Compat ?
        document.documentElement.scrollTop : document.body.scrollTop;
    }
  };

  addEventListener(window, 'scroll', setPageOffset);
  addEventListener(window, 'pagehide', () => {
    removeEventListener(window, 'scroll', setPageOffset);
    unlisten();
  });
}

// Run the application when both DOM is ready and page content is loaded
if (['complete', 'loaded', 'interactive'].includes(document.readyState) && document.body) {
  run();
} else {
  document.addEventListener('DOMContentLoaded', run, false);
}

这是我第一次探索服务器端渲染。我知道这意味着服务器和客户端正在发送2个不同的东西,因此客户端必须重新渲染任何东西。它没有破坏任何东西,但我想知道如何解决这个问题,以便警告可以消失。

1 个答案:

答案 0 :(得分:1)

此问题与异步路由有关。我找不到其他解决办法,只能改变所有要同步的路线。加载异步路由时,客户端添加的<noscript>标记放在那里。

示例,更改:

const routes = {
    path: '/',
    getComponent: function(location, cb) {
        require.ensure([], function(require) {
            return cb(null, require('./views/homepage'));
        })
    };
};

进入这个:

const routes = {
    path: '/',
    getComponent: function(location, cb) {
        return cb(null, require('./views/homepage'));
    };
};

编辑:要重新启用异步路由,请在客户端路由器中执行此操作:

match({ history, routes }, (error, redirectLocation, renderProps) => {
    render(<Router {...renderProps} />, mountNode)
})

通过@firasd在这里回答编辑:Async Routes Causes Server-Side Checksum Invalid Error