React 是一個開源 JavaScript 庫,開發人員使用它來建立基於 Web 和移動的應用程式,並且支援構建互動式使用者介面和 UI 元件。React 是由 Facebook 軟體工程師 Jordan Walke 建立,React 的第一個版本在七年前問世,現在,Facebook 負責維護。React框架自首次釋出以來,React 的受歡迎程度直線飆升,熱度不減。
2020 年 10 月,React 17 釋出了,但令人驚訝的是——“零新功能”。當然,這並不是真的表示沒有任何新新增的功能,讓廣大程式設計師使用者興奮。事實上,這個版本為我們帶來了很多重大功能的升級及16版本的bug修復,並推出了:Concurrent Mode 和Suspense。
雖然這兩個功能尚未正式釋出,這些功能已提供給開發人員進行測試。一旦釋出,它們將改變 React 呈現其 UI 的方式,從而達到雙倍提高效能和使用者體驗。
簡要說明, Concurrent Mode 和Suspense 可以使使用者無縫處理資料載入,載入狀態,使用者介面操作更加平滑和無縫切換。 在Concurrent Mode 下,React可以暫停高消耗的,非緊急的元件的渲染,並聚焦在更加緊迫的任務處理,如UI 渲染,始終保持應用為可響應式,避免白屏,卡頓等現象。
本文主要分享深入瞭解Concurrent Mode 和Suspense 模式下的資料提取功能。
為什麼需要 Concurrent Mode?
眾所周知,JavaScript 框架或庫是單執行緒的工作。因此,當一個程式碼塊執行時,其餘的塊必須等待執行。無法併發執行多執行緒工作。介面渲染也是一樣的。
一旦 React 開始渲染某些東西,無法中斷直到執行完成。React 開發人員將這種渲染稱為“阻塞渲染”。 這種阻塞渲染會建立一個不穩定的使用者介面,並且隨時可能停止響應。
具體問題
假如,我們需要顯示一個很長的可選列表用於過濾產品的應用程式。我們使用搜尋框用於過濾記錄,設計方案是當使用者點選搜尋按鈕後,使用者介面需要重新重新整理列出相關聯的資料。
如果列表過長,資料過多,UI“卡頓”,即渲染對使用者可見。這種卡頓也會大大降低產品效能。開發人員可以使用一些技術,如節流和防抖,這些技術會有一定幫助,但不是完美的解決方案。
節流限制特定函式被呼叫的次數。使用節流,我們可以避免重複呼叫昂貴和耗時的API或函式。這個過程能夠提高效能,尤其是在使用者介面上呈現資訊。
防抖會在預定的時間內忽略對函式的呼叫。函式呼叫僅在經過預定時間後進行。
下圖描述了卡頓現象:
在等待非緊急 API 呼叫完成時,UI 卡頓,從而阻止呈現使用者介面。解決方案是使用併發模式進行可中斷渲染。
無中斷渲染
通過可中斷渲染,React.js 在處理和重新渲染列表時不會阻塞 UI。它通過暫停瑣碎的工作、更新 DOM 並確保 UI 不會卡頓,使 React.js 更加細化。React 使用使用者輸入並行更新或重繪輸入框。React 使用使用者輸入並重繪輸入框並行執行。它還更新記憶體中的列表。React 完成更新後,它會更新 DOM 並在使用者的顯示器上重新呈現列表。本質上,無中斷渲染使 React 能夠“多工”。此功能提供了更流暢的 UI 體驗。
併發模式
併發模式是一組功能,可幫助 React 應用程式保持響應並平滑地適應使用者的裝置和網路速度能力。併發模式將其擁有的任務劃分為更小的塊。 React 的排程程式可以挑選並選擇要執行的作業。作業的排程取決於它們的優先順序。通過對任務進行優先順序排序,它可以停止瑣碎或不緊急的事情,或者進一步推動它們。 React 始終將使用者介面更新和渲染放在首位。
使用併發模式,我們可以:
- 控制首次渲染過程
- 優先處理渲染過程
- 暫停和恢復元件的渲染
- 快取和優化元件的執行時渲染
- 隱藏顯示內容直到需要展示時
隨著 UI 渲染,併發模式改進了對傳入資料的響應,懶載入控制元件,非同步處理過程。併發模式保證了使用者介面始終處於啟用狀態,並且持續在後臺更新資料,併發模式也始終使用React 的兩個鉤掛:useTransition
和useDeferredValue
使用useDeferredValue Hook
useDeferredValue Hook
的定義如下:
const deferredValue = useDeferredValue(value, { timeoutMs: <some value> });
此命令設定值在timeoutMs
中設定的時間後“滯後”。 使用者介面是必須立即更新還是必須等待資料,該命令使使用者介面保持啟用狀態和響應性,該Hook避免了 UI 卡頓,並始終保持使用者介面響應,以保持獲取資料滯後的較小成本。
使用 Transition Hook
useTransition Hook
是React
中主要用於掛起的Hook
,假設這樣的場景下:其中有一個帶有使用者名稱按鈕的網頁。只需點選一個按鈕,網頁就會在螢幕上顯示使用者的詳細資訊。
假設使用者首先單擊一個按鈕,然後單擊下一個。螢幕要麼變成空白,要麼我們在螢幕上看到一個微調器。如果獲取詳細資訊花費的時間太長,使用者介面可能會凍結。
useTransition
方法返回兩個Hook
的值:startTransition
和 isPending
。定義的語法如下:
const [startTransition, isPending] = useTransition({ timeoutMs: 3000 });
startTransition
定義的語法:
<button disabled={isPending}
startTransition(() => {
<fetch Calls>
});
</button>
{isPending? " Loading...": null}
使用 useTransition
鉤子,React.js
繼續顯示沒有使用者詳細資訊的使用者介面,直到使用者詳細資訊準備好,但 UI 是響應式的。React
優先考慮使用者介面,以在並行獲取資料時保持響應。
為獲取資料的Suspense
Suspense
是React
與併發模式一起引入的另一個實驗性功能。Suspense
使元件能夠在渲染前等待一段預定的時間。
Suspense
的主要作用是從元件非同步讀取資料,而無需擔心資料的來源。Suspense
最適合延遲載入的概念。Suspense
允許資料獲取庫通知React
資料元件是否可以使用。在必要的元件準備就緒之前,React
不會更新 UI。
使用Suspense
的好處:
1.資料獲取庫和React
元件之間的整合
2.控制視覺載入狀態
3.避免競爭條件
Spinner
元件的基本語法如下:
import Spinner from './Spinner';
<Suspense fallback={<Spinner />}>
<SomeComponent />
</Suspense>
Concurrent Mode
中使用的Suspense
允許耗時的元件在等待資料的同時開始渲染。同時顯示佔位符。這種組合產生了更流暢的UI體驗。
Suspense 和 懶載入元件
React.lazy
是一個新功能,它使React.js
能夠延遲載入元件。懶載入意味著僅在需要時才載入元件(檢索和呈現它們的程式碼)。他們會優先考慮最關鍵的使用者介面元件。React
開發人員建議將懶載入元件包裝在Suspense
元件中。
這樣做可確保元件在渲染時不會出現“不良狀態”。使用者介面在整個過程中保持響應,並帶來更流暢的使用者體驗。
啟用併發模式
要啟用併發模式,請安裝最新的測試版本。安裝 React 的先決條件是節點資料包管理器 (npm)。要安裝測試版本,請執行以下命令:
npm install react@experimental react-dom@experimental
要測試是否設定了測試版本,請建立一個示例 React 應用程式。沒有測試功能的渲染程式碼如下:
import * as React from 'react';
import { render } from 'react-dom';
render(<App />, document.getElementById('root'));
併發模式的,具體程式碼如下:
import * as React from 'react';
import { createRoot } from 'react-dom';
createRoot(document.getElementById('root')).render(<App />);
這將為整個應用程式啟用併發模式。React 將渲染呼叫分為兩部分:
- 建立根元素
- 使用渲染呼叫
目前,React 計劃維護三種模式:
- 傳統模式是向後相容的傳統或當前模式
- 阻塞模式是併發模式開發的中間階段
- 併發模式
阻塞模式是使用createBlockingRoot 呼叫來替換createRoot 呼叫,在併發模式的開發情況下,阻塞模式為開發者提供了機會來修復bug或解決問題。
React 官方文件中也說明了每種模式支援的功能:
示例應用:
本文也建立了一個測試程式來驗證併發模式和其他模式的用法和效果。本文以畫素應用為例在150*150的畫布上隨機分佈畫素幷包含一個搜尋框,每次使用者點選搜尋框時候,畫布會重新渲染自己。
即使UI 介面無法在併發模式下渲染,使用者輸入也不會停止更新。畫素畫布在處理完成後重新渲染。在傳統模式下,快速鍵入時,UI 會停止,有時會在再次渲染畫布之前停止。使用者輸入也會停止並且不會更新。
構建畫素應用程式的主要檔案是 canvas.js。我們還製作了一個輸入框,使用者可以在其中輸入任何內容。每次按下一個鍵都會重新渲染畫素畫布。
程式碼示例:
- Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// Traditional or non-Concurrent Mode react
const rootTraditional = document.getElementById("root");
ReactDOM.render(<App caption="Traditional or Block Rendering" />,
rootTraditional);
// Concurrent Mode enabled
const rootConcurrent = document.getElementById("root-concurrent");
ReactDOM.createRoot(rootConcurrent).render(<App caption="Interruptible
Rendering" />);
- App.js
import React, { useState, useDeferredValue } from "react";
import "./App.css";
import { Canvas } from "./Canvas";
export default function App(props)
{ const [value, setValue] = useState("");
//This is available only in the Concurrent mode.
const deferredValue = useDeferredValue(value, {
timeoutMs: 5000
});
const keyPressHandler = e => {
setValue(e.target.value);
};
return (
<div className="App">
<h1>{props.caption}</h1>
<input onKeyUp={keyPressHandler} />
<Canvas value={deferredValue} />
</div>
);
}
- Canvas.js
import React from "react";
const CANVAS_SIZE = 70;
const generateRandomColor = () => {
var letters = "0123456789ABCDEF";
var color = "#";
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
const createCanvas = (rows, columns) => {
let array = [];
for (let i = 0; i < rows; i++) {
let row = [];
for (let j = 0; j < columns; j++) {
row.push(0);
}
array.push(row);
}
return array;
};
//This is the square with the pixels
const drawCanvas = value => {
const canvas = createCanvas(CANVAS_SIZE, CANVAS_SIZE);
return canvas.map((row, rowIndex) => {
let cellsArrJSX = row.map((cell, cellIndex) => {
let key = rowIndex + "-" + cellIndex;
return (
<div
style={{ backgroundColor: generateRandomColor() }}
className="cell"
key={"cell-" + key}
/>
);
});
return (
<div key={"row-" + rowIndex} className="canvas-row">
{cellsArrJSX}
</div>
);
});
};
export const Canvas = ({ value }) => {
return (
<div>
<h2 style={{ minHeight: 30 }}>{value}</h2>
<div className="canvas">{drawCanvas(value)}</div>
</div>
);
};
- Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<title>React App Concurrent Mode</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="container">
<div id="root" class="column"></div>
<div id="root-concurrent" class="column"></div>
</div>
</body>
</html>
執行示例
讓我們看看我們的程式碼。我們看到的第一個螢幕是初始螢幕。使用傳統或塊渲染是現在React 的做法。可中斷渲染是併發模式的測試功能。我們先看看傳統的渲染工作。
畫素畫布在每次擊鍵時重新渲染。在傳統渲染中,整個 UI 會在每次擊鍵時暫停,直到它可以重新渲染螢幕。在此期間,即使我們繼續打字,使用者輸入不會更新。
下圖顯示可中斷渲染。在可中斷渲染中,使用者可以繼續輸入。在為每次擊鍵並行重新渲染畫布時,UI 不會停止或停止。
重新渲染完成後,React 會更新 UI。雖然在靜態截圖中很難看到,但我們可以看到網格在變化,但使用者仍然可以打字而不會出現 UI 卡頓的情況。
總結
在本文中,我們研究了 React 的測試併發功能和 Suspense。使用併發模式,React.js 始終保持使用者介面響應。它將應用程式的任務分解為更小的塊,並允許對使用者介面任務進行優先順序排序。因此,此模式可提供更流暢和無縫的使用者體驗,並提高應用程式的整體效能。
結合併發模式,Suspense 允許使用者介面保持響應。同時,資料獲取等繁重耗時的任務可以並行完成,從而提供整體無縫體驗。
有關併發模式的完整詳細資訊可在 React 官方文件中瞭解。
隨著React版本的改進, React框架越來越被更多的中國前端開發者所熟知並且廣泛應用到他們的專案開發中。是繼續Vue.js 後又一備受歡迎的前端主流框架,現在也因此衍生除了很多支援與React框架整合的功能工具, 如前端報表ActiveReportsJS控制元件,提供了與 React 直接整合的線上編輯器和報表展示工具,完善前端的資料展示功能。