[轉] React同構思想

來源:互聯網
上載者:User

標籤:

React比較吸引我的地方在於其用戶端-服務端同構特性,服務端-用戶端可複用組件,本文來簡單介紹下這一架構思想。

出於篇幅原因,本文不會介紹React基礎,所以,如果你還不清楚React的state/props/生存周期等基本概念,建議先學習相關文檔

用戶端React

先來回顧一下React如何寫一個組件。比如要做一個下面的表格:

可以這樣寫: 先建立一個表格類。 Table.js

var React = require(‘react‘);var DOM = React.DOM;var table = DOM.table, tr = DOM.tr, td = DOM.td;module.exports = React.createClass({    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)                    );                })            });    }});

假設已經有了我們要的表格的結構化資料。 datas.js

// 三行資料,分別包括名字、年齡、性別module.exports = [    {        ‘name‘: ‘foo‘,        ‘age‘: 23,        ‘gender‘: ‘male‘    },    {        ‘name‘: ‘bar‘,        ‘age‘: 25,        ‘gender‘: ‘female‘    },    {        ‘name‘: ‘alice‘,        ‘age‘: 34,        ‘gender‘: ‘male‘    }];

有了表格類和相應的資料之後,就可以調用並渲染這個表格了。 render-client.js

var React = require(‘react‘);var ReactDOM = require(‘react-dom‘);// table類var Table = require(‘./Table‘);// table執行個體var table = React.createFactory(Table);// 資料來源var datas = require(‘./datas‘);// render方法把react執行個體渲染到頁面中 https://facebook.github.io/react/docs/top-level-api.html#reactdomReactDOM.render(    table({datas: datas}),    document.body);

我們把React基礎庫Table.jsdatas.jsrender-client.js等打包成pack.js,引用到頁面中:

<!doctype html><html>    <head>        <title>react</title>    </head>    <body>    </body>    <script src="pack.js"></script></html>‘

這樣頁面便可按資料結構渲染出一個表格來

這裡 pack.js 的具體打包工具可以是grunt/gulp/webpack/browerify等,打包方法不在這裡贅述

這個例子的關鍵點是使用props來傳遞單向資料流。例如,通過遍曆從``props傳來的資料datas```產生表格的每一行資料:

this.props.datas.map...

組件的每一次變更(比如有新增資料),都會調用組件內部的render方法,更改其DOM結構。上面這個例子中,當給datas push新資料時,react會自動為頁面中的表格新增資料行。

服務端React

上面的例子中建立的Table組件,出於效能、SEO等因素考慮,我們會考慮在服務端直接產生HTML結構,這樣就可以在瀏覽器端直接渲染DOM了。

這時候,我們的Table組件,就可以同時在用戶端和服務端使用了。

只不過與瀏覽器端使用ReactDOM.render指定組件的渲染目標不同,在伺服器中渲染,使用的是ReactDOMServer這個模組,它有兩個產生HTML字串的方法:

  • renderToString
  • renderToStaticMarkup

關於這兩個方法的區別,我想放到後面再來解釋,因為跟後面介紹的內容很有關係。

有了這兩個方法,我們來建立一個在服務端nodejs環境啟動並執行檔案,使之可以直接在服務端產生表格的HTML結構。

render-server.js:

var React = require(‘react‘);// 與用戶端require(‘react-dom‘)略有不同var React = require(‘react‘);// 與用戶端require(‘react-dom‘)略有不同var ReactDOMServer = require(‘react-dom/server‘);// table類var Table = require(‘./Table‘);// table執行個體var table = React.createFactory(Table);module.exports = function () {    return ReactDOMServer.renderToString(table(datas));};

上面這段代碼複用了同一個Table組件,產生瀏覽器可以直接渲染的HTML結構,下面我們通過改改nodejs的官方Hello World來做一個真實的頁面。

server.js :

var makeTable = require(‘./render-server‘);var http = require(‘http‘);http.createServer(function (req, res) {  res.writeHead(200, {‘Content-Type‘: ‘text/html‘});  var table = makeTable();  var html = ‘<!doctype html>\n              <html>                <head>                    <title>react server render</title>                </head>                <body>‘ +                    table +                ‘</body>              </html>‘;  res.end(html);}).listen(1337, "127.0.0.1");console.log(‘Server running at http://127.0.0.1:1337/‘);

這時候運行node server.js就能看到,不實用js,達到了同樣的表格效果,這裡我使用了同一個Table.js,完成用戶端及服務端的同構,一份代碼,兩處使用。

這裡我們通過查看頁面的HTML源碼,發現表格的DOM中帶了一些資料:

data-reactid / data-react-checksum 都是些啥?這裡同樣先留點懸念,後面再解釋。

服務端 + 用戶端渲染

上面的這個例子,通過在服務端調用同一個React組件,達到了同樣的介面效果,但是有人可能會不開心了:貌似有點弱啊!

上面的例子有兩個明顯的問題:

  • datas.js 資料來源是寫死的,不符合大部分真實生產環境

  • 服務端產生HTML結構有時候並不完善,有時候不藉助js是不行的。比如當我們的表格需要輪詢伺服器的資料介面,實現表格式資料與伺服器同步的時候,怎麼實現一個組件兩端使用。

為瞭解決這個問題,我們的Table組件需要變得更複雜。

資料來源

假設我們的表格式資料每過一段時間要和服務端同步,在瀏覽器端,我們必須藉助ajax,React官方給我們指明了這類需求的方向,通過componentDidMount這一生存周期方法來拉取資料。

componentDidMount 方法,我個人把它比喻成一個“善後”的方法,就是在React把基本的HTML結構掛載到DOM中後,再通過它來做一些善後的事情,例如拉取資料更新DOM等等。

於是我們改一下我們的``Table組件,去掉假資料datas.js,在componentDidMount```中調用我們封裝好的抓取資料方法,每三秒去伺服器抓取一次資料並更新到頁面中。

Table.js

var React = require(‘react‘);var ReactDOM = require(‘react-dom‘);var DOM = React.DOM;var table = DOM.table, tr = DOM.tr, td = DOM.td;var Data = require(‘./data‘);module.exports = React.createClass({    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 () {            Data.fetch(‘http://datas.url.com‘).then(function (datas) {                this.setProps({                    datas: datas                });            });        }, 3000)    }});

這裡假設我們已經封裝了一個拉取資料的Data.fetch方法,例如Data.fetch = jQuery.ajax

到這一步,我們實現了用戶端的每3秒自動更新表格式資料。那麼上面這個Table組件是不是可以直接複用到服務端,實現資料拉取呢,不好意思,答案是“不”

React的奇葩之一,就是其組件有“生存周期”這一說法,在組件的生命的不同時期,例如非同步資料更新,DOM銷毀等等過程,都會調用不同的生命週期方法。

然而服務端情況不同,對服務端來說,它要做的事情便是:去資料庫拉取資料 -> 根據資料產生HTML -> 吐給用戶端。這是一個固定的過程,拉取資料和產生HTML過程是不可打亂順序的,不存在先把內容吐給用戶端,再拉取資料這樣的非同步過程。

所以,componentDidMount這樣的“善後”方法,React在伺服器渲染組件的時候,就不適用了。

而且我還要告訴你,componentDidMount這個方法,在服務端確實永遠都不會執行!

看到這裡,你可能要想,這步坑爹嗎!搞了半天,這個東西只能在用戶端用,說好的同構呢!

別急,拉取資料,我們需要另外的方法。

React中可以通過statics定義“靜態方法”,學過物件導向編程的同學,自然懂statics方法的意思,沒學過的,拉出去打三十大板。

我們再來改一下Table組件,把拉取資料的Data.fetch邏輯放到這裡來。

Table.js:

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...            this.constructor.fetchData(function (datas) {                this.setProps({                    datas: datas                });            });        }, 3000);    }});

非常重要:Table組件能在用戶端和服務端複用fetchData方法拉取資料的關鍵在於,Data.fetch必須在用戶端和服務端有不同的實現!例如在用戶端調用Data.fetch時,是發起ajax請求,而在服務端調用Data.fetch時,有可能是通過UDP協議從其他資料服務器擷取資料、查詢資料庫等實現

由於服務端React不會調用componentDidMount,需要改一下服務端渲染的檔案,同樣不再通過datas.js擷取資料,而是調用Table的靜態方法fetchData,擷取資料後,再傳遞給服務端渲染方法renderToString,擷取資料在實際生產環境中是個非同步過程,所以我們的代碼也需要是非同步:

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) {    Table.fetchData(function (datas) {        var html = ReactDOMServer.renderToString(table({datas: datas}));        callback.call(null, html);    });};

這時候,我們的Table組件已經實現了每3秒更新一次資料,所以,我們既需要在服務端調用React初始html資料,還需要在用戶端調用React即時更新,所以需要在頁面中引入我們打包後的js。

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‘});        makeTable(function (table) {            var html = ‘<!doctype html>\n                      <html>                        <head>                            <title>react server render</title>                        </head>                        <body>‘ +                            table +                            ‘<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/‘);
成果

通過上面的改動,我們在服務端擷取表格式資料,產生HTML供瀏覽器直接渲染;頁面渲染後,Table組件每隔3秒會通過ajax擷取新的表格式資料,有資料更新的話,會直接更新到頁面DOM中。

checksum的作用

還記得前面的問題嗎?

ReactDOMServer.renderToStringReactDOMServer.renderToStaticMarkup 有什麼不同?服務端產生的data-react-checksum是幹嘛使的?

我們想一想,就算服務端沒有初始化HTML資料,僅僅依靠用戶端的React也完全可以實現渲染我們的表格,那服務端產生了HTML資料,會不會在用戶端React執行的時候被重新渲染呢?我們服務端辛辛苦苦產生的東西,被用戶端無情地覆蓋了?

當然不會!React在服務端渲染的時候,會為組件產生相應的校正和(checksum),這樣用戶端React在處理同一個組件的時候,會複用服務端已產生的初始DOM,累加式更新,這就是data-react-checksum的作用。

ReactDOMServer.renderToStringReactDOMServer.renderToStaticMarkup 的區別在這個時候就很好解釋了,前者會為組件產生checksum,而後者不會,後者僅僅產生HTML結構資料。

所以,只有你不想在用戶端-服務端同時操作同一個組件的時候,方可使用renderToStaticMarkup

[轉] React同構思想

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.