為什么要做同構
要回答這個問題,首先要問什么是同構。所謂同構,顧名思義就是同一套代碼,既可以運行在客戶端(瀏覽器),又可以運行在服務器端(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組件是否滿足同構要求的自動化檢測等。這些事情都是后續要一步一步去做的,到時候也會做一些整理和積累。