banner
cos

cos

愿热情永存,愿热爱不灭,愿生活无憾
github
tg_channel
bilibili

青訓營 |「響應式系統與 React」

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 編程痛點#

  1. 當狀態更新時,UI 不會自動更新,需要手動地調用 DOM進行更新。
  2. 欠缺基本的代碼層面的封裝隔離,代碼層面沒有組件化
  3. UI 之間的數據依賴關係,需要手動維護,如果依賴鏈路長.則會遇到 “Callback Hell”(回調地獄)。

響應式與轉換式#

轉換式系統:給定輸入求解輸出,如編譯器、數值計算

響應式系統:監聽事件,消息驅動,如監控系統、UI 界面

事件 -> 執行既定的回調 -> 狀態變更 -> UI 更新

響應式編程和組件化#

那麼我們就希望解決以上痛點:

  • 狀態更新,UI 自動更新。
  • 前端代碼組件化,可重用,可封裝。
  • 狀態之間的互相依賴關係,只需聲明即可。

image.png

組件化

  1. 組件一個是 原子組件 / 或組件的組合
  2. 組件內部擁有狀態,外部不可見
  3. 父組件可將狀態傳入組件內部,來控制子組件的運轉。

狀態歸屬問題#

當前價格 屬於 Root 節點!因為要向下傳遞,這其實不合理,在下面的狀態管理庫裡會講到這個的解決方法。

狀態應該歸屬於兩個節點(或多個)向上尋找到的最近共同祖先

思考:

  1. React 是單向數據流,還是雙向數據流?

答:單向的,永遠是只有父組件給子組件傳東西,但這並不代表子組件不能改變父組件的狀態。

  1. 如何解決狀態不合理上升的問題?

答:通過狀態管理庫,接下來也會講到。

  1. 組件的狀態改變後,如何更新 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 的實現#

三個問題:

  1. JSX 是不符合 JS 標準的語法
  2. 返回的 JSX 改變時,如何更新 DOM?
  3. 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 外部進行統一管理,但是這是會降低組件的復用性,所以這一般出現在業務代碼中。以下幾種框架,用哪個都行

狀態機#

收到外部事件後根據當前狀態,遷移到下一個狀態。

哪些狀態適合放到狀態管理庫?

  • 可能會被很多層級的組件用到的狀態

應用級框架科普#

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 邏輯,適合前後端緊密小團隊項目。

課後作業#

  1. React 組件的 render 函數,在哪些時機下,會被重新執行?
  2. React 這種函數式編程,和 vue 這種基於模板語法的前端框架,各有什麼優點和缺點?
  3. React 推薦使用組合來進行組件的復用,而不是繼承,背後有什麼樣的考慮?

個人理解

總結感想#

本節課大致介紹了 React 及其原理,介紹了組件化、一些應用級框架

本文引用的內容大部分來自牛岱老師的課,以及 React 官方文檔

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。