React入門學習

  • 2019 年 10 月 15 日
  • 筆記

為了獲得更好的閱讀體驗,請訪問原地址:傳送門

一、React 簡介


React 是什麼

React 是一個起源於 Facebook 的內部項目,因為當時 Facebook 對於市場上所有的 JavaScript MVC 框架都不太滿意,所以索性就自己寫了一套,用來架設 Instagram。做出來之後,發現這套東西還蠻好用的,於是就在 2013 年 5 月開源了

在這裡我們需要稍微注意一下 庫(Library)框架(Framework) 的區別,React 本身是一個用於構建用戶界面的 JavaScript 庫,而我們平時所說的 React 框架其實是指的是 React/ React-router 和 React-redux 的結合體,庫和框架的本質區別體現在於控制權:

  • 「庫」是一個封裝好的特定的集合,提供給開發者使用,而且是特定於某一方面的集合(方法和函數),庫沒有控制權,控制權完全在於使用者本身;
  • 「框架」顧名思義是一套架構,會基於自身的特點向用戶提供一套比較完整的解決方案,如果使用者選定了一套框架,那麼就需要根據框架本身做出一定的適應。

為什麼使用 React?

這是一個非常有趣的問題,也讓我困惑和苦惱。在筆者還在學校的時候嘗試用 Vue 搭建了一套簡單的博客系統,學習曲線平滑,讓只會一些基礎 HTML/ CSS 代碼的我通過一段時間學習就能夠上手了,但是學習 React 以來,進展變得相對緩慢.. 一部分原因是因為 React 創新性的開發模式以及讓我感到無所適從的 JSX 語法(菜才是原罪)。

Vue 作者尤雨溪在知乎上回答「Vue 和 React 的優點分別是什麼?」這個問題的時候提到 :

這裡我可以大方地承認,如果多年以後要論歷史地位,React 肯定是高於 Vue 的。事實上,我作為一個開發者,也是由衷地佩服 Jordan Walke, Sebastian Markbage 這樣的,能從開發模式層面上提出突破性的新方向的人。

React 從一開始的定位就是提出 UI 開發的新思路。當年 Pete Hunt 最開始推廣 React 的時候的一句口號就叫 "Rethinking Best Practices",這樣的定位使得 React 打開了一些全新的思路,吸引了一群喜歡折騰的早期核心用戶,並在這個基礎上通過社區迭代孵化出了許多今天被 React 開發者當作常識的 pattern。這是 React 偉大的地方,Vue 裏面也有很多地方是直接受到了 React 的啟發。React 敢做這樣的嘗試,是因為它是 Facebook。這樣的體量的公司,在 infrastructure 層面獲得質的提升,收益是巨大的,而且 Facebook 的工程師們足夠聰明又要靠工資吃飯,改變他/她們的習慣並不是什麼問題。而對外推廣,則是一種大公司才有的 「改變業界」 的底氣。

相比「為什麼使用 React?」的理由,稱讚 React 的倒是明顯更多一些(React 確實是突破性的開發模式)。

是因為 React 組件化的思想嗎?不是。我覺得這跟多少跟微服務化之類的概念有點兒類似,這是屬於一個時代對於計算機工程的思想進步,是對於團隊協作提出的新一種成熟的解決方案,也是必然的一種趨勢。當前流行的不管是 Angular/ Vue 還是 React,都天然的支持着組件化的概念。

那是因為 React 性能出眾嗎?我想也不是。或許 React 剛出世時因為其獨特高效的虛擬 DOM 設計,能夠在前端江湖中平步青雲,但是現在前端技術都主鍵地趨於成熟(我也不懂,我亂說的..),從很多地方的對比數據中,都能夠看得到其實 React 與其他框架的性能差異並不是特別大。並且體現在平時的開發中,這樣對比不明顯的速度差異,根本沒有多大的用處。

還看到一種觀點,說 React 適用於構建大型的項目。從我並不多的了解中,我知道 React 體系中天然有着許多的約束,以及一些不成文的約定,這就好像是 SpringBoot 中默認提供給使用者的一些姿勢,天然就有很強的工程性,加上一些約定俗成的代碼風格 or 歸約,這就使得 Java 很適合一些大型的團隊項目。但能不能開發大型的項目從來都是取決於人,而不是採用了哪種框架。

所以比較令我信服的理由是(我亂猜的):像 Java 一樣,React 體系足夠成熟,社區也非常活躍,你遇到的問題很容易在網絡上找到答案,並且也有一些成熟的實踐 or 輪子用以解決各種各樣的問題。而且 React 還有一個比較特別的特性是:你能夠比較無痛地使用 React Native 開發原生移動應用。

二、React 核心概念


虛擬 DOM(Vitural Document Object Model)

要理解這個「虛擬 DOM」的概念,首先我們就需要知道什麼是「DOM」。我們先暫時忘掉什麼網頁之類的,我們想像現在我們需要編寫程序來對下列的 Markdown 文檔進行改變應該怎麼做:

# Title  ## subtitle - 1  content - 1  ## subtitle - 2  content - 2

比如我現在就想要 content - 2 的內容進行改變,那麼我就需要一行一行的不斷遍歷直到最後遍歷到它才能進行操作,對內容改變的操作都差不多,所以如果我想對這個查找的操作進行優化,最簡單的想法就是把它樹化以減少高度,增加效率。

DOM 的概念

DOM 是英文 Document Object Model 的縮寫,即文檔對象模型。它是一種跨平台的、獨立於編程語言的 API,它把 HTML、XHTML 或 XML 文檔都當做一個樹結構,而每個節點視為一個對象,這些對象可以被編程語言操作,進而改變文檔的結構,映射到文檔的顯示。DOM 最開始的時候是和 JavaScript 交織在一起的,只是後來它們最終演變成了兩個獨立的實體。DOM 被設計成與特定編程語言相獨立,儘管絕大部分時候我們都是使用 JavaScript 來操作,但其實其他的語言一樣可以(如 Python)。

假如有這麼一段 HTML 代碼:

<html>    <head>      <title>文檔標題</title>    </head>      <body>      <a href="">鏈接</a>      <h1>標題</h1>    </body>  </html>

那麼它最終就應該會是下面這棵樹一樣的結構:

這裡不對 DOM 節點的類型啊方法之類的進行討論,我們只需要對 DOM 有一個大致的概念就好了。

瀏覽器渲染 DOM 的流程

我們可以簡單了解一下瀏覽器渲染 DOM 的流程:

  1. 解析 HTML 建立 DOM 樹;
  2. 解析 CSS,並結合 DOM 樹形成 Reander 樹;
  3. 布局 Render 樹(Layout/ reflow),確定各節點的尺寸、位置等信息;
  4. 繪製 Render 樹(Paint),繪製頁面像素信息;
  5. 瀏覽器將各層信息發給 GPU,GPU 會將各層合成(Composite),顯示在屏幕上;

操作 DOM 為什麼慢

其實嚴格來說,單純的操作 DOM 並不慢,說它慢是帶有一定條件的。

想像在一次事件循環中多次操作 DOM 時,有時希望 JS 代碼中能立刻獲取最新的 DOM 節點信息,這時瀏覽器不得不掛起 JS 引擎,轉而調用 DOM 引擎,計算渲染出最新的 DOM,以此來獲取最新的 DOM 節點信息,接着再重新激活 JS 引擎 繼續後續的操作。

可以預見,上述操作不僅需要多次進行引擎的切換,還需要多次計算布局,重新繪製 DOM。事實上paint是一個耗時的過程,然而layout是一個更耗時的過程,我們無法確定layout一定是自上而下或是自下而上進行的,甚至一次layout會牽涉到整個文檔布局的重新計算。

但是layout是肯定無法避免的,所以我們主要是要最小化layout的次數。

所以,降低引擎切換頻率、減小 DOM 變更規模才是 DOM 性能優化方案的關鍵!

Virtual DOM 算法步驟

虛擬 DOM 正是解決了上述問題,它的本質就是用 JS 對象來模擬出我們真實的 DOM 樹,它的算法大致如下:

  1. 用 JavaScript 對象映射形成 DOM 樹的結構,然後用這個樹構建一個真正的 DOM 樹,插到文檔當中;
  2. 當狀態變更的時候,重新構造一棵新的對象樹,然後用新的樹和舊的樹進行比較(Diff 算法),記錄兩棵樹差異;
  3. 把第二步中所記錄的差異應用到步驟一所構建的真正的 DOM 樹上,視圖就更新。

虛擬 DOM 和真實 DOM 的區別

我們由此可以對比出兩者的不同:

  1. 改變多個狀態,影響多個節點布局時,只是頻繁的修改了內存中的 JS 對象,然後一次性比較並修改真實 DOM 中需要改的部分,最後在真實 DOM 中進行排版與重繪,減少過多 DOM 節點排版與重繪損耗;
  2. 真實 DOM 頻繁排版與重繪的效率是相當低的;
  3. 虛擬 DOM 有效降低大面積(真實 DOM 節點)的重繪與排版,因為最終與真實 DOM 比較差異,可以只渲染局部(同2);

使用虛擬DOM的損耗計算:

總損耗 = 虛擬DOM增刪改 + (與Diff算法效率有關)真實DOM差異增刪改 + (較少的節點)排版與重繪

直接使用真實DOM的損耗計算:

總損耗 = 真實DOM完全增刪改 + (可能較多的節點)排版與重繪

Diff 算法

虛擬 DOM 的核心在於 Diff,它自動幫你計算那些應該調整的,然後只修改應該被調整的區域,省下的不是運行速度這種 "小速度",而是開發速度/ 維護速度/ 邏輯簡練程度等 "總體速度"。

但虛擬 DOM 快也是在相對條件下的,這裡引用 @尤雨溪大大在知乎問題《網上都說操作真實 DOM 慢,但測試結果卻比 React 更快,為什麼?》上回答的一句話吧:

不要天真地以為 Virtual DOM 就是快,diff 不是免費的,batching 么 MVVM 也能做,而且最終 patch 的時候還不是要用原生 API。在我看來 Virtual DOM 真正的價值從來都不是性能,而是它 1) 為函數式的 UI 編程方式打開了大門;2) 可以渲染到 DOM 以外的 backend,比如 ReactNative。

Diff 大致可以分為三種類型:

  • Tree Diff: 新舊兩棵 DOM 樹,逐層對比的過程,就是 Tree Diff,當整顆DOM逐層對比完畢,則所有需要被按需更新的元素,必然能夠找到;
  • Component Diff: 在進行 Tree Diff 的時候,每一層中,組件級別的對比,叫做 Component Diff:
    • 如果對比前後,組件的類型相同,則暫時認為此組件不需要被更新;
    • 如果對比前後,組件類型不同,則需要移除舊組件,創建新組件,並追加到頁面上;
  • Element Diff: 在進行組件對比的時候,如果兩個組件類型相同,則需要進行元素級別的對比,這叫做 Element Diff;

三、Hello World


  • 引用自:http://www.ruanyifeng.com/blog/2015/03/react.html – 阮一峰 – React 入門實例教程

使用 React 的網頁源碼,結構大致如下(可以直接運行):

<!DOCTYPE html>  <html>  <head>      <meta charset="UTF-8"/>      <title>Hello React!</title>      <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>      <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>      <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>  </head>  <body>    <div id="example"></div>  <script type="text/babel">      ReactDOM.render(          <h1>Hello, world!</h1>,          document.getElementById('example')      );  </script>    </body>  </html>

上面代碼有兩個地方需要注意。首先,最後一個 <script> 標籤的 type 屬性為 text/babel 。這是因為 React 獨有的 JSX 語法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel"

其次,上面代碼一共用了三個庫: react.jsreact-dom.jsBrowser.js ,它們必須首先加載。其中,react.js是 React 的核心庫,react-dom.js 是提供與 DOM 相關的功能,Browser.js 的作用是將 JSX 語法轉為 JavaScript 語法,這一步很消耗時間,實際上線的時候,應該將它放到服務器完成。

$ babel src --out-dir build

上面命令可以將 src 子目錄的 js 文件進行語法轉換,轉碼後的文件全部放在 build 子目錄。

ReactDOM.render()

ReactDOM.render 是 React 的最基本方法,用於將模板轉為 HTML 語言,並插入指定的 DOM 節點。

ReactDOM.render(    <h1>Hello, world!</h1>,    document.getElementById('example')  );

上面代碼將一個 h1 標題,插入 example 節點,運行結果如下:

JSX 語法

  • 引用自:https://www.runoob.com/react/react-jsx.html – RUNOOB.COM – React JSX

上一節的代碼, HTML 語言直接寫在 JavaScript 語言之中,不加任何引號,這就是 JSX 的語法,它允許 HTML 與 JavaScript 的混寫。我們先來看以下一段代碼:

const element = <h1>Hello, world!</h1>;

與瀏覽器的 DOM 元素不同,React 當中的元素事實上是普通的對象,React DOM 可以確保 瀏覽器 DOM 的數據內容與 React 元素保持一致。要將 React 元素渲染到根 DOM 節點中,我們通過把它們都傳遞給 ReactDOM.render() 的方法來將其渲染到頁面上:

var myDivElement = <div className="foo" />;  ReactDOM.render(myDivElement, document.getElementById('example'));

JSX 看起來類似 HTML ,你也可以在上面代碼中嵌套多個 HTML 標籤,但是需要使用一個 div 元素包裹它。

JavaScript 表達式

我們可以在 JSX 中使用 JavaScript 表達式。表達式寫在花括號 {} 中。實例如下:

ReactDOM.render(      <div>        <h1>{1+1}</h1>      </div>      ,      document.getElementById('example')  );

在 JSX 中不能使用 if else 語句,但可以使用 conditional (三元運算) 表達式來替代。以下實例中如果變量 i 等於 1 瀏覽器將輸出 true, 如果修改 i 的值,則會輸出 false.

ReactDOM.render(      <div>        <h1>{i == 1 ? 'True!' : 'False'}</h1>      </div>      ,      document.getElementById('example')  );

樣式

React 推薦使用內聯樣式。我們可以使用 camelCase 語法來設置內聯樣式. React 會在指定元素數字後自動添加 px 。以下實例演示了為 h1 元素添加 myStyle 內聯樣式:

var myStyle = {      fontSize: 100,      color: '#FF0000'  };  ReactDOM.render(      <h1 style = {myStyle}>菜鳥教程</h1>,      document.getElementById('example')  );

注釋

注釋需要寫在花括號中,實例如下:

ReactDOM.render(      <div>      <h1>菜鳥教程</h1>      {/*注釋...*/}       </div>,      document.getElementById('example')  );

數組

JSX 允許在模板中插入數組,數組會自動展開所有成員:

var arr = [    <h1>菜鳥教程</h1>,    <h2>學的不僅是技術,更是夢想!</h2>,  ];  ReactDOM.render(    <div>{arr}</div>,    document.getElementById('example')  );

參考資料


  1. http://www.ruanyifeng.com/blog/2015/03/react.html – React 入門實例教程 – 阮一峰
  2. https://www.jianshu.com/p/60100985dd7f – 前端框架與庫的區別
  3. https://www.zhihu.com/question/301860721/answer/545031906 – Vue 和 React 的優點分別是什麼?
  4. https://zhuanlan.zhihu.com/p/22184194 – 你真的理解 DOM 了嗎?
  5. https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model/Introduction – DOM 概述
  6. https://blog.huteming.site/posts/e0c41c5f/ – 為什麼說虛擬DOM更快

按照慣例黏一個尾巴:

歡迎轉載,轉載請註明出處!
獨立域名博客:wmyskxz.com
簡書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關注公眾微信號:wmyskxz
分享自己的學習 & 學習資料 & 生活
想要交流的朋友也可以加qq群:3382693