React 官網(wǎng)為什么那么快?

當(dāng)我們打開 React 官網(wǎng)時(shí),會(huì)發(fā)現(xiàn)從瀏覽器上輸入url 到頁(yè)面首屏完全展示這一過(guò)程所花的時(shí)間極短,而且在頁(yè)面中點(diǎn)擊鏈接切換路由的操作非常順滑,幾乎頁(yè)面可以達(dá)到“秒切”的效果,根本不會(huì)有卡頓等待的情況發(fā)生,于是帶著“react官網(wǎng)到底是怎么做的”疑問(wèn)開始了本次探索,發(fā)現(xiàn)其主要用了以下的優(yōu)化手段

靜態(tài)站點(diǎn)生成 SSG
下面是react官方中文文檔首頁(yè)的截圖,大家注意下方的紅色區(qū)域,后面會(huì)作為推斷的一個(gè)理由


當(dāng)我們打開控制臺(tái)之后,點(diǎn)擊network并選擇 DOC文檔請(qǐng)求,就會(huì)發(fā)現(xiàn)有一個(gè)請(qǐng)求路徑為https://zh-hans.reactjs.org/ 的GET請(qǐng)求,響應(yīng)結(jié)果為一個(gè) html文檔,里面剛好能找到對(duì)應(yīng)上圖中紅色區(qū)域文字的文本,這也就佐證了這個(gè)html文檔所對(duì)應(yīng)的頁(yè)面就是react官網(wǎng)首頁(yè),而這種渲染頁(yè)面的方式只有兩種,一種是服務(wù)端渲染 SSR,還有一種是靜態(tài)站點(diǎn)生成 SSG

很多人總是分不清客戶端渲染CSR、服務(wù)端渲染SSR還有靜態(tài)站點(diǎn)生成SSG,下面我們簡(jiǎn)單介紹一下它們各自的特點(diǎn),看完之后相信你就能清晰的感受到它們的區(qū)別所在了

頁(yè)面的渲染流程
在開始之前,我們先來(lái)回顧一下頁(yè)面最基本的渲染流程是怎么樣的?

瀏覽器通過(guò)請(qǐng)求得到一個(gè) HTML文本
渲染進(jìn)程解析 HTML 文本,構(gòu)建 DOM 樹
瀏覽器解析 HTML 的同時(shí),如果遇到內(nèi)聯(lián)樣式或者樣本樣式,則下載并構(gòu)建樣式規(guī)則(stytle rules)。若遇到 Javascript 腳本,則會(huì)下載并執(zhí)行腳本
DOM 樹和樣式規(guī)則構(gòu)建完成之后,渲染進(jìn)程將兩者合并成渲染樹(render tree)
渲染進(jìn)程開始對(duì)渲染樹進(jìn)行布局,生成布局樹(layout tree)
渲染進(jìn)程對(duì)布局樹進(jìn)行繪制,生成繪制記錄
渲染進(jìn)程對(duì)布局樹進(jìn)行分層,分別柵格化每一層并得到合成幀
渲染進(jìn)程將合成幀發(fā)送給 GPU 進(jìn)程將圖像繪制到頁(yè)面中

可以看到,頁(yè)面的渲染其實(shí)就是瀏覽器將HTML文本轉(zhuǎn)化為頁(yè)面幀的過(guò)程,下面我們?cè)賮?lái)看看剛剛提到的技術(shù):

客戶端渲染 CSR
如今我們大部分 WEB 應(yīng)用都是使用 JavaScript 框架(Vue、React、Angular)進(jìn)行頁(yè)面渲染的,頁(yè)面中的大部分DOM元素都是通過(guò)Javascript插入的。也就是說(shuō),在執(zhí)行 JavaScript 腳本之前,HTML 頁(yè)面已經(jīng)開始解析并且構(gòu)建 DOM 樹了,JavaScript 腳本只是動(dòng)態(tài)的改變 DOM 樹的結(jié)構(gòu),使得頁(yè)面成為希望成為的樣子,這種渲染方式叫動(dòng)態(tài)渲染,也就是平時(shí)我們所稱的客戶端渲染 CSR(client side render)

下面代碼為瀏覽器請(qǐng)求 react 編寫的單頁(yè)面應(yīng)用網(wǎng)頁(yè)時(shí)響應(yīng)回的HTML文檔,其實(shí)它只是一個(gè)空殼,里面并沒(méi)有具體的文本內(nèi)容,需要執(zhí)行 JavaScript 腳本之后才會(huì)渲染我們真正想要的頁(yè)面

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <link rel="icon" href="/favicon.ico" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="/logo192.png" />
  <link rel="manifest" href="/manifest.json" />
  <title>Jira任務(wù)管理系統(tǒng)</title>
  <script
    type="text/javascript">!function (n) { if ("/" === n.search[1]) { var a = n.search.slice(1).split("&").map((function (n) { return n.replace(/~and~/g, "&") })).join("?"); window.history.replaceState(null, null, n.pathname.slice(0, -1) + a + n.hash) } }(window.location)</script>
  <link href="/static/css/2.4ddacf8e.chunk.css" rel="stylesheet">
  <link href="/static/css/main.cecc54dc.chunk.css" rel="stylesheet">
</head>

<body><noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <script>!function (e) { function r(r) { for (var n, a, i = r[0], c = r[1], f = r[2], s = 0, p = []; s < i.length; s++)a = i[s], Object.prototype.hasOwnProperty.call(o, a) && o[a] && p.push(o[a][0]), o[a] = 0; for (n in c) Object.prototype.hasOwnProperty.call(c, n) && (e[n] = c[n]); for (l && l(r); p.length;)p.shift()(); return u.push.apply(u, f || []), t() } function t() { for (var e, r = 0; r < u.length; r++) { for (var t = u[r], n = !0, i = 1; i < t.length; i++) { var c = t[i]; 0 !== o[c] && (n = !1) } n && (u.splice(r--, 1), e = a(a.s = t[0])) } return e } var n = {}, o = { 1: 0 }, u = []; function a(r) { if (n[r]) return n[r].exports; var t = n[r] = { i: r, l: !1, exports: {} }; return e[r].call(t.exports, t, t.exports, a), t.l = !0, t.exports } a.e = function (e) { var r = [], t = o[e]; if (0 !== t) if (t) r.push(t[2]); else { var n = new Promise((function (r, n) { t = o[e] = [r, n] })); r.push(t[2] = n); var u, i = document.createElement("script"); i.charset = "utf-8", i.timeout = 120, a.nc && i.setAttribute("nonce", a.nc), i.src = function (e) { return a.p + "static/js/" + ({}[e] || e) + "." + { 3: "20af26c9", 4: "b947f395", 5: "ced9b269", 6: "5785ecf8" }[e] + ".chunk.js" }(e); var c = new Error; u = function (r) { i.onerror = i.onload = null, clearTimeout(f); var t = o[e]; if (0 !== t) { if (t) { var n = r && ("load" === r.type ? "missing" : r.type), u = r && r.target && r.target.src; c.message = "Loading chunk " + e + " failed.\n(" + n + ": " + u + ")", c.name = "ChunkLoadError", c.type = n, c.request = u, t[1](c) } o[e] = void 0 } }; var f = setTimeout((function () { u({ type: "timeout", target: i }) }), 12e4); i.onerror = i.onload = u, document.head.appendChild(i) } return Promise.all(r) }, a.m = e, a.c = n, a.d = function (e, r, t) { a.o(e, r) || Object.defineProperty(e, r, { enumerable: !0, get: t }) }, a.r = function (e) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e, "__esModule", { value: !0 }) }, a.t = function (e, r) { if (1 & r && (e = a(e)), 8 & r) return e; if (4 & r && "object" == typeof e && e && e.__esModule) return e; var t = Object.create(null); if (a.r(t), Object.defineProperty(t, "default", { enumerable: !0, value: e }), 2 & r && "string" != typeof e) for (var n in e) a.d(t, n, function (r) { return e[r] }.bind(null, n)); return t }, a.n = function (e) { var r = e && e.__esModule ? function () { return e.default } : function () { return e }; return a.d(r, "a", r), r }, a.o = function (e, r) { return Object.prototype.hasOwnProperty.call(e, r) }, a.p = "/", a.oe = function (e) { throw console.error(e), e }; var i = this.webpackJsonpjira = this.webpackJsonpjira || [], c = i.push.bind(i); i.push = r, i = i.slice(); for (var f = 0; f < i.length; f++)r(i[f]); var l = c; t() }([])</script>
  <script src="/static/js/2.2b45c055.chunk.js"></script>
  <script src="/static/js/main.3224dcfd.chunk.js"></script>
</body>

</html>
復(fù)制代碼
服務(wù)端渲染 SSR
顧名思義,服務(wù)端渲染就是在瀏覽器請(qǐng)求頁(yè)面 URL 的時(shí)候,服務(wù)端將我們需要的 HTML 文本組裝好,并返回給瀏覽器,這個(gè) HTML 文本被瀏覽器解析之后,不需要經(jīng)過(guò) JavaScript 腳本的下載過(guò)程,即可直接構(gòu)建出我們所希望的 DOM 樹并展示到頁(yè)面中。這個(gè)服務(wù)端組裝HTML的過(guò)程就叫做服務(wù)端渲染 SSR

下面是服務(wù)端渲染時(shí)返回的 HTML 文檔,由于代碼量實(shí)在是太多,所以只保留了具有象征意義的部分代碼,但不難發(fā)現(xiàn),服務(wù)端渲染返回的HTML文檔中具有頁(yè)面的核心文本

<!DOCTYPE html>
<html lang="zh-hans">

<head>
  <link rel="preload" as="script" />
  <meta name="generator" content="Gatsby 2.24.63" />
  <style data-href="/styles.dc271aeba0722d3e3461.css">
    /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
    html {
      line-height: 1.15;
      -webkit-text-size-adjust: 100%
    }

    /* ....many CSS style */
  </style>
</head>

<body>
  <script>
    (function () {
      /*
        BE CAREFUL!
        This code is not compiled by our transforms
        so it needs to stay compatible with older browsers.
      */

      var activeSurveyBanner = null;
      var socialBanner = null;
      var snoozeStartDate = null;
      var today = new Date();

      function addTimes(date, days) {
        var time = new Date(date);
        time.setDate(time.getDate() + days);
        return time;
      }
      // ...many js code
    })();
  </script>
  <div id="___gatsby">
    <!-- ...many html dom -->
    <div class="css-1vcfx3l">
      <h3 class="css-1qu2cfp">一次學(xué)習(xí),跨平臺(tái)編寫</h3>
      <div>
        <p>無(wú)論你現(xiàn)在使用什么技術(shù)棧,在無(wú)需重寫現(xiàn)有代碼的前提下,通過(guò)引入 React 來(lái)開發(fā)新功能。</p>
        <p>React 還可以使用 Node 進(jìn)行服務(wù)器渲染,或使用 <a target="_blank"
                                           rel="nofollow noopener noreferrer">React Native</a> 開發(fā)原生移動(dòng)應(yīng)用。</p>
      </div>
    </div>
     <!-- ...many html dom -->
  </div>

</body>

</html>
復(fù)制代碼






靜態(tài)站點(diǎn)生成 SSG
這也就是React官網(wǎng)所用到的技術(shù),與SSR的相同之處就是對(duì)應(yīng)的服務(wù)端同樣是將已經(jīng)組合好的HTML文檔直接返回給客戶端,所以客戶端依舊不需要下載Javascript文件就能渲染出整個(gè)頁(yè)面,那不同之處又有哪些呢?

使用了SSG技術(shù)搭建出的網(wǎng)站,每個(gè)頁(yè)面對(duì)應(yīng)的HTML文檔在項(xiàng)目build打包構(gòu)建時(shí)就已經(jīng)生成好了,用戶請(qǐng)求的時(shí)候服務(wù)端不需要再發(fā)送其它請(qǐng)求和進(jìn)行二次組裝,直接將該HTML文檔響應(yīng)給客戶端即可,客戶端與服務(wù)端之間的通信也就變得更加簡(jiǎn)單

但讀到這里很容易會(huì)發(fā)現(xiàn)它有幾個(gè)致命的弱點(diǎn):

HTML文檔既然是在項(xiàng)目打包時(shí)就已經(jīng)生成好了,那么所有用戶看到的都只能是同一個(gè)頁(yè)面,就像是一個(gè)靜態(tài)網(wǎng)站一樣,這也是這項(xiàng)技術(shù)的關(guān)鍵字眼——靜態(tài)
每次更改內(nèi)容時(shí)都需要構(gòu)建和部署應(yīng)用程序,所以其具有很強(qiáng)的局限性,不適合制作內(nèi)容經(jīng)常會(huì)變換的網(wǎng)站
但每項(xiàng)技術(shù)的出現(xiàn)都有其對(duì)應(yīng)的使用場(chǎng)景,我們不能因?yàn)槟稠?xiàng)技術(shù)的某個(gè)缺點(diǎn)就否定它,也不能因?yàn)槟稠?xiàng)技術(shù)的某個(gè)優(yōu)點(diǎn)就濫用它! 該技術(shù)還是有部分應(yīng)用場(chǎng)景的,如果您想要搭建一個(gè)充滿靜態(tài)內(nèi)容的網(wǎng)站,比如個(gè)人博客、項(xiàng)目使用文檔等Web應(yīng)用程序,使用SSG再適合不過(guò)了,使用過(guò)后我相信你一定能感受到這項(xiàng)技術(shù)的強(qiáng)大之處!

問(wèn)題解答
現(xiàn)在我們就可以回答為什么react官網(wǎng)要使用SSG這項(xiàng)技術(shù)去做了?

因?yàn)橄鄬?duì)于客戶端渲染,服務(wù)端渲染和靜態(tài)網(wǎng)點(diǎn)生成在瀏覽器請(qǐng)求URL之后得到的是一個(gè)帶有數(shù)據(jù)的HTML文本,并不是一個(gè)HTML空殼。瀏覽器只需要解析HTML,直接構(gòu)建DOM樹就可以了。而客戶端渲染,需要先得到一個(gè)空的HTML頁(yè)面,這個(gè)時(shí)候頁(yè)面已經(jīng)進(jìn)入白屏,之后還需要經(jīng)過(guò)加載并執(zhí)行 JavaScript、請(qǐng)求后端服務(wù)器獲取數(shù)據(jù)、JavaScript 渲染頁(yè)面幾個(gè)過(guò)程才可以看到最后的頁(yè)面。特別是在復(fù)雜應(yīng)用中,由于需要加載 JavaScript 腳本,越是復(fù)雜的應(yīng)用,需要加載的 JavaScript腳本就越多、越大,這會(huì)導(dǎo)致應(yīng)用的首屏加載時(shí)間非常長(zhǎng),從而降低了體驗(yàn)感

至于SSR與SSG的選取,我們要從應(yīng)用場(chǎng)景出發(fā),到底是用戶每次請(qǐng)求都在服務(wù)端重新組裝一個(gè)HTML文檔?還是在項(xiàng)目構(gòu)建的時(shí)候就生成一個(gè)唯一的HTML文檔呢?

React團(tuán)隊(duì)成員在開發(fā)官網(wǎng)的時(shí)候肯定早就想到了這個(gè)問(wèn)題,既然是官網(wǎng),那肯定沒(méi)有權(quán)限之分,所有進(jìn)入到該網(wǎng)站的人看到的內(nèi)容應(yīng)該是一樣的才對(duì),那每次請(qǐng)求都在服務(wù)端組裝一個(gè)一模一樣的HTML有什么意義呢?為什么不提前在服務(wù)端渲染好,然后發(fā)給每個(gè)人,這樣N次渲染就變成了1次渲染,大大減少了客戶端與服務(wù)端通信的時(shí)間,進(jìn)而提升了用戶體驗(yàn)

總結(jié)
無(wú)論是哪種渲染方式,一開始都是要請(qǐng)求一個(gè) HTML 文本,但是區(qū)別就在于這個(gè)文本是否已經(jīng)被服務(wù)端組裝好了

客戶端渲染還需要去下載和執(zhí)行額外的Javascript腳本之后才能得到我們想要的頁(yè)面效果,所以速度會(huì)比服務(wù)端渲染慢很多
服務(wù)端渲染得到的HTML文檔就已經(jīng)組合好了對(duì)應(yīng)的文本,瀏覽器請(qǐng)求到之后直接解析渲染出來(lái)即可,不需要再去下載和執(zhí)行額外的Javasript 腳本,所以速度會(huì)比客戶端渲染快很多
對(duì)于一些內(nèi)容不經(jīng)常變化的網(wǎng)站,我們甚至可以在服務(wù)端渲染的基礎(chǔ)上予以改進(jìn),將每次請(qǐng)求服務(wù)端都渲染一次HTML文檔改成總共就只渲染一次,這就是靜態(tài)站點(diǎn)生成技術(shù)
下圖是客戶端渲染和服務(wù)端渲染的流程圖:

一些預(yù)加載/預(yù)處理資源的方式
研究完首屏渲染之后,我們?cè)賮?lái)研究一下路由跳轉(zhuǎn)后內(nèi)容的切換。經(jīng)常看 react 文檔的朋友可能早就發(fā)現(xiàn)了,其路由跳轉(zhuǎn)無(wú)比絲滑,感覺(jué)就像是一個(gè)靜態(tài)頁(yè)面一樣,完全沒(méi)有發(fā)送網(wǎng)絡(luò)請(qǐng)求的痕跡,比如我現(xiàn)在處在hook 簡(jiǎn)介這一個(gè)板塊,當(dāng)我點(diǎn)擊 hook 規(guī)則 目錄之后


發(fā)現(xiàn)頁(yè)面瞬間秒切了過(guò)去,內(nèi)容也瞬間展現(xiàn)在了出來(lái),沒(méi)有一絲卡頓,用戶體驗(yàn)直接爆炸,這到底是怎么做到的呢?


下面我們就來(lái)一點(diǎn)一點(diǎn)分析它的每個(gè)優(yōu)化手段

preload
在當(dāng)前頁(yè)面中,你可以指定可能或很快就需要的資源在其頁(yè)面生命周期的早期——瀏覽器的主渲染機(jī)制介入前就進(jìn)行預(yù)加載,這可以讓對(duì)應(yīng)的資源更早的得到加載并使用,也更不易阻塞頁(yè)面的初步渲染,進(jìn)而提升性能






關(guān)鍵字 preload 作為元素 <link> 的屬性 rel的值,表示用戶十分有可能需要在當(dāng)前瀏覽中加載目標(biāo)資源,所以瀏覽器必須預(yù)先獲取和緩存對(duì)應(yīng)資源 。下面我們來(lái)看一個(gè)示例:

<link as="script" rel="preload" href="/webpack-runtime-732352b70a6d0733ac95.js">
復(fù)制代碼
這樣做的好處就是讓在當(dāng)前頁(yè)面中可能被訪問(wèn)到的資源提前加載但并不阻塞頁(yè)面的初步渲染,進(jìn)而提升性能

下面是 react文檔中對(duì) preload關(guān)鍵字的使用,告訴瀏覽器等等可能需要這個(gè)資源,希望能夠盡早下載下來(lái)

可以預(yù)加載的資源有很多,現(xiàn)在瀏覽器支持的主要有:

audio:音頻文件,通常用于 audio 標(biāo)簽
document: 旨在由 frame 或嵌入的 HTML 文檔
embed:要嵌入到 embed 元素中的資源
fetch:要通過(guò) fetch 或 XHR 請(qǐng)求訪問(wèn)的資源,例如 ArrayBuffer 或 JSON 文件
font: 字體文件
image:圖像文件
object:要嵌入到 object 元素中的資源
script: JavaScript 文件
style: CSS 樣式表
track: WebVTT 文件
worker:一個(gè) JavaScript 網(wǎng)絡(luò)工作者或共享工作者
video:視頻文件,通常用于 video 標(biāo)簽
注意:使用 preload作為 link標(biāo)簽rel屬性的屬性值的話一定要記得在標(biāo)簽上添加 as屬性,其屬性值就是要預(yù)加載的內(nèi)容類型

preconnect
元素屬性的關(guān)鍵字preconnect是提示瀏覽器用戶可能需要來(lái)自目標(biāo)域名的資源,因此瀏覽器可以通過(guò)搶先啟動(dòng)與該域名的連接來(lái)改善用戶體驗(yàn) —— MDN

下面來(lái)看一個(gè)用法示例:

<link rel="preconnect" >
復(fù)制代碼
下面是 react官方文檔中的使用:

簡(jiǎn)單來(lái)說(shuō)就是提前告訴瀏覽器,在后面的js代碼中可能會(huì)去請(qǐng)求這個(gè)域名下對(duì)應(yīng)的資源,你可以先去把網(wǎng)絡(luò)連接建立好,到時(shí)候發(fā)送對(duì)應(yīng)請(qǐng)求時(shí)也就更加快速

dns-prefetch
DNS-prefetch (DNS 預(yù)獲取) 是嘗試在請(qǐng)求資源之前解析域名。這可能是后面要加載的文件,也可能是用戶嘗試打開的鏈接目標(biāo) —— MDN

那我們?yōu)槭裁匆M(jìn)行域名預(yù)解析呢?這里面其實(shí)涉及了一些網(wǎng)絡(luò)請(qǐng)求的東西,下面簡(jiǎn)單介紹一下:

當(dāng)瀏覽器從(第三方)服務(wù)器請(qǐng)求資源時(shí),必須先將該跨域域名解析為 IP 地址,然后瀏覽器才能發(fā)出請(qǐng)求。此過(guò)程稱為 DNS 解析。DNS 緩存可以幫助減少此延遲,而 DNS 解析可以導(dǎo)致請(qǐng)求增加明顯的延遲。對(duì)于打開了與許多第三方的連接的網(wǎng)站,此延遲可能會(huì)大大降低加載性能。預(yù)解析域名就是為了在真正發(fā)請(qǐng)求的時(shí)候減少延遲,從而在一定程度上提高性能

用法示例:

<link rel="dns-prefetch" >
復(fù)制代碼
下面是 react官方文檔中的使用:

通俗點(diǎn)來(lái)說(shuō),dns-prefetch 的作用就是告訴瀏覽器在給第三方服務(wù)器發(fā)送請(qǐng)求之前去把指定域名的解析工作給做了,這個(gè)優(yōu)化方法一般會(huì)和上面的preconnect一起使用,這些都是性能優(yōu)化的一些手段,我們也可以在自己項(xiàng)目中合適的地方來(lái)使用

prefetch
關(guān)鍵字 prefetch 作為元素  的屬性 rel 的值,是為了提示瀏覽器,用戶未來(lái)的瀏覽有可能需要加載目標(biāo)資源,所以瀏覽器會(huì)事先獲取和緩存對(duì)應(yīng)資源,優(yōu)化用戶體驗(yàn) ——MDN

上面的解釋已經(jīng)很通俗易懂了,就是告訴瀏覽器用戶未來(lái)可能需要這些資源,這樣瀏覽器可以提前獲取這些資源,等到用戶真正需要使用這些資源的時(shí)候一般都已經(jīng)加載好了,內(nèi)容展示就會(huì)十分的流暢

用法示例:

<link rel="prefetch" href="/page-data/docs/getting-started.html/page-data.json" crossorigin="anonymous" as="fetch">
復(fù)制代碼
可以看到 react文檔在項(xiàng)目中大量使用到了 prefetch來(lái)優(yōu)化項(xiàng)目

那么我們?cè)谑裁辞闆r下使用 prefetch才比較合適呢?

像 react文檔一樣,當(dāng)你的頁(yè)面中具有可能跳轉(zhuǎn)到其他頁(yè)面的路由鏈接時(shí),就可以使用prefetch 預(yù)請(qǐng)求對(duì)應(yīng)頁(yè)面的資源了

但如果一個(gè)頁(yè)面中這樣的路由鏈接很多呢?那豈不是要大量的發(fā)送網(wǎng)絡(luò)請(qǐng)求,雖然現(xiàn)在流量很便宜,但你也不能那么玩?。。╠oge)

React 當(dāng)然考慮到了這個(gè)問(wèn)題,因?yàn)樵谒奈臋n中包含有大量的路由鏈接,不可能全部都發(fā)一遍請(qǐng)求,這樣反而不利于性能優(yōu)化,那react是怎么做的呢?

通過(guò)監(jiān)聽 Link元素,當(dāng)其出現(xiàn)到可見(jiàn)區(qū)域時(shí)動(dòng)態(tài)插入帶有prefetch屬性值的link標(biāo)簽到HTML文檔中,從而去預(yù)加載對(duì)應(yīng)路由頁(yè)面的一些資源,這樣當(dāng)用戶點(diǎn)擊路由鏈接跳轉(zhuǎn)過(guò)去時(shí)由于資源已經(jīng)請(qǐng)求好所以頁(yè)面加載會(huì)特別快

舉個(gè)例子,還沒(méi)有點(diǎn)擊下圖中劃紅線的目錄時(shí),由于其子目錄沒(méi)有暴露到視圖窗口中,所以頁(yè)面中并沒(méi)有對(duì)應(yīng)的標(biāo)簽,而當(dāng)點(diǎn)擊了該目錄后,其子目錄就會(huì)展示在視圖窗口中,react會(huì)自動(dòng)將暴露出來(lái)的路由所對(duì)應(yīng)的數(shù)據(jù)通過(guò)prefetch提前請(qǐng)求過(guò)來(lái),這樣當(dāng)用戶點(diǎn)擊某個(gè)子目錄的時(shí)候,由于已經(jīng)有了對(duì)應(yīng)的數(shù)據(jù),直接獲取內(nèi)容進(jìn)行展示即可。用這樣的方法,我們感受到的速度能不快嗎?

下面是我們?cè)趎etwork查看到的結(jié)果


補(bǔ)充
react官網(wǎng)其實(shí)并不完全是由react這個(gè)框架進(jìn)行開發(fā)的,能做上述所說(shuō)的那么多性能優(yōu)化其實(shí)得益于Gatsby這個(gè)庫(kù)

Gatsby 是一個(gè)性能很好,開發(fā)很自由的,基于 React 和 GraphQL 來(lái)構(gòu)建網(wǎng)站的庫(kù)。一般用于構(gòu)建靜態(tài)網(wǎng)站,比如博客、企業(yè)官網(wǎng)等,或者說(shuō)靜態(tài)內(nèi)容相對(duì)比較多的網(wǎng)站

它在打包的時(shí)候就生成了所有頁(yè)面對(duì)應(yīng)的 HTML文件以及數(shù)據(jù)文件等,這樣當(dāng)你訪問(wèn)某個(gè)頁(yè)面時(shí),服務(wù)端可以直接返回HTML ,另外一方面當(dāng)頁(yè)面中有使用 Link 時(shí),會(huì)提前加載這個(gè)頁(yè)面所對(duì)應(yīng)的數(shù)據(jù),這樣點(diǎn)擊跳轉(zhuǎn)后頁(yè)面加載速度就會(huì)很快。所以上文中所說(shuō)的優(yōu)化手段,其實(shí)是 Gatsby幫助實(shí)現(xiàn)的,有興趣的朋友可以去它的官網(wǎng)了解更多相關(guān)知識(shí)

至于這個(gè)監(jiān)聽Link元素是怎么實(shí)現(xiàn)的呢?
具體實(shí)現(xiàn)是使用 Intersection Observer ,相關(guān)介紹見(jiàn) IntersectionObserver API 使用教程 - 阮一峰的網(wǎng)絡(luò)日志 ,有寫到圖片懶加載和無(wú)限滾動(dòng)也可以使用這個(gè) API 去實(shí)現(xiàn),只不過(guò)現(xiàn)在有個(gè)別瀏覽器還沒(méi)有支持,所以在兼容性上存在一些阻攔,導(dǎo)致這個(gè) Api現(xiàn)在還沒(méi)有被普及

參考
本篇文章參考了以下幾篇文章并結(jié)合上了自己的理解,下面文章個(gè)人覺(jué)得質(zhì)量真的很高,大家也可以去看看。另外大家在文章中如果發(fā)現(xiàn)問(wèn)題可以在評(píng)論區(qū)中指出,大家共同進(jìn)步~

github.com/findxc/blog…

github.com/findxc/blog…

作者:Running53

鏈接:https://juejin.cn/post/7128369638794231839




作者:Running53


歡迎關(guān)注微信公眾號(hào) :深圳灣碼農(nóng)