React 的歷史與應用#
應用
- 前端應用開發,如 Facebook,Instagram,Netflix 網頁版。
- 移動原生應用開發,如 Instagram,Discord,Oculus。
- 結合 Electron,進行桌面應用開發。
歷史
- 2010 年 Facebook 在其 php 生態中,引入了 xhp 框架,首次引入了組合式組件的思想,啟發了後來的 React 的設計。
- 2011 年 Jordan Walke 創造了 FaxJS,也就是後來的 React 原型:
- 既可以在客戶端渲染也可以在服務端渲染
- 響應式,當狀態變更時,UI 會自動更新。
- 性能好,快速渲染
- 高度封裝組件,函數式聲明,
- 2013 年 React 正式開源,在 2013 JSConf 上 Jordan Walke 介紹了這款全新的框架:React
- 2014 年 - 今天 生態大爆發,各種圍繞 React 的新工具 / 新框架開始湧現
React 設計思路#
UI 編程痛點#
- 當狀態更新時,UI 不會自動更新,需要手動地調用 DOM進行更新。
- 欠缺基本的代碼層面的封裝和隔離,代碼層面沒有組件化。
- UI 之間的數據依賴關係,需要手動維護,如果依賴鏈路長.則會遇到 “Callback Hell”(回調地獄)。
響應式與轉換式#
轉換式系統:給定輸入求解輸出,如編譯器、數值計算
響應式系統:監聽事件,消息驅動,如監控系統、UI 界面
事件 -> 執行既定的回調 -> 狀態變更 -> UI 更新
響應式編程和組件化#
那麼我們就希望解決以上痛點:
- 狀態更新,UI 自動更新。
- 前端代碼組件化,可重用,可封裝。
- 狀態之間的互相依賴關係,只需聲明即可。
組件化
- 組件一個是 原子組件 / 或組件的組合
- 組件內部擁有狀態,外部不可見
- 父組件可將狀態傳入組件內部,來控制子組件的運轉。
狀態歸屬問題#
當前價格 屬於 Root 節點!因為要向下傳遞,這其實不合理,在下面的狀態管理庫裡會講到這個的解決方法。
狀態應該歸屬於兩個節點(或多個)向上尋找到的最近共同祖先。
思考:
- React 是單向數據流,還是雙向數據流?
答:單向的,永遠是只有父組件給子組件傳東西,但這並不代表子組件不能改變父組件的狀態。
- 如何解決狀態不合理上升的問題?
答:通過狀態管理庫,接下來也會講到。
- 組件的狀態改變後,如何更新 DOM?
答:講解 React 實現中會提到。
組件設計:
- 組件聲明了狀態和 UI 的映射
- 組件有 Props(外部)/State(內部)兩種屬性
- Props 接受父組件傳入的狀態
- State 是內部的屬性。
- 可被其他組件組成
ps:學過小程序的同學應該知道,小程序中的屬性的雙向綁定實際上應該也有用到了這個思想。
生命週期#
掛載 -> 狀態更新 -> 卸載
React(hooks)的寫法#
關於 React Hook 可以參看官方文檔,以下內容大部分摘自:Hook 簡介 – React (reactjs.org)
Hook 是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。
State Hook#
import React, {useState} from 'react';
function Example() {
// 聲明一個叫 “count” 的 state 變量。
const [count, setCount] = useState(0);
return (
<div>
<p>你點擊了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>
點擊我
</button>
</div>
);
}
上面這段函數用來顯示一個計數器,當點擊按鈕時,計數器的值就會自動增加
在這裡,
useState
就是一個 Hook 。通過在函數組件裡調用它來給組件添加一些內部 state。React 會在重複渲染時保留這個 state。useState
會返回一對值:當前狀態和其更新函數,你可以在事件處理函數中或其他一些地方調用這個函數。
useState
唯一的參數就是初始 state。在上面的例子中,我們的計數器是從零開始的,所以初始 state 就是0
。值得注意的是,不同於this.state
,這裡的 state 不一定要是一個對象 —— 如果你有需要,它也可以是。這個初始 state 參數只有在第一次渲染時會被用到。
Hook 是一些可以讓你在函數組件裡 “鉤入” React state 及生命週期等特性的函數。
可以在一個組件中多次使用 State Hook:
function ExampleWithManyStates() {
// 聲明多個 state 變量!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '學習 Hooks' }]);
// ...
}
Effect Hook#
函數副作用是指當調用函數時,除了返回值之外,還會對主調用函數產生其他附加的影響。例如修改全局變量(函數外的變量)或修改參數。純函數就是指沒有函數副作用的函數,這在 js 那一節裡都有講過。
例如,下面這個組件在 React 更新 DOM 後會設置一個頁面標題:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 相當於 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用瀏覽器的 API 更新頁面標題
document.title = `你點擊了 ${count} 次`;
});
return (
<div>
<p>你點擊了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>
點擊我
</button>
</div>
);
}
當你調用
useEffect
時,就是在告訴 React 在 完成對 DOM 的更改後運行你的 “副作用” 函數 。由於副作用函數是在組件內聲明的,所以它們可以訪問到組件的 props 和 state。
Hook 的使用法則#
Hook 就是 JavaScript 函數,但是使用它們會有兩個額外的規則:
- 只能在函數最外層調用 Hook。不要在循環、條件判斷或者子函數中調用。
- 只能在 React 的函數組件中調用 Hook。不要在其他 JavaScript 函數中調用。(還有一個地方可以調用 Hook —— 就是自定義的 Hook 中)
同時,我們提供了 linter 插件來自動執行這些規則。這些規則乍看起來會有一些限制和令人困惑,但是要讓 Hook 正常工作,它們至關重要。
以上是 React 官方文檔的說法
React 的實現#
三個問題:
- JSX 是不符合 JS 標準的語法
- 返回的 JSX 改變時,如何更新 DOM?
- State/Props 更新時, 要重新觸發 render 函數
Problem1#
解決辦法也很簡單,就是將 JSX 轉換為符合 JS 語法的
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
// 等價於
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
Problem2#
返回的 JSX 本身是類似 DOM 的一種東西,但不是 DOM,DOM 操作本身是十分耗費性能的。所以需要將返回的 JSX 與原來的 DOM 結構計算一個 diff(差別)?但是這個 diff 算法本身不能太耗時,盡可能小,盡可能短。
Virtual DOM (虛擬 DOM)#
Virtual DOM 是一種用於與真實 DOM 同步,而在 JS 內存中維護的一個對象,它具有和 DOM 類似的樹狀結構並可以和 DOM 建立一一對應的關係。
這種方式賦予了 React 聲明式的 API:您告訴 React 希望讓 UI 是什麼狀態,React 就確保 DOM 匹配該狀態。這使您可以從屬性操作、事件處理和手動 DOM 更新這些在構建應用程序時必要的操作中解放出來。
狀態更新 -> diff 比對 Virtual DOM 和真實 DOM - > Re-render Virtual DOM 改變我們的真實 DOM
How to Diff??#
更新次數少 <- 權衡 -> 計算速度快
完美的最小 Diff 算法,需要 O (n^3) 的複雜度
而犧牲理論最小 Diff,換取時間,得到了 O (n) 複雜度的算法,他是局部最優的。Heuristic O (n) Algorithm (啟發式算法)
-
不同類型的元素 -> 替換
-
同類型的 DOM 元素 -> 更新
-
同類型的組件元素 -> 遞歸
這個算法只遍歷了一遍,就可以算出 diff。
而這也是 React 一個弊病,一個組件發生改變時,其子組件全部會重新渲染。要解決這個問題,就要看接下來的 React 的狀態管理庫。
React 的狀態管理庫#
核心思想#
將狀態抽離到 UI 外部進行統一管理,但是這是會降低組件的復用性,所以這一般出現在業務代碼中。以下幾種框架,用哪個都行
- Redux 中文文檔
- XState - JavaScript State Machines and Statecharts
- MobX 介紹・MobX 中文文檔
- Recoil 中文文檔 | Recoil 中文網 (recoiljs.cn)
狀態機#
收到外部事件後根據當前狀態,遷移到下一個狀態。
哪些狀態適合放到狀態管理庫?
- 可能會被很多層級的組件用到的狀態
應用級框架科普#
React 本身是沒有提供足夠多的工程能力,如路由、頁面配置等等。
- Next.js - React 應用開發框架 硅谷明星創業公司 Vercel 的 React 開發框架,穩定,開發體驗好,支持 Unbundled Dev,sWC 等,其同樣有 Serverless 一鍵部署平台幫助開發者快速完成部署。口號是 "Let's Make Web Faster"
- Modern.js - 現代 Web 工程體系 (modernjs.dev) 字節跳動 Web Infra 團隊研發的全棧開發框架,內瓷了很多開箱即用的能力與最佳實踐,可以減少很多調研選擇工具的時間。
- Get Started with Blitz (blitzjs.com)無 API 思想的全棧開發框架,開發過程中無需寫 API 調用與 CRUD 邏輯,適合前後端緊密小團隊項目。
課後作業#
- React 組件的 render 函數,在哪些時機下,會被重新執行?
- React 這種函數式編程,和 vue 這種基於模板語法的前端框架,各有什麼優點和缺點?
- React 推薦使用組合來進行組件的復用,而不是繼承,背後有什麼樣的考慮?
個人理解
總結感想#
本節課大致介紹了 React 及其原理,介紹了組件化、一些應用級框架
本文引用的內容大部分來自牛岱老師的課,以及 React 官方文檔