close

作者 | Arek Nawo
翻譯 | 王強
策劃 | 閆園園
近日,React 18 已經正式發布了,帶來了許多令人興奮的新特性。在這個版本中,React 通過其改進的渲染系統帶來了並發能力,並在此基礎上構建了轉換或自動批處理等性能增強特性。本文將介紹這些特性的機制,以及它們對 React 開發人員有哪些幫助。
正式發布之路

在深入了解所有特性之前,我們先來回顧一下 React 18 發布背後的整個過程,因為與之前的版本相比這一次的發布經歷非常獨特。React 17 並沒有帶來很多新特性。然而它改進了很多基礎組件,支持新 React 特性的無縫漸進採用,從而為未來的更新奠定了基礎。這些更改的效果現在就體現在了 React 18 中。

React 發布過程中最顯著的變化是與官方 alpha 版本一起,宣布新成立的 React 工作組(WG)。該小組的目標是收集來自社區的反饋,並幫助生態系統為即將到來的變化做好準備。此外,它是關於 React 內部工作的重要知識來源。

感謝 React 17 的改進和工作組的投入,React 18 最終成為了一個具有豐富特性的版本,卻只有少量、重大更改。

重大更改

由於新的並發特性是漸進適配並按需啟用的,React 18 中的重大更改僅限於幾個簡單的 API 更改,以及對 React 中多個行為的穩定性和一致性的一些改進。

客戶端渲染 API

最引人注目的更改之一是新的,帶有 createRoot() 的 root API。它旨在替換現有的 render() 函數,提供更好的人體工程學並啟用新的並發渲染特性。

import { createRoot } from "react-dom/client";import App from "App";const container = document.getElementById("app");const root = createRoot(container);root.render(<App/>);

請注意,這個新的 API 現在已從 react-dom/client 模塊導出。

卸載和水合 API 也發生了變化。

// Unmount component at DOM node:// ...root.unmount();// Hydrationimport { hydrateRoot } from "react-dom/client";// ...const container = document.getElementById("app");constroot=hydrateRoot(container,<Apptab="home"/>);

由於使用Suspense時會出現不正確timing的問題,渲染回調已經一去不復返了。替代選擇(雖然不是一對一的替換)是頂部組件內部的一個效果:

import { createRoot } from "react-dom/client";import { useEffect } from "react";import App from "App";const App = () => { useEffect(() => { console.log("render callback"); }); return <div></div>;};const container = document.getElementById("app");const root = createRoot(container);root.render(<App/>);
自動批處理

createRoot() API 還是 React 18 中另一個改進的入口——自動批處理。在 React 的早期版本中,狀態更新在 React 事件偵聽器中完成時已經批量處理了,以優化性能並避免重渲染。從 React 18 開始,狀態更新也將被安排到其他地方——比如在 Promise、setTimeout 回調和原生事件處理程序中。

const App = () => { const handleClick = () => { setA((a) => a + 1); setB((b) => b - 1); // Updates batched - single re-render }; setTimeout(() => { setA((a) => a + 1); setB((b) => b - 1); // New (v18): Updates batched - single re-render }, 1000); // ...};

這個更改雖然一般來說符合人們期望,也挺有用,但可能是破壞性的。如果你的代碼依賴於在分開的狀態更新之間重渲染的組件,那麼你必須使其適應新的批處理機制,或使用 flushSync() 函數來強制立即刷新更改。

import { flushSync } from "react-dom";// ...const handleClick = () => { flushSync(() => { setA((a) => a + 1); }); // Re-render flushSync(() => { setB((b) => b - 1); }); // Re-render};
嚴格模式更新

React 18 帶來了大把新特性,此外還有很多新特性正在路上。為了讓你的代碼為此做好準備,StrictMode 變得更加嚴格了。最重要的是,StrictMode 將測試組件對可重用狀態的彈性,模擬一系列的掛載和卸載行為。它旨在讓你的代碼為即將推出的特性(可能以組件的形式)做好準備,這將在組件的掛載周期中保留這個狀態。

雖然它肯定會在未來提供更好的性能,但就目前而言,啟用 StrictMode 時必須要考慮這個事情。

其他更改

除了以上提到的更改之外,根據你的 React 代碼庫,你可能還會發現其他一些更改。

值得一提的是,React 18 將不再支持 Internet Explorer,因為 React 18 現在依賴很多現代瀏覽器特性,如 Promise 或 Object.assign。鑑於微軟將在今年 6 月 15 日停止對該瀏覽器的支持,React 和其他 JS 庫也將停止對它的支持是很自然的。那些仍然需要支持 IE 的人們將不得不繼續使用 React 17。

其餘的更改與一些 React 行為的穩定性和一致性有關,不太可能影響你的代碼庫。不管怎樣,你可以在此處找到完整更改列表。

並發的 React

並發渲染器是 React 渲染系統的一項幕後特性。它允許並發渲染,即同時在後台準備多個版本的 UI。這意味着更好的性能和更平滑的狀態轉換。

雖然並發似乎只是一個實現細節,但其實它是大多數新特性的動力源泉。事實上,只有當你使用其中一種特性(如 transition、Suspense 或流式 SSR)時,才會啟用並發渲染。這就是為什麼了解並發渲染的工作機制是非常重要的。

Transition

Transition 是由並發渲染提供支持的新特性之一。它旨在與現有狀態管理 API 一起使用,以區分緊急和非緊急狀態更新。通過這種方式,React 知道哪些更新需要優先考慮,哪些更新需要在後台通過並發渲染準備。

要知道何時使用 transition,你必須更好地了解用戶是如何與你的應交互的。例如,在字段中鍵入或單擊按鈕是用戶期望立即獲得響應的操作——響應可能是出現在文本字段中的一個值,或是要打開的某個菜單。但對於搜索、加載或處理數據(例如搜索欄、圖表、過濾表等)這些事情,用戶也會期望它們需要一些時間來完成。後者就是你使用 transition 的場景了。

你可以使用 useTransition() 鈎子來創建一個 transition。這個鈎子返回一個函數來啟動一個 transition,還有一個掛起的指示器來通知你 transition 的進度。

import { useTransition, useState } from "react";const App = () => { const [isPending, startTransition] = useTransition(); const [value, setValue] = useState(0); function handleClick() { startTransition(() => { setValue((value) => value + 1); }); } return ( <div> {isPending && <Loader />} <button onClick={handleClick}>{value}</button> </div> );};

你在 startTransition() 回調中提交的任何狀態更新都將被標記為 transition,從而使其他更新具有優先權。如果你不能使用這個鈎子,還有一個單獨的 startTransition() 函數可用——雖然它不會通知你轉換的進度。

import { startTransition } from "react";// ...startTransition(() => { // Transition updates});//...
Suspense 更新

與 React Suspense 結合使用時,transition 的效果是最好的。由於一些改進,Suspense 現在可以很好地與並發渲染集成、在服務器上工作,並且可能很快支持 lazy() 加載組件之外的用例。與 transition 一起使用時,Suspense 將避免隱藏現有內容。考慮以下示例:

import { Suspense } from "react";// ...const App = () => { const [value, setValue] = useState("a"); const handleClick = () => { setValue("b"); }; return ( <> <Suspense fallback={<Loader />}> {value === "a" ? <A /> : <B />} </Suspense> <Button onClick={handleClick}>B</Button> </> );};

在狀態改變時,lazy() 加載的組件將觸發 Suspense,導致 fallback 元素的渲染。如果你將狀態更改標記為一個 transition,React 將知道它應該在後台準備新視圖,同時仍保持當前視圖可見。

import { Suspense, useTransition } from "react";// ...const App = () => { const [value, setValue] = useState("a"); const [isPending, startTransition] = useTransition(); const handleClick = () => { startTransition(() => { setValue("b"); }); }; return ( <> <Suspense fallback={<Loader />}> <div style={{ opacity: isPending ? 0.7 : 1 }}> {value === "a" ? <A /> : <B />} </div> </Suspense> <Button onClick={handleClick}>B</Button> </> );};

現在,即使在處理 transition 時視圖不會改變,你仍然可以使用過渡指示器來向用戶提供反饋,例如設置容器不透明度。將上述改進與未來 Suspense 的新能力(與 lazy() 加載的組件之外的異步任務一起使用)相結合,意味着 Suspense 將成為 React 最強大的特性之一。

服務端渲染改進

除了 Suspense 支持之外,React 的 SSR 方面還有很多其他變化。將 Suspense 與 SSR 流式傳輸和懶惰水合(lazy hydration)相結合,意味着你的服務端渲染應用將儘快水合併可用。不僅如此,零打包體積的服務端組件即將到來。它們目前處於試驗階段,但可能會在以後的次要版本中進入穩定狀態。使用它們時,你將能減少提供給客戶端的 JS 代碼,甚至進一步優化 React 應用程序的性能和加載時間。

漸進採用

由於前文提到的 React 17 的多個更改,即使你的代碼庫很大,你也應該能夠輕鬆地逐步採用 React 18。你不僅可以在應用程序的選定部分中使用新版本,還可以從 render() 遷移到 createRoot(),來一步步選擇加入新的特性和行為。最重要的是,即使使用的是 createRoot(),你仍然可以逐步採用並發渲染,因為它只有在你使用它的特性時才會啟用。總體而言,遷移過程應該很順利,甚至會是一樁樂事。

React 的未來

React 18 帶來了許多新特性,你也可以看到一些即將出現的新事物。服務器組件、用於數據獲取的 Suspense,和組件渲染都是接下來的新特性的一部分。

React 正在與它的整個生態系統一起發展,我迫不及待地想看看接下來會發生什麼!

原文鏈接:

https://blog.openreplay.com/react-18-features-breakdown

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()