基於React的PC網站前端架構分析

這篇文章是以一個實習生的視角對前端網站架構的一點分析和理解css

最開始接觸前端的時候,是從簡單的html、css、js開始的,當時盛行的WEB理念是結構樣式行爲相分離,即html、css、js分離,獨立開發,互相之間經過link和script來互相調用。html

最開始我所接觸到的小項目,都是直接將html、css、js等靜態資源直接部署到服務器上,而後根據請求路由響應不一樣的html文件便可。 前端

簡單網站結構

即便學習了webpack以後,我依然認爲webpack的做用只是壓縮js和css文件,提升服務器響應速度,優化用戶體驗,而部署到服務器上的依然是壓縮後的min.css和min.js文件。node

後來進入A公司實習以後,確實也是這種開發模式,當時咱們開發h5頁面,都是直接書寫html、css、js文件,而後部署到服務器上,直接訪問html便可。react

後來進入B公司工做以後,才慢慢接觸到真正的前端工程是什麼樣子的。webpack

先後端分離

部分傳統大型PC網站的業務前端部分都是採用的MVC架構方式,也就是每次立項以後,先後端約定好接口,分別開發,開發完畢以後,前端將開發好的頁面交給後端(通常是Java或者PHP),而後由後端響應客戶端請求,返回具體的html頁面。es6

這種開發模式的缺點在於費時費力,溝通成本和聯調正本很是高,前端有一點小的改動都須要先後端一塊兒聯調改動上線,大大增長了總工做量。web

所以,現代大型PC網站通常都採用了先後端分離的架構方式,前端和後端的業務功能各自收斂,能夠分別開發上線,互不影響,能夠極大提升工做效率。數據庫

先後端分離通常分爲兩種:express

  • 沒有中間層的先後端分離;
  • 有中間層的先後端分離。 這裏以目前最火的三大框架之一的react爲主進行介紹。

無中間層 沒有web中間層的先後端分離屬於比較簡單的類型,咱們將一個統一的html/pug模板和其餘的css、js等靜態資源放置到cdn上,每次訪問頁面的時候,直接將模板返回給用戶,而後裏面全部的dom節點和其餘數據都是由js來執行生成的。

無中間層的先後端分離

然而這種沒有中間層的先後端分離的又有不少劣勢:

  • 首屏渲染時間過長;
  • 對seo不友好。
  • 有中間層

有中間層的先後端分離是通常大型項目採用的先後端分離方式。

自從2009年node橫空出世以後,前端也逐漸承擔了一些後端的業務,可是node因爲自身健壯性的限制,又不適合做爲大型項目的後端服務器,因此node熱過一陣以後,逐漸成爲了鏈接傳統前端和後端的中間層,咱們也稱這種前端+node的架構爲「大前端」。

返回模板 在node層中,咱們能夠作的事情就有不少了,其中最基礎的就是返回不一樣的前端模板。

使用過幾款前端模板,其中給我感受最好的就是pug模板了(之前叫作jade)。

pug中的語法都是js語法,對前端工程師十分友好,並且pug功能很強大,能夠做爲html-middleware,被node完美支持,這裏建議學習使用 Get Started

數據拼接

其次,當網站發展地愈來愈大,數據量愈來愈多,對服務層進行分割的時候,會產生不少的服務模塊,或者咱們的數據分散在不一樣的數據庫服務器上的時候,或者咱們的前端頁面中要嵌入第三方廣告的一些api的時候,node就能夠幫咱們完成數據拼接的工做。

由於服務器訪問接口的速度要比瀏覽器快不少個數量級,所以在node中訪問多個接口而且拼接起來是很是高效的,拼接後的數據咱們就能夠直接傳入模板中,供js使用了。

可是一般意義上來講,訪問基礎服務或者從數據服務器訪問數據放在後端來作比較合適,但凡事總有例外,在萬不得已的狀況下,咱們能夠在node中間層中進行數據拼接。

數據拼接

這種模式通常不提倡使用,由於可維護性太差,並且安全性也很低,通常狀況下都是後端有一個專門的數據模塊去訪問數據庫和服務,而後將數據拼接起來,咱們只須要在node中調用後端的一個API,就能夠拿到咱們想要的數據了。

監控服務

node層能夠捕捉一些異常請求或者事件,上報到咱們的第三方監控平臺,如Sentry等,同時node還能夠承擔一部分的數據統計的工做,將一些用戶應爲打到第三方數據統計平臺,供pm和數據分析師查看。

node還能夠承擔對整個實例進行監控的職責,當出現異常致使cpu使用率或者內存使用率超過閾值以後就能夠及時觸發報警機制。

可是一樣,加上node層就意味這網站又多了一層後端須要監控和oncall,工做的複雜度會上升不少。

服務器端渲染

SSR(server-side-render)能夠說是很是重要的功能之一了,它能夠幫助咱們解決以前提到的首屏渲染時間過長和對seo支持較低的問題。

現代seo爬蟲通常分爲兩種:

  • 支持解析js的爬蟲,這類數量較少,以Google爲表明;
  • 不支持解析js的爬蟲,大部分都是這類,基本上都是非Google的搜索引擎的爬蟲了。 對於Google的爬蟲來講,是否使用SSR在seo方面可有可無,由於最終均可以爬取到正常的頁面。

而對於非Google的搜索引擎來講,咱們就須要利用SSR,先將具體的dom節點渲染出來,供爬蟲爬取。

並且這樣同時還有一個優勢:用戶在網頁loading的過程就中能夠看到頁面內容了,而不是一個空白頁面。若是不使用SSR的話,在網頁loading資源的過程當中,一直呈現給用戶一片空白,這就有可能形成用戶的流失。

B站SSR

這張圖是我在50kb/s的網速下,訪問b站第一秒鐘看到的內容,如果b站不使用SSR技術的話,可能等到用戶可以看到首屏內容以後,時間都過去了五六秒。

這裏是一個簡單的使用SSR的Node層代碼:

// 代碼中使用了es6語法,不懂的能夠先學習一下阮一峯老師的《ES6入門》
// 這個地方node若是沒有使用babel的話,import會報錯,能夠直接使用require方法替換
import { renderToString } from 'react-dom/server';
import DemoContainer from 'containers/demo';
// 以koa2框架爲例
module.exports = (ctx) => {
    const props = {...};
    // 這裏的html就存放着咱們組件render完以後的dom節點。
    const html = renderToString(<Demo >); // 這裏以返回pug模板爲例,第二個參數是要傳入pug模板中的數據 ctx.render('demo.pug', { __props: JSON.stringify(props), html }); }; 複製代碼

這裏是pug的代碼片斷:

// pug代碼片斷
body
    #root
        !{ html }
    script.
        window.__props = '!{ __props }'
複製代碼

使用SSR的時候要切記保證前端和服務器端的組件props保持一致,所以這裏個人習慣是在node層將props直接傳入window對象上,而後前端的組件直接從window對象獲取props便可。

SSR的時候,React組件只會執行componentWillMount和render兩個生命週期用來生成dom結構,其餘的生命週期以及方法掛載都是在前端完成的。

node層的功能不止以上這些,這裏就不過多展開介紹了。

雖然SSR的有點不少,可是仍是有自身的弊端的。使用SSR就意味着你用本身服務器代替了一部分本來屬於用戶客戶端的功能,所以會形成服務器性能下降,成本增高的可能,相對於小團隊或者資金不算充裕的團隊,要謹慎選擇是否使用SSR。

先後端同構

提及SSR,就不得不提一下先後端同構問題。 同構的意思爲前端和node用執行同一套代碼,首屏使用服務端渲染,將渲染好的html直接交給瀏覽器去渲染,客戶端負責加載js,執行組件剩餘生命週期,並掛載自定義事件等。 一套好的先後端同構代碼能夠大幅減小咱們維護代碼的工做量,而且有十分高效的執行效率,如何優雅地書寫先後端同構的代碼也是一項技術活,須要咱們提早規劃好一套前端架構。

例如:
import React, { Component } from "react";
import { Provider } from "react-redux";
import ReactDOM from "react-dom";
import Loadable from "react-loadable";
import { BrowserRouter, StaticRouter } from "react-router-dom";

// server side render
const SSR = App =>
  class SSR extends Component<{
    store: any;
    url: string;
  }> {
    render() {
      const context = {};
      return (
        <Provider store={this.props.store} context={context}> <StaticRouter location={this.props.url}> <App /> </StaticRouter> </Provider>
      );
    }
  };

// client side render
const CLIENT = configureState => Component => {
  const initStates = window.__INIT_STATES__;
  const store = configureState(initStates);
  Loadable.preloadReady().then(() => {
    ReactDOM.hydrate(
      <Provider store={store}> <BrowserRouter> <Component /> </BrowserRouter> </Provider>,
      document.getElementById("root")
    );
  });
};

export default function entry(configureState) {
  return IS_NODE ? SSR : CLIENT(configureState);
}
複製代碼

而且在同構方面,阿里有一套降級策略。當服務器壓力正常時,由服務器進行SSR,提升用戶體驗,當用戶訪問量激增,如雙十一時,服務器會自動進行降級處理,node不進行SSR,所有轉換成客戶端渲染,減輕服務器的壓力。

選擇框架

瞭解了先後端分離以後,咱們就要對node層進行框架選擇了。

目前比較主流的框架有三款:Express、koa1.0、koa2.0。

對於初學者來講,建議直接使用koa2.0進行中間層的學習和開發。

express的缺點在於:

  • 過重,有不少模塊咱們可能都不會用到; 回調地獄,即便使用Promise也只能緩解。 koa1.0的缺點在於:

  • 必須配合co庫和generator來使用,配置繁瑣。 而自從node升級到7.6版本以上,增長了async/await語法糖以後,咱們就能夠不須要任何三方庫,直接在原生node中使用koa2的語法。

koa2是express的升級版框架,裏面不少模塊是直接從express中遷移過來的,可是又將之前不常常用到的模塊刪除,只有開發者在須要使用的時候採起引入那麼模塊。

而且koa2使用async/await語法糖以後,代碼看似變成了同步執行,很是適合前端工程師的邏輯思惟。

這裏是express、promise、koa2的樣例代碼:

// express版本
module.exports = (req, res) => {
    const data1 = request.get('/api/demo1', (err, res) => {
        const data2 = request.get('/api/demo2', (err, res) => {
            const data3 = request.get('/api/demo3', (err, res) => {
                res.send(data1 + data2 + data3);
            })
        })
    })
}
// promise版本
module.exports = (req, res) => {
    new Promise((resolve, reject) => {
        request.get('/api/demo1', (err, res) => {
            resolve(res);
        }).then(res => {
            request.get('/api/demo2', (err, res2) => res + res2 );
        }).then(res2 => {
            request.get('/api/demo3', (err, res3) => res2 + res3)
        }).then((data) => {
            res.send(data);
        });
    })
}
複製代碼

看起來雖然整齊了一些,可是依然十分繁瑣。

// koa1和koa2在寫法上基本相同,區別在於koa1在使用以前要對co庫和generator進行繁瑣的配置。
// 每個await的時候最好加上try-catch,防止由於一個異步請求失敗而致使node進程崩潰,這裏簡化了寫法。
module.exports = async (ctx) => {
    const data1 = await request.get('/api/demo1');
    const data2 = await request.get('/api/demo2');
    const data3 = await request.get('api/demo3');
    ctx.body = {
        data: data1 + data2 + data3
    };
}
複製代碼

koa2用起來很是的舒服,很適合前端工程師的思惟邏輯。

雖然koa2的代碼看起來像同步執行,但其實在編譯以後只是變成了promise函數,await後面全部的代碼都放到了promise的回調中執行了。

開發結構

選擇好了框架以後,剩下的就只有開發了,通常的node層都遵循一下的目錄結構:

node

  • lib // 存放第三方插件
  • util // 存放本身編寫的工具函數
  • middleware // 存放中間件
  • routes // 存放路由
  • controller // 存放路由處理函數
  • app.js // node層入口文件 基本的node層架構這裏就介紹差很少了,剩下的前端部分也通常是你們熟悉的東西。 前端目錄結構:

public

  • static
  • src
  • js
  • components
  • containers
  • routes
  • stores
  • actions
  • reducers
  • pages
  • css/scss/less
  • static 這裏按照正常的React開發邏輯去走便可。

最後還有一些其餘的文件夾能夠自由發揮,好比template存放模板,scripts存放平時寫的腳本等。

配置

一個線上項目要擁有兩套模式——生產模式和開發模式。

生產模式即咱們線上運行環境。

開發模式即咱們平時本地開發環境。

若是有須要的話甚至能夠配置更多的環境。

這兩種環境的要求不同,所以咱們會有兩套配置文件,將不一樣的配置文件傳入node和webpack中,就能夠根據配置的不一樣啓動不一樣環境了。

配置

自動化測試

自動化測試在一個成熟的大型網站中必不可少。

雖然目前由於前端領域的快速增加,業務層的自動化測試也由於業務的快速迭代而變得不穩定,可是一些基礎的測試仍是頗有必要作的。

平時開發的時候要作好類庫單元測試的自動化以及UI組件的單元測試的自動化。

這些測試文件最好存放在單獨的test目錄下,或者在每個基礎UI組件目錄下加上component.test.js文件,這樣啓動測試的時候會自動找到.test文件進行測試。

每次項目上線以前都要進行一次集成測試,測試路由是否正常,webpack打包和node模塊安裝是否正常,模擬用戶登陸訪問等操做是否正常。

偶爾咱們還須要作壓力測試和容災測試等。

對於初學者來講,測試是一個很重要的概念和習慣,平時要多寫一寫單元測試。

相關文章
相關標籤/搜索