React 路由器链接在服务器和客户端呈现不同

React router links rendered differently on server and client

我在渲染同构应用程序时遇到问题,当我不使用 react-router(1.0.0-rc3) 时它工作正常,但是当我引入路由器时,特别是一个渲染链接的组件,如下所示:

const React = require('react');
const Link = require('react-router').Link;
module.exports = class About extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <ul>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/list">List</Link></li>
      </ul>
    );
  }
}

服务器和客户端的输出不同,我收到这个警告

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) n><a class="" href="#/about" data-reacti
(server) n><a class="" href="/about" data-reactid

所以服务器(或客户端)呈现 href 标签的方式不同,这就是我呈现服务器端的方式

const React = require('react');
const reactDOMServer = require('react-dom/server');
const tmpl = require('blueimp-tmpl');
const fs = require('fs');
const path = require('path');
const templateFunction = tmpl.tmpl;
const match = require('react-router').match;
const RoutingContext = require('react-router').RoutingContext;

const routes = require('../../app/routes');
const App = require('../../app/app');
const serverProps = require('../../server.props');

templateFunction.load = function(id) {
  const filePath = path.resolve(serverProps.publicPath, id);
  return fs.readFileSync(filePath, "utf8");
};

module.exports = function*(next) {
  match({ routes, location: this.url }, (error, redirectLocation, renderProps) => {
      if (error) {
        this.throw(error.message);
      } else if (redirectLocation) {
        this.redirect(redirectLocation.pathname + redirectLocation.search);
      } else if (renderProps) {
        const html = reactDOMServer.renderToString( <RoutingContext {...renderProps} /> );        
        this.body = templateFunction("index.html", html);
      } else {
        this.throw(404);
      }
    });
};

我在这里使用的模板引擎是 blueimp-tmpl,我首先怀疑它可能会在渲染时对 href-hash-sign 做一些事情,但我记录了输出 renderToString 并且 href-hash-sign 已经消失了在进入模板之前。

我在 npm history 包中做了一些挖掘(它是 react-router 的对等依赖项)它似乎是生成链接的 href 部分的组件,但无法弄清楚为什么它会以不同的方式呈现它.

有什么想法吗?

编辑,这是路线

const React = require('react');
const Router = require('react-router').Router;
const Route = require('react-router').Route;

const BaseLayout = require("./components/base-layout/base-layout");
const List = require("./components/list/list");
const About = require("./components/about/about");

module.exports = (
  <Router>
      <Route path="/" component={BaseLayout}>
        <Route path="about" component={About} />
        <Route path="list" component={List} />
      </Route>
    </Router>
);

BR twd

好的,解决了。

最初的问题,客户端的 href 得到哈希标签类型的链接是因为缺少历史配置,解决方法是:

// router.js
const React = require('react');
const Router = require('react-router').Router;
const Route = require('react-router').Route;

const BaseLayout = require("./components/base-layout/base-layout");
const List = require("./components/list/list");
const About = require("./components/about/about");

// This was the missing part... 
const createBrowserHistory = require('history/lib/createBrowserHistory');

module.exports = (
  <Router history={createBrowserHistory()}>
      <Route path="/" component={BaseLayout}>
        <Route path="about" component={About} />
        <Route path="list" component={List} />
      </Route>
    </Router>
);

链接现在看起来不错,但这导致服务器端渲染失败,因为我在渲染时使用了整个路由器,并且 createBrowserHistory 需要 DOM 才能工作。通过将路由拉出到不同的文件来解决:

// routes.js
const React = require('react');
const Route = require('react-router').Route;

const BaseLayout = require("./components/base-layout/base-layout");
const List = require("./components/list/list");
const About = require("./components/about/about");

module.exports = (
  <Route path="/" component={BaseLayout}>
    <Route path="about" component={About}/>
    <Route path="list" component={List}/>
  </Route>
);

并像这里显示的那样在服务器上使用纯路由 Server Rendering Docs。 这使得服务器端渲染工作,但现在我再次破坏了客户端渲染,因为我的 Router 组件不理解如何使用在不同文件中声明的路由,使用正确的语法很容易修复它:

// router.js
const React = require('react');
const Router = require('react-router').Router;

const routes = require("./routes");
const createBrowserHistory = require('history/lib/createBrowserHistory');
module.exports = (
  <Router routes={routes} history={createBrowserHistory()}>    
  </Router>
);