React Redux Sever Rendering實戰(zhàn)

React Redux Sever Rendering(Isomorphic JavaScript)

React Redux Sever Rendering(Isomorphic)入門

前言

由于可能有些讀者沒聽過 Isomorphic JavaScript 邓了。因此在進(jìn)到開發(fā) React Redux Sever Rendering 應(yīng)用程式的主題之前我們先來聊聊 Isomorphic JavaScript 這個議題设塔。

根據(jù) Isomorphic JavaScript 這個網(wǎng)站的說明:

Isomorphic JavaScript
Isomorphic JavaScript apps are JavaScript applications that can run both client-side and server-side.
The backend and frontend share the same code.

Isomorphic JavaScript 系指瀏覽器端和伺服器端共用 JavaScript 的程式碼。

另外溯捆,除了 Isomorphic JavaScript 外,讀者或許也有聽過 Universal JavaScript 這個用詞。那什麼是 Universal JavaScript 呢?它和 Isomorphic JavaScript 是指一樣的意思嗎吸祟?針對這個議題網(wǎng)路上有些開發(fā)者提出了自己的觀點: Universal JavaScriptIsomorphism vs Universal JavaScript桃移。其中 Isomorphism vs Universal JavaScript 這篇文章的作者 Gert Hengeveld 指出 Isomorphic JavaScript 主要是指前后端共用 JavaScript 的開發(fā)方式屋匕,而 Universal JavaScript 是指 JavaScript 程式碼可以在不同環(huán)境下運行,這當(dāng)然包含瀏覽器端和伺服器端借杰,甚至其他環(huán)境过吻。也就是說 Universal JavaScript 在意義上可以涵蓋的比 Isomorphic JavaScript 更廣泛一些,然而在 Github 或是許多技術(shù)討論上通常會把兩者視為同一件事情蔗衡,這部份也請讀者留意纤虽。

Isomorphic JavaScript 的好處

在開始真正撰寫 Isomorphic JavaScript 前我們在進(jìn)一步探討使用 Isomorphic JavaScript 有哪些好處?在談好處之前绞惦,我們先看看最早 Web 開發(fā)是如何處理頁面渲染和 state 管理逼纸,還有遇到哪些挑戰(zhàn)。

最早的時候我們談?wù)?Web 很單純济蝉,都是由 Server 端進(jìn)行模版的處理杰刽,你可以想成 template 是一個函數(shù),我們傳送資料進(jìn)去王滤,template 最后產(chǎn)生一張 HTML 給瀏覽器顯示贺嫂。例如:Node 使用的(EJSJade)雁乡、Python/Django 的 Template 或替代方案 Jinja第喳、PHP 的 SmartyLaravel 使用的 Blade蔗怠,甚至是 Ruby on Rails 用的 ERB墩弯。都是由后端去 render 所有資料和頁面,前端處理相對單純寞射。

然而隨著前端工程的軟體工程化和使用者體驗的要求,開始出現(xiàn)各式前端框架的百花齊放锌钮,例如:Backbone.js桥温、Ember.jsAngular.js 等前端 MVC (Model-View-Controller) 或 MVVM (Model-View-ViewModel) 框架,將頁面于前端渲染的不刷頁單頁式應(yīng)用程式(Single Page App)也因此開始流行梁丘。

后端除了提供初始的 HTML 外侵浸,還提供 API Server 讓前端框架可以取得資料用于前端 template旺韭。複雜的邏輯由 ViewModel/Presenter 來處理,前端 template 只處理簡單的是否顯示或是元素迭代的狀況掏觉,如下圖所示:

React Redux Sever Rendering(Isomorphic)入門

然而前端渲染 template 雖然有它的好處但也遇到一些問題包括效能区端、SEO 等議題。此時我們就開始思考 Isomorphic JavaScript 的可能性:為什麼我們不能前后端都使用 JavaScript 甚至是 React澳腹?

React Redux Sever Rendering(Isomorphic)入門

事實上织盼,React 的優(yōu)勢就在于它可以很優(yōu)雅地實現(xiàn) Server Side Rendering 達(dá)到 Isomorphic JavaScript 的效果。在 react-dom/server 中有兩個方法 renderToStringrenderToStaticMarkup 可以在 server 端渲染你的 components酱塔。其主要都是將 React Component 在 Server 端轉(zhuǎn)成 DOM String沥邻,也可以將 props 往下傳,然而事件處理會失效羊娃,要到 client-side 的 React 接收到后才會把它加上去(但要注意 server-side 和 client-side 的 checksum 要一致不然會出現(xiàn)錯誤)唐全,這樣一來可以提高渲染速度和 SEO 效果。renderToStringrenderToStaticMarkup 最大的差異在于 renderToStaticMarkup 會少加一些 React 內(nèi)部使用的 DOM 屬性蕊玷,例如:data-react-id邮利,因此可以節(jié)省一些資源。

使用 renderToString 進(jìn)行 Server 端渲染:

import ReactDOMServer from 'react-dom/server';

ReactDOMServer.renderToString(<HelloButton name="Mark" />);

渲染出來的效果:

<button data-reactid=".7" data-react-checksum="762752829">
  Hello, Mark
</button>

總的來說使用 Isomorphic JavaScript 會有以下的好處:

  1. 有助于 SEO
  2. Rendering 速度較快垃帅,效能較佳
  3. 放棄蹩腳的 Template 語法擁抱 Component 元件化思考延届,便于維護(hù)
  4. 盡量前后端共用程式碼節(jié)省開發(fā)時間

不過要注意的是如果有使用 Redux 在 Server Side Rendering 中,其流程相對複雜挺智,不過大致流程如下:
由后端預(yù)先載入需要的 initialState祷愉,由于 Server 渲染必須全部都轉(zhuǎn)成 string,所以先將 state 先 dehydration(脫水)赦颇,等到 client 端再 rehydration(覆水)二鳄,重建 store 往下傳到前端的 React Component。

而要把資料從伺服器端傳遞到客戶端媒怯,我們需要:

  1. 把取得初始 state 當(dāng)做參數(shù)并對每個請求建立一個全新的 Redux store 實體
  2. 選擇性地 dispatch 一些 action
  3. 把 state 從 store 取出來
  4. 把 state 一起傳到客戶端

接下來我們就開始動手實作一個簡單的 React Server Side Rendering app

專案成果截圖

image

Server Rendering

獲取數(shù)據(jù)可以調(diào)用 action订讼,routes 在服務(wù)器端的處理參考 react-router server rendering,在服務(wù)器端用一個 match 方法將拿到的 request url 匹配到我們之前定義的 routes扇苞,解析成和客戶端一致的 props 對象傳遞給組件欺殿。

./devServer.js

var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev');

import React from 'react';
import { renderToString } from 'react-dom/server';
import { RouterContext, match } from 'react-router';
import { Provider } from 'react-redux';
import createRouter from './client/routes';
import configureStore from './client/store';

var app = express();
var compiler = webpack(config);

import comments from './client/data/comments';
import posts from './client/data/posts';

// create an object for the default data
const defaultState = {
  posts,
  comments
};

app.use(require('webpack-dev-middleware')(compiler, {
  noInfo: true,
  publicPath: config.output.publicPath
}));

app.use(require('webpack-hot-middleware')(compiler));

function renderFullPage(html, initialState) {
  return `
    <!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>isomorphic-redux-app</title>
    <link rel="shortcut icon" type="image/png" />

  </head>
  <body>
    <div id="root">${html}</div>
    <script>
        window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
    </script>
    <script src="/static/bundle.js"></script>
  </body>
</html>
  `;
}

app.use((req, res) => {
 const store = configureStore(defaultState);
 const routes = createRouter();
 const state = store.getState();

  match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {
    if (err) {
      res.status(500).end(`Internal Server Error ${err}`);
    } else if (redirectLocation) {
      res.redirect(redirectLocation.pathname + redirectLocation.search);
    } else if (renderProps) {
      const html = renderToString(
          <Provider store={store}>
            <RouterContext {...renderProps} />
          </Provider>
        );
        res.end(renderFullPage(html, store.getState()));
    } else {
      res.status(404).end('Not found');
    }
  });
});

app.listen(7770, 'localhost', function(err) {
  if (err) {
    console.log(err);
    return;
  }

  console.log('Listening at http://localhost:7770');
});

服務(wù)器端渲染部分可以直接通過共用客戶端 store.dispatch(action) 來統(tǒng)一獲取 Store 數(shù)據(jù)。另外注意 renderFullPage 生成的頁面 HTML 在 React 組件 mount 的部分(<div id="root">)鳖敷,前后端的 HTML 結(jié)構(gòu)應(yīng)該是一致的脖苏。然后要把 store 的狀態(tài)樹寫入一個全局變量(INITIAL_STATE),這樣客戶端初始化 render 的時候能夠校驗服務(wù)器生成的 HTML 結(jié)構(gòu)定踱,并且同步到初始化狀態(tài)棍潘,然后整個頁面被客戶端接管。

本項目地址:React-Redux-Server-Rendering

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市亦歉,隨后出現(xiàn)的幾起案子恤浪,更是在濱河造成了極大的恐慌,老刑警劉巖肴楷,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件水由,死亡現(xiàn)場離奇詭異,居然都是意外死亡赛蔫,警方通過查閱死者的電腦和手機砂客,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來濒募,“玉大人鞭盟,你說我怎么就攤上這事」逄辏” “怎么了齿诉?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晌姚。 經(jīng)常有香客問我粤剧,道長,這世上最難降的妖魔是什么挥唠? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任抵恋,我火速辦了婚禮,結(jié)果婚禮上宝磨,老公的妹妹穿的比我還像新娘弧关。我一直安慰自己,他們只是感情好唤锉,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布世囊。 她就那樣靜靜地躺著,像睡著了一般窿祥。 火紅的嫁衣襯著肌膚如雪株憾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天晒衩,我揣著相機與錄音嗤瞎,去河邊找鬼。 笑死听系,一個胖子當(dāng)著我的面吹牛贝奇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播靠胜,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼弃秆,長吁一口氣:“原來是場噩夢啊……” “哼届惋!你這毒婦竟也來了髓帽?” 一聲冷哼從身側(cè)響起菠赚,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎郑藏,沒想到半個月后衡查,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡必盖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年拌牲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歌粥。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡塌忽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出失驶,到底是詐尸還是另有隱情土居,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布嬉探,位于F島的核電站擦耀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涩堤。R本人自食惡果不足惜眷蜓,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胎围。 院中可真熱鬧吁系,春花似錦、人聲如沸白魂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碧聪。三九已至冒版,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逞姿,已是汗流浹背辞嗡。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留滞造,地道東北人续室。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像谒养,于是被迫代替她去往敵國和親挺狰。 傳聞我的和親對象是個殘疾皇子明郭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容