1. 程式人生 > >使用React的static方法實現同構以及同構的常見問題

使用React的static方法實現同構以及同構的常見問題

fonts eap 細致 tput isp shee 模塊 system device

代碼地址請在github查看,假設有新內容。我會定時更新。也歡迎您star,issue,共同進步

1.我們服務端渲染數據從何而來

1.1 怎樣寫出同構的組件

服務端生成HTML結構有時候並不完好。有時候不借助js是不行的。比方當我們的組件須要輪詢服務器的數據接口,實現數據與服務器同步的時候就顯得非常重要。事實上這個獲取數據的過程能夠是數據庫獲取,也能夠是從其它的反向代理服務器來獲取。

對於client來說,我們能夠通過ajax請求來完畢,僅僅要將ajax請求放到componentDidMount方法中來完畢就能夠。

而之所以放在該方法中有兩個原因,第一個是為了保證此時DOM已經掛載到頁面中;還有一個原因是在該方法中調用setState會導致組件又一次渲染(詳細你能夠查看這個文章)。而對於服務端來說,
一方面它要做的事情便是:去數據庫或者反向代理服務器拉取數據 -> 依據數據生成HTML -> 吐給client。這是一個固定的過程,拉取數據和生成HTML過程是不可打亂順序的。不存在先把內容吐給client,再拉取數據這種異步過程。所以,componentDidMount在服務器渲染組件的時候,就不適用了(由於render方法已經調用,可是componentDidMount還沒有運行,所以渲染得到的是沒有數據的組件。原因在於生命周期方法componentDidMount在render之後才會調用

)。

還有一方面,componentDidMount這種方法,在服務端確實永遠都不會運行!因此我們要採用和client渲染全然不一致的方法來解決渲染之前數據不存在問題。

關於服務端渲染和client渲染的差別你能夠查看Node直出理論與實踐總結

var React = require(‘react‘);
var DOM = React.DOM;
var table = DOM.table, tr = DOM.tr, td = DOM.td;
var Data = require(‘./data‘);
module.exports = React.createClass({
    statics: {
        //獲取數據在實際生產環境中是個異步過程,所以我們的代碼也須要是異步的
fetchData: function (callback) { Data.fetch().then(function (datas) { callback.call(null, datas); }); } }, render: function () { return table({ children: this.props.datas.map(function (data) { return tr(null, td(null, data.name), td(null, data.age), td(null, data.gender) ); }) }); }, componentDidMount: function () { setInterval(function () { // 組件內部調用statics方法時,使用this.constructor.xxx // client在componentDidMount中獲取數據,並調用setState改動狀態要求 // 組件又一次渲染 this.constructor.fetchData(function (datas) { this.setProps({ datas: datas }); }); }, 3000); } });

當中服務器端的處理邏輯render-server.js例如以下:

var React = require(‘react‘);
var ReactDOMServer = require(‘react-dom/server‘);
// table類
var Table = require(‘./Table‘);
// table實例
var table = React.createFactory(Table);
module.exports = function (callback) {
    //在client調用Data.fetch時,是發起ajax請求。而在服務端調用Data.fetch時,
    //有可能是通過UDP協議從其它數據服務器獲取數據、查詢數據庫等實現
    Table.fetchData(function (datas) {
        var html = ReactDOMServer.renderToString(table({datas: datas}));
        callback.call(null, html);
    });
};

以下是服務器的邏輯server.js:

var makeTable = require(‘./render-server‘);
var http = require(‘http‘);
//註冊中間件
http.createServer(function (req, res) {
    if (req.url === ‘/‘) {
        res.writeHead(200, {‘Content-Type‘: ‘text/html‘});
        //先訪問數據庫或者反代理服務器來獲取到數據,並註冊回調,將含有數據的html結構返回給client,此處僅僅是渲染一個組件。否則須要renderProps.components.forEach來遍歷全部的組件獲取數據
        //http://www.toutiao.com/i6284121573897011714/
        makeTable(function (table) {
            var html = ‘<!doctype html>\n                      <html>                        <head>                            <title>react server render</title>                        </head>                        <body>‘ +
                            table +
                            //這裏是client的代碼。實現每隔一定事件更新數據,至於怎樣加入以下的script標簽內容,能夠參考這裏https://github.com/liangklfangl/react-universal-bucket
                            ‘<script src="pack.js"></script>                        </body>                      </html>‘;
            res.end(html);
        });
    } else {
        res.statusCode = 404;
        res.end();
    }
}).listen(1337, "127.0.0.1");
console.log(‘Server running at http://127.0.0.1:1337/‘);

註意:由於我們的react服務端渲染僅僅是一次性的。不會隨著調用setState而又一次reRender,所以我們須要在返回給client的html中加入client的代碼,真正的每隔一定時間更新組件的邏輯是client通過ajax來完畢的。

1.2 怎樣避免服務端渲染後client再次渲染

服務端生成的data-react-checksum是幹嘛使的?我們想一想。就算服務端沒有初始化HTML數據,僅僅依靠client的React也全然能夠實現渲染我們的組件,那服務端生成了HTML數據。會不會在clientReact運行的時候被又一次渲染呢?我們服務端辛辛苦苦生成的東西,被client無情地覆蓋了?當然不會!

React在服務端渲染的時候,會為組件生成相應的校驗和(在redux的情況下事實上應該是一個組件樹,為整個組件樹生成校驗和,由於這整個組件樹就是我們首頁要顯示的內容)(checksum)。這樣clientReact在處理同一個組件的時候,會復用服務端已生成的初始DOM,增量更新(也就是說當client和服務端的checksum不一致的情況下才會進行dom diff,進行增量更新),這就是data-react-checksum的作用。能夠通過以下的幾句話來總結下:

 假設data-react-checksum同樣則不又一次render。省略創建DOM和掛載DOM的過程,接著觸發 componentDidMount 等事件來處理服務端上的未盡事宜(事件綁定等),從而加快了交互時間;不同一時候,組件在client上被又一次掛載 render。

ReactDOMServer.renderToString 和 ReactDOMServer.renderToStaticMarkup 的差別在這個時候就非常好解釋了。前者會為組件生成checksum,而後者不會。後者僅僅生成HTML結構數據。所以,僅僅有你不想在client-服務端同一時候操作同一個組件的時候,方可使用renderToStaticMarkup。註意:上面使用了statics塊,該寫法僅僅在createClass中可用。你能夠使用以下的寫法:

//組件內的寫法
class Component extends React.Component {
    static propTypes = {
    ...
    }
    static someMethod(){
    }
}

在組件外面你能夠依照例如以下寫法:

class Component extends React.Component {
   ....
}
Component.propTypes = {...}
Component.someMethod = function(){....}

詳細你能夠查看這裏。

關於服務端渲染經常會出現以下的warning,大多數情況下是由於在返回 HTML 的時候沒有將服務端上的數據一同返回,或者是返回的數據格式不正確導致

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 generatted 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. Insted, figure out why the markup being generated is different on the client and server

2.怎樣區分client與服務端代碼

2.1 加入client代碼到服務端渲染的html字符串

通過這個樣例我們知道,將webpack-isomorphic-tools這個插件加入到webpack的plugin中:

module.exports = {
    entry:{
        ‘main‘: [
          ‘webpack-hot-middleware/client?path=http://‘ + host + ‘:‘ + port + ‘/__webpack_hmr‘,
        // "bootstrap-webpack!./src/theme/bootstrap.config.js",
        "bootstrap-loader",
        //確保安裝bootstrap3,bootstrap4不支持less
          ‘./src/client.js‘
        ]
    },
   output: {
      path: assetsPath,
      filename: ‘[name]-[hash].js‘,
      chunkFilename: ‘[name]-[chunkhash].js‘,
      publicPath: ‘http://‘ + host + ‘:‘ + port + ‘/dist/‘
      //表示要訪問我們client打包好的資源必須在前面加上的前綴。也就是虛擬路徑
    },
    plugins:[
        new webpack.DefinePlugin({
          __CLIENT__: true,
          __SERVER__: false,
          __DEVELOPMENT__: true,
          __DEVTOOLS__: true //,
        }),
     webpackIsomorphicToolsPlugin.development()
     //在webpack的development模式下一定更要調用它支持asset hold reloading!
     //https://github.com/liangklfang/webpack-isomorphic-tools
    ]
}

此時我們client.js會被打包到相應的文件路徑下。然後在我們的模版中,僅僅要將這個打包好的script文件加入到html返回給client就能夠了。以下是遍歷我們的webpack-assets.json來獲取到我們全部的產生的資源,然後加入到html模板中返回的邏輯:

export default class Html extends Component {
  static propTypes = {
    assets: PropTypes.object,
    component: PropTypes.node,
    store: PropTypes.object
  };
  render() {
    const {assets, component, store} = this.props;
    const content = component ? renderToString(component) : ‘‘;
    //假設有組件component傳遞過來,那麽我們直接調用renderToString
    const head = Helmet.rewind();
    return (
      <html lang="en-us">
        <head>
          {head.base.toComponent()}
          {head.title.toComponent()}
          {head.meta.toComponent()}
          {head.link.toComponent()}
          {head.script.toComponent()}
          <link rel="shortcut icon" href="/favicon.ico" />
         <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css"/>
        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Work+Sans:400,500"/>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/violet/0.0.1/violet.min.css"/>
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          {/* styles (will be present only in production with webpack extract text plugin)
             styles屬性僅僅有在生產模式下才會存在,此時通過link來加入。

便於緩存 */} {Object.keys(assets.styles).map((style, key) => <link href={assets.styles[style]} key={key} media="screen, projection" rel="stylesheet" type="text/css" charSet="UTF-8"/> )} {/* assets.styles假設開發模式下,那麽肯定是空,那麽我們直接採用內聯的方式來插入就可以。

此時我們的css沒有單獨抽取出來,也就是沒有ExtractTextWebpackPlugin。打包到js中從而內聯進來 */} {/* (will be present only in development mode) */} {/* outputs a <style/> tag with all bootstrap styles + App.scss + it could be CurrentPage.scss. */} {/* can smoothen the initial style flash (flicker) on page load in development mode. */} {/* ideally one could also include here the style for the current page (Home.scss, About.scss, etc) */} </head> <body> <div id="content" dangerouslySetInnerHTML={{__html: content}}/> {/*將組件renderToString後放在id為content的div內部*/} <script dangerouslySetInnerHTML={{__html: `window.__data=${serialize(store.getState())};`}} charSet="UTF-8"/> {/*將store.getState序列化後放在window.__data上,讓client代碼能夠拿到*/} <script src={assets.javascript.main} charSet="UTF-8"/> {/*將我們的main.js,來自於client打包並放在特定文件夾下的資源放在頁面中, 這就成了client自己的js資源了 */} </body> </html> ); } }

所以說以下的div#content中是服務端渲染後得到的html字符串,並被原樣返回給client。

這種話。對於服務端的任務就完畢了

 <div id="content" dangerouslySetInnerHTML={{__html: content}}/>

而我們的以下的script標簽的內容就是我們的client代碼打包後的結果:

   <script src={assets.javascript.main} charSet="UTF-8"/>

此時client和服務端的邏輯都已經完畢了,client能夠繼續接收用戶操作而發送ajax請求更新組件狀態。

2.2 怎樣使得服務端和client發起請求的邏輯通用

一個好的使用方法在於使用isomorphic-fetch

2.3 immutable數據在同構中的註意事項

首先在服務端返回的時候必須將store.getState得到的結果序列化,並且此時假設store返回的某一個部分state是immutbale的,那麽client要又一次通過這部分state數據來創建新的immutable對象(如以下的樣例中我們的recipeGrid和connect是immutable的):

  <script dangerouslySetInnerHTML={{__html: `window.__data=${serialize(store.getState())};`}} charSet="UTF-8"/>

對於client來說,我們必須將從服務端註入到HTML上的state數據轉成 immutable對象,並將該對象作為initialState來創建store:

  const data = window.__data;
  //當中data是服務端返回的store.getState的值。也就是store的當前狀態
  if (data) {
     data.recipeGrid = Immutable.fromJS(data.recipeGrid);
     //這裏必須設置,否則報錯說:paginator.equals is not a function
      data.connect = Immutable.fromJS(data.connect);
     //能夠使用https://github.com/liangklfang/redux-immutablejs
  }
  const store = finalCreateStore(reducer, data);
2.4 服務端server不支持ES6的兼容

假設你想在服務端使用import等ES6的語法的話。你能夠採用以下的方式。首先在項目的根文件夾下配置.babelrc文件,內容例如以下:

{
  "presets": ["react", "es2015", "stage-0"],
  "plugins": [
    "transform-runtime",
    "add-module-exports",
    "transform-decorators-legacy",
    "transform-react-display-name"
  ]
}

然後配置一個單獨的文件server.babel.js:

const fs = require("fs");
const babelrc = fs.readFileSync("./.babelrc");
let config ;
try{
    config = JSON.parse(babelrc);
}catch(err){
    console.error("你的.babelrc文件有誤,請細致檢查");
    console.error(err);
}
//你能夠指定ignore配置來忽略某些文件。
//https://github.com/babel/babel/tree/master/packages/babel-register
require("babel-register")(config);
//require("babel-register")會導致以後全部的.es6,.es,.js,.jsx的文件都會被babel處理

最後我們加入我們的server.js,內容例如以下(直接node server.js,而真正的邏輯放在../src/server中):

#!/usr/bin/env node
require(‘../server.babel‘); // babel registration (runtime transpilation for node)
var path = require(‘path‘);
var rootDir = path.resolve(__dirname, ‘..‘);
global.__CLIENT__ = false;
global.__SERVER__ = true;
global.__DISABLE_SSR__ = false;  
// <----- DISABLES SERVER SIDE RENDERING FOR ERROR DEBUGGING
global.__DEVELOPMENT__ = process.env.NODE_ENV !== ‘production‘;
if (__DEVELOPMENT__) {
//服務端代碼熱載入
  if (!require(‘piping‘)({
      hook: true,
      ignore: /(\/\.|~$|\.json|\.scss$)/i
    })) {
    return;
  }
}
// https://github.com/halt-hammerzeit/webpack-isomorphic-tools
var WebpackIsomorphicTools = require(‘webpack-isomorphic-tools‘);
global.webpackIsomorphicTools = new WebpackIsomorphicTools(require(‘../webpack/webpack-isomorphic-tools-config‘))
  .development(__DEVELOPMENT__)
  .server(rootDir, function() {
  //rootDir必須和webpack的context一致。調用這種方法服務器就能夠直接require不論什麽資源了
  //這個路徑用於獲取webpack-assets.json文件,這個是webpack輸出的
  // webpack-isomorphic-tools is all set now.
  // here goes all your web application code:
  // (it must reside in a separate *.js file 
  //  in order for the whole thing to work)
  //  此時webpack-isomorphic-tools已經註冊好了,這裏能夠寫你的web應用的代碼。並且這些代碼必須在一個獨立的文件裏
    require(‘../src/server‘);
  });

經過上面的babel-register的處理,此時你的../src/server.js中能夠使用隨意ES6的代碼了。

2.5 服務端代碼單獨使用webpack打包

假設對於服務端的代碼要單獨打包,那麽必須進行以下的設置:

target: "node"

你能夠參考這裏。

2.6 服務端渲染之忽略css/less/scss文件

在2.4中我們使用了babel-register幫助服務端識別特殊的js語法,但對less/css文件無能為力,慶幸的是。在普通情況下,服務端渲染不須要樣式文件的參與,css文件僅僅要引入到HTML文件裏就可以,因此。能夠通過配置項。忽略全部 css/less 文件:

require("babel-register")({
  //默認情況ignore是node_modules表示node_modules下的全部文件的require不會進行處理
  //這裏明白指定css/less不經過babel處理
  ignore: /(.css|.less)$/, });

詳細內容你能夠查看babel-register文檔。

你能夠傳遞其指定的全部的其它選項。包含plugins和presets。

可是有一點要註意。就是距離我們源文件的近期一個.babelrc始終會起作用,同一時候其優先級也要比你在此配置的選項優先級高。

此時我們忽略了樣式文件的解析並不會導致client對組件再次渲染,由於我們的checksum和詳細的css/less/scss文件無關,僅僅是和組件render的結果有關。

2.7 使用webpack-isomorphic-tools識別css/less/scss文件

通過 babel-register 能夠使用babel解決jsx語法問題,對 css/less 僅僅能進行忽略,但在使用了CSS Modules 的情況下。服務端必須能夠解析 less文件,才幹得到轉換後的類名,否者服務端渲染出的HTML結構和打包生成的client css 文件裏,類名無法相應。

其原因在於:我們在服務端使用了CSS Module的情況下必須採用例如以下的方式來完畢類名設置:

const React = require("react");
const styles = require("./index.less");
class Test extends React.Component{
 render(){
     return (
        //假設不是css module。那麽可能是這種情況:className="banner"
           <div className={styles.banner}>This is banner<\/div>
        )
   }
}

假設服務端無法解析css/less肯定無法得到終於的class的名稱(經過css module處理後的className)。從而導致client和服務端渲染得到的組件的checksum不一致(由於class的值不一致)。而對於2.6提到的忽略less/css文件的情況,盡管服務端沒有解析該類名,可是我們的組件上已經通過class屬性值指定了同樣的字符串,因此checksum是全然一致的。

為了解決問題,須要一個額外的工具,即webpack-isomorphic-tools,幫助識別less文件。通過這個工具,我們會將服務器端組件引入的less/css/scss文件進行特別的處理,如以下是Widget組件引入的scss文件被打包成的內容並寫入到webpack-assets.json中:

 "./src/containers/Widgets/Widgets.scss": {
      "widgets": "widgets___3TrPB",
      "refreshBtn": "refreshBtn___18-3v",
      "idCol": "idCol___3gf_9",
      "colorCol": "colorCol___2bs_U",
      "sprocketsCol": "sprocketsCol___3nkz0",
      "ownerCol": "ownerCol___fwn86",
      "buttonCol": "buttonCol___1feoO",
      "saving": "saving___7FVQZ",
      "_style": ".widgets___3TrPB .refreshBtn___18-3v {\n  margin-left: 20px;\n}\n\n.widgets___3TrPB .idCol___3gf_9 {\n  width: 5%;\n}\n\n.widgets___3TrPB .colorCol___2bs_U {\n  width: 20%;\n}\n\n.widgets___3TrPB .sprocketsCol___3nkz0 {\n  width: 20%;\n  text-align: right;\n}\n\n.widgets___3TrPB .sprocketsCol___3nkz0 input {\n  text-align: right;\n}\n\n.widgets___3TrPB .ownerCol___fwn86 {\n  width: 30%;\n}\n\n.widgets___3TrPB .buttonCol___1feoO {\n  width: 25%;\n}\n\n.widgets___3TrPB .buttonCol___1feoO .btn {\n  margin: 0 5px;\n}\n\n.widgets___3TrPB tr.saving___7FVQZ {\n  opacity: 0.8;\n}\n\n.widgets___3TrPB tr.saving___7FVQZ .btn[disabled] {\n  opacity: 1;\n}\n"
    }

此時,在服務端你能夠使用上面說的styles.banner這種方式來設置className,而不用操心使用babel-register僅僅能忽略css/less/scss文件而無法使用css module特性,從而導致checksum不一致!

詳細你能夠查看這裏

2.8 前後端路由不同的處理

單頁應用一個常見的問題在於:全部的代碼都會在頁面初始化的時候一起載入,即使這部分的代碼是不須要的,這經常會產生長時間的白屏。webpack支持將你的代碼進行切分,從而切割成為不同的chunk而按需載入。

當我們在特定路由的時候載入該路由須要的代碼邏輯,哪些當前頁面不須要的邏輯按需載入。

對於server-rendering來說,我們服務端不會採用按需載入的方式,而我們的client經常會使用System.import或者require.ensure來實現按需載入。

比方以下的樣例:

module.exports = {
    path: ‘complex‘,
    getChildRoutes(partialNextState, cb) {
       //假設是服務端渲染。我們將Page1,Page2和其它全部的組件打包到一起,假設是client,那麽我們會將Page1,Page2的邏輯單獨打包到一個chunk中從而按需載入
        if (ONSERVER) {
            cb(null, [
                require(‘./routes/Page1‘),
                require(‘./routes/Page2‘)
            ])
        } else {
            require.ensure([], (require) => {
                cb(null, [
                    require(‘./routes/Page1‘),
                    require(‘./routes/Page2‘)
                ])
            })
        }
    },
    //IndexRoute表示默認載入的子組件,
    getIndexRoute(partialNextState, cb) {
        if (ONSERVER) {
            const { path, getComponent } = require(‘./routes/Page1‘);
            cb(null, { getComponent });
        } else {
            require.ensure([], (require) => {
                // separate out the path part, otherwise warning raised
                // 獲取下一個模塊的path和getComponent,由於他是採用module.export直接導出的
                // 我們直接將getComponent傳遞給callback函數
                const { path, getComponent } = require(‘./routes/Page1‘);
                cb(null, { getComponent });
            })
        }
    },
    getComponent(nextState, cb) {
        if (ONSERVER) {
            cb(null, require(‘./components/Complex.jsx‘));
        } else {
            require.ensure([], (require) => {
                cb(null, require(‘./components/Complex.jsx‘))
            })
        }
    }
}

這個樣例的路由相應於/complex,假設是服務端渲染,那麽我們會將Page1,Page2代碼和其它的組件代碼打包到一起。

假設是client渲染,那麽我們會將Page1,Page2單獨打包成為一個chunk。當用戶訪問”/complex”的時候才會載入這個chunk。那麽為什麽服務端渲染要將Page1,Page2一起渲染呢?事實上你要弄清楚,對於服務端渲染來說。將Page1,Page2一起渲染事實上是獲取到了該兩個子頁面的DOM返回給client(形成當前頁面的子頁面的兩個Tab頁面)。而client單獨載入chunk事實上僅僅是為了讓這部分DOM能夠響應用戶的點擊,滾動等事件而已。註意:服務端渲染和我們的req.url有關,如以下的樣例:

 match({ history, routes: getRoutes(store), location: req.originalUrl }, (error, redirectLocation, renderProps) => {
    if (redirectLocation) {
      res.redirect(redirectLocation.pathname + redirectLocation.search);
      //重定向要加入pathname+search
    } else if (error) {
      console.error(‘ROUTER ERROR:‘, pretty.render(error));
      res.status(500);
      hydrateOnClient();
      //發送500告訴client請求失敗。同一時候不讓緩存了
    } else if (renderProps) {
      loadOnServer({...renderProps, store, helpers: {client}}).then(() => {
        const component = (
          <Provider store={store} key="provider">
            <ReduxAsyncConnect {...renderProps} />
          <\/Provider>
        );
        res.status(200);
        global.navigator = {userAgent: req.headers[‘user-agent‘]};
        res.send(‘<!doctype html>\n‘ +
          renderToString(<Html assets={webpackIsomorphicTools.assets()} component={component} store={store}\/>));
      });
    } else {
      res.status(404).send(‘Not found‘);
    }
  });
});

我們的服務端依據req.url獲取到renderProps,從而將一個組件樹渲染成為html字符串返回給client。所以我們服務端不會按需渲染。終於導致的結果僅僅是多渲染了該path下的一部分DOM而已,並且這樣有一個優點就是高速響應用戶操作(還是要client進行註冊事件等)而不用client又一次render該部分DOM。

而從client來說,我此時僅僅須要載入該path下相應的chunk就能夠了,而不是將整個應用的chunk一起載入,從而按需載入,速度更快,更加合理。

服務端match路由須要註意的問題:盡量前置重定向(寫到路由的 onEnter 裏)。

除非須要拉取數據進行推斷,不要在路由確定之後再重定向。

由於在拿到路由配置之後就要依據相應的頁面去拉數據了。這之後再重定向就比較浪費。

如以下的樣例:

  const requireLogin = (nextState, replace, cb) => {
    function checkAuth() {
      const { auth: { user }} = store.getState();
      if (!user) {
        // oops, not logged in, so can‘t be here!
        replace(‘/‘);
      }
      cb();
    }
    if (!isAuthLoaded(store.getState())) {
      store.dispatch(loadAuth()).then(checkAuth);
    } else {
      checkAuth();
    }
  };

以下使用onEnter鉤子函數的路由配置:

    <Route onEnter={requireLogin}>
       //假設沒有登錄,那麽以下的路由組件根本不會實例化,更不用說拉取數據了
        <Route path="chat" component={Chat}/>
        <Route path="loginSuccess" component={LoginSuccess}/>
  <\/Route>

參考資料:

React同構思想

React數據獲取為什麽一定要在componentDidMount裏面調用?

ReactJS 生命周期、數據流與事件

React statics with ES6 classes

React同構直出優化總結

騰訊新聞React同構直出優化實踐

Node直出理論與實踐總結

React+Redux 同構應用開發

ReactJS 服務端同構實踐「QQ音樂web團隊」

代碼拆分 - 使用 require.ensure

性能優化三部曲之三——Node直出讓你的網頁秒開 #6

使用React的static方法實現同構以及同構的常見問題