React 同構開發(一)

NO IMAGE

http://www.cnblogs.com/bingooo/p/5724354.html


為什麼要做同構

要回答這個問題,首先要問什麼是同構。所謂同構,顧名思義就是同一套程式碼,既可以執行在客戶端(瀏覽器),又可以執行在伺服器端(node)。

我們知道,在前端的開發過程中,我們一般都會有一個index.html,
在這個檔案中寫入頁面的基本內容(靜態內容),然後引入JavaScript指令碼根據使用者的操作更改頁面的內容(資料)。在效能優化方面,通常我們所說的種種優化措施也都是在這個基礎之上進行的。在這個模式下,前端所有的工作似乎都被限制在了這一畝三分地之上。

那麼同構給了我們什麼樣的不同呢?前面說到,在同構模式下,客戶端的程式碼也可以執行在伺服器上。換句話說,我們在伺服器端就可以將不同的資料組裝成頁面返回給客戶端(瀏覽器)。這給頁面的效能,尤其是首屏效能帶來了巨大的提升可能。另外,在SEO等方面,同構也提供了極大的便利。除此以外,在整個開發過程中,同構會極大的降低前後端的溝通成本,後端更加專注於業務模型,前端也可以專注於頁面開發,中間的資料轉換大可以交給node這一層來實現,省去了很多來回溝通的成本。

基於React的同構開發

說了這麼多,如何做同構開發呢?
這還得歸功於 React提供的服務端渲染。

ReactDOMServer.renderToString  
ReactDOMServer.renderToStaticMarkup

不同於 ReactDom.render將DOM結構渲染到頁面,
這兩個函式將虛擬DOM在服務端渲染為一段字串,代表了一段完整的HTML結構,最終以html的形式吐給客戶端。

下面看一個簡單的例子:

// 定義元件 
import React, { Component, PropTypes } from 'react';
class News extends Component {
constructor(props) {
super(props);
}
render() {
var {data} = this.props;
return <div className="item">
<a href={data.url}>{ data.title }</a>
</div>;
}
}
export default News;

我們在客戶端,通常通過如下方式渲染這個元件:

// 中間省略了很多其他內容,例如redux等。
let data = {url: 'http://www.taobao.com', title: 'taobao'}
ReactDom.render(<News data={data} />, document.getElementById("container"));

在這個例子中我們寫死了資料,通常情況下,我們需要一個非同步請求拉取資料,再將資料通過props傳遞給News元件。這時候的寫法就類似於這樣:

Ajax.request({params, success: function(data) {
ReactDom.render(<News data={data} />, document.getElementById("container"));    
}});

這時候,非同步的時間就是使用者實際等待的時間。

那麼,在同構模式下,我們怎麼做呢?

// 假設我們的web伺服器使用的是KOA,並且有這樣的一個controller  
function* newsListController() {
const data = yield this.getNews({params});
const data = {
'data': data
};
this.body = ReactDOMServer.renderToString(News(data));
};

這樣的話,我麼在服務端就生成了頁面的所有靜態內容,直接的效果就是減少了因為首屏資料請求導致的使用者的等待時間。除此以外,在禁用JavaScript的瀏覽器中,我們也可以提供足夠的資料內容了。

什麼原理

其實,react同構開發並沒有上面的例子那麼簡單。上面的例子只是為了說明服務端渲染與客戶端渲染的基本不同點。其實,及時已經在服務端渲染好了頁面,我們還是要在客戶端重新使用ReactDom.render函式在render一次的。因為所謂的服務端渲染,僅僅是渲染靜態的頁面內容而已,並不做任何的事件繫結。所有的事件繫結都是在客戶端進行的。為了避免客戶端重複渲染,React提供了一套checksum的機制。所謂checksum,就是React在服務端渲染的時候,會為元件生成相應的校驗和(checksum),這樣客戶端React在處理同一個元件的時候,會複用服務端已生成的初始DOM,增量更新,這就是data-react-checksum的作用。

所以,最終,我們的同構應該是這個樣子的:

// server 端  
function* newsListController() {
const data = yield this.getNews({params});
const data = {
'data': data
};
let news = ReactDOMServer.renderToString(News(data));
this.body = '<!doctype html>\n\
<html>\
<head>\
<title>react server render</title>\
</head>\
<body><div id="container">'  
news  
'</div><script>var window.__INIT_DATA='  JSON.stringify(data)  '</script><script src="app.js"></script>\
</body>\
</html>';
};
// 客戶端,app.js中  
let data = JSON.parse(window.__INIT_DATA__);  
ReactDom.render(<News props={data} />, document.getElementById("container"));

小結

最近一直在做同構相關的東西,本文主要討論react同構開發的基本原理和方式,作為一個引子,其中省去了很多細節問題。關於同構應用開發,其實有很多事情要做,比如node應用的釋出、監控、日誌管理,react元件是否滿足同構要求的自動化檢測等。這些事情都是後續要一步一步去做的,到時候也會做一些整理和積累。