文章目錄
- 前言
- 一、16更新
- 二、17更新
- 三、18更新
- 四、19更新
- 總結
前言
總結react 16,17,18,19所更新的內容,并且部分會涉及到原理講解。
一、16更新
1、在16.8之前更新,還是基于class組件的升級和維護更新。并且更新了一個重要的架構,Fiber架構。
什么是Fiber:Fiber架構的核心是“Fiber”節點,它是工作的基本單位。每個React元素都對應一個Fiber節點,這些節點構成了一個工作單元的樹狀結構。Fiber節點包含組件的類型、其對應的DOM節點信息以及子節點和兄弟節點的引用等信息。
2、而16.8的更新,是react的一個重要更新呢版本。
更新內容:
-
引入Hooks,新增許多hooks的api
-
Hooks解決了什么問題:
① 組件復用:
之前:
函數組件無法直接管理狀態和生命周期,所以組件復用通常依賴類組件,和高階組件實現。
例如:// 高階組件 function withLoading(WrappedComponent) {return class extends React.Component {state = { isLoading: false };componentDidMount() {this.setState({ isLoading: true });setTimeout(() => {this.setState({ isLoading: false });}, 2000);}render() {const { isLoading } = this.state;return (<div>{isLoading ? <div>Loading...</div> : <WrappedComponent {...this.props} />}</div>);}}; } // 被包裹的組件 class MyComponent extends React.Component {render() {return <div>My Component Content</div>;} } // 使用高階組件 const MyComponentWithLoading = withLoading(MyComponent); // 渲染 ReactDOM.render(<MyComponentWithLoading />, document.getElementById("root"));
hooks出現之后:
例如:// 自定義 Hook function useLoading() {const [isLoading, setIsLoading] = useState(false);React.useEffect(() => {setIsLoading(true);const timer = setTimeout(() => {setIsLoading(false);}, 2000);return () => clearTimeout(timer); // 清理副作用}, []);return isLoading; } // 函數組件 function MyComponent() {const isLoading = useLoading();return (<div>{isLoading ? <div>Loading...</div> : <div>My Component Content</div>}</div>); } // 渲染 ReactDOM.render(<MyComponent />, document.getElementById("root"));
② 生命周期函數復雜性:
在類組件中,常見的生命周期方法包括:
constructor:初始化狀態。
componentDidMount:組件掛載后執行。
componentDidUpdate:組件更新后執行。
componentWillUnmount:組件卸載前執行。
使用 Hooks 模擬生命周期-
初始化狀態(constructor)
在函數組件中,狀態可以通過 useState 初始化。 -
模擬 componentDidMount
使用 useEffect 的回調函數,當組件掛載后執行。 -
模擬 componentDidUpdate
使用 useEffect 的依賴數組,當依賴項變化時執行。 -
模擬 componentWillUnmount
使用 useEffect 的返回值(清理函數),在組件卸載前執行。
直接示例:class MyComponent extends React.Component {constructor(props) {super(props);this.state = {data: null,count: 0};}componentDidMount() {console.log("Component did mount");this.fetchData();}componentDidUpdate(prevProps, prevState) {console.log("Component did update");if (prevState.count !== this.state.count) {console.log(`Count changed from ${prevState.count} to ${this.state.count}`);}}componentWillUnmount() {console.log("Component will unmount");this.clearData();}fetchData = () => {// 模擬數據加載setTimeout(() => {this.setState({ data: "Loaded data" });}, 1000);};clearData = () => {console.log("Clearing data");this.setState({ data: null });};render() {const { data, count } = this.state;return (<div><h1>{data || "Loading..."}</h1><button onClick={() => this.setState({ count: count + 1 })}>Increment</button></div>);} }
hooks模擬:
function MyComponent() {const [data, setData] = React.useState(null);const [count, setCount] = React.useState(0);// 模擬 componentDidMountReact.useEffect(() => {console.log("Component did mount");fetchData();}, []); // 空依賴數組表示只在掛載時執行// 模擬 componentDidUpdateReact.useEffect(() => {console.log("Component did update");console.log(`Count changed to ${count}`);}, [count]); // 依賴數組包含 count,表示當 count 變化時執行// 模擬 componentWillUnmountReact.useEffect(() => {return () => {console.log("Component will unmount");clearData();};}, []); // 空依賴數組表示只在卸載時執行const fetchData = () => {// 模擬數據加載setTimeout(() => {setData("Loaded data");}, 1000);};const clearData = () => {console.log("Clearing data");setData(null);};return (<div><h1>{data || "Loading..."}</h1><button onClick={() => setCount(count + 1)}>Increment</button></div>); }
-
二、17更新
1、新的JSX轉化,不需要手動引入react
-
React 16
babel-loader會預編譯JSX為 React.createElement(…) -
React 17
React 17中的 JSX 轉換不會將 JSX 轉換為 React.createElement,而是自動從 React 的 package 中引入react并調用。而是React 的運行時調用jsx 和 jsxs 函數:react/jsx-runtime 模塊提供了 jsx 和 jsxs 函數,分別用于處理單個子元素和多個子元素的情況。這些函數在運行時被調用,用于創建 React 元素。
jsx與 React.createElement 相比,jsx 函數在處理子元素和 key 值時更加高效。例如,key 值在 jsx 函數中作為第三個參數直接傳遞,而不是像在 React.createElement 中那樣作為屬性對象的一部分。
優勢:
減少包體積
簡化代碼:開發者不再需要在每個組件文件頂部顯式引入 React,使得組件代碼更加簡潔。
優化包大小:由于不再需要導入整個 React 對象,構建工具可以更好地優化輸出代碼,從而減小輸出包的大小。
高效參數處理
參數結構優化:jsx 函數的參數結構更加合理。它將 children 放在了 props 對象中,并將 key 作為單獨的參數傳遞。這種參數結構使得 React 在處理元素時可以更高效地訪問和處理 children 和 key,減少了不必要的屬性查詢和處理邏輯。
子元素處理優化:在 React 17 之前,React.createElement 的子元素是作為后續參數傳遞的,這在處理多個子元素時可能會導致性能問題。而 jsx 函數將子元素作為數組傳遞給 children 屬性,這種方式更加清晰且易于優化。
性能優化空間
靜態分析優化:由于 jsx 函數的實現更加標準化和簡潔,編譯器可以更容易地對 JSX 代碼進行靜態分析和優化。例如,編譯器可以在編譯時進行常量提升、代碼壓縮等優化操作,從而生成更高效的代碼。
減少動態屬性查詢:jsx 函數消除了對動態屬性查找的需要,這在一定程度上減少了運行時的性能開銷。雖然現代 JavaScript 引擎對動態屬性查詢進行了優化,但在大規模應用中,這種優化仍然可以帶來一定的性能提升例如:
import { jsx as _jsx } from 'react/jsx-runtime'; function App() {return _jsx('h1', { children: 'Hello' }, '1'); }
2、事件代理更改
17中,不在document對象上綁定事件,改為綁定于每個react應用的rootNode節點,因為各個應用的rootNode肯定不同,所以這樣可以使多個版本的react應用同時安全的存在于頁面中,不會因為事件綁定系統起沖突。react應用之間也可以安全的進行嵌套。
3、事件池(event pooling)的改變
4、異步執行
17將副作用清理函數(useEffect)改為異步執行,即在瀏覽器渲染完畢后執行。
5、forwardRef 和 memo組件的行為
React 17中forwardRef 和 memo組件的行為會與常規函數組件和class組件保持一致。它們在返回undefined時會報錯。
三、18更新
注意:v18的新特性是使用現代瀏覽器的特性構建的,徹底放棄對 IE 的支持。
-
并發模式
18引入了并發特性,并發特性允許React在渲染過程中中斷和恢復工作,從而更好地響應用戶交互和系統事件,與傳統的同步渲染不同,并發渲染將渲染任務拆分為多個小任務,并根據優先級動態調度這些任務。
實現原理
Fiber架構:React 18使用Fiber架構來管理渲染任務。Fiber節點包含組件的類型、狀態、props等信息,并允許React在渲染過程中暫停和恢復。Fiber架構使用雙端隊列(work-in-progress tree和current tree)來管理渲染任務。
時間切片:React將長時間的渲染任務拆分成多個較短的時間片,以避免阻塞主線程。雖然React內部自動管理時間切片,但開發者可以通過控制更新任務的優先級來間接影響時間切片的分配。
優先級調度:React引入了任務優先級的概念,將任務分為不同的優先級,如高優先級任務(用戶交互、動畫等)和低優先級任務(數據加載、復雜計算等)。React會根據任務的優先級動態調度渲染任務,確保高優先級任務能夠及時得到處理。
中斷與恢復:在渲染過程中,如果瀏覽器資源緊張或有其他高優先級的任務需要執行,React可以暫停當前的渲染任務,釋放資源給更重要的任務。一旦資源變得可用,React會恢復之前的渲染任務,并繼續執行剩余的小任務。
如何使用并發特性
啟用并發模式:通過將應用切換到并發模式,開發者可以充分利用并發渲染的優勢。示例代碼如下:import ReactDOM from 'react-dom/client'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />);
-
更新render API
// v17 import ReactDOM from 'react-dom' import App from './App' ReactDOM.render(<App />, document.getElementById('root')) // v18 import ReactDOM from 'react-dom/client' import App from './App' ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />)
-
自動批處理
批處理是指 React 將多個狀態更新,聚合到一次 render 中執行,以提升性能
在v17的批處理只會在事件處理函數中實現,而在Promise鏈、異步代碼、原生事件處理函數中失效。而v18則所有的更新都會自動進行批處理。// v17 const handleBatching = () => {// re-render 一次,這就是批處理的作用setCount((c) => c + 1)setFlag((f) => !f) } // re-render兩次 setTimeout(() => {setCount((c) => c + 1)setFlag((f) => !f) }, 0)// v18 const handleBatching = () => {// re-render 一次setCount((c) => c + 1)setFlag((f) => !f) } // 自動批處理:re-render 一次 setTimeout(() => {setCount((c) => c + 1)setFlag((f) => !f) }, 0)
如果不想批處理,可以用flushSync強制同步處理,同步執行更新
-
新api
1、startTransition
示例:如果一個渲染任務較重的tab頁面切換,包裹后可以不阻塞其交互點擊行為。
原理:降低渲染優先級,優先處理用戶交互行為,渲染行為延后import { startTransition } from 'react'; function TabContainer() {const [tab, setTab] = useState('about');function selectTab(nextTab) {startTransition(() => {setTab(nextTab);});} }
2、useTransition
提供了一個帶有isPending標志的 Hook useTransition來跟蹤 transition 狀態,用于過渡期。
startTransition回調可以嵌套和處理異步方法function TabContainer() {const [isPending, startTransition] = useTransition();const [tab, setTab] = useState('about');function selectTab(nextTab) {startTransition(() => {setTab(nextTab);});} }
3、useDeferredValue
類似于上一個hooks標記一個state為延遲更新數據,標記為非緊急更新狀態。import { useState, useDeferredValue } from 'react'; function SearchPage() {const [query, setQuery] = useState('');const deferredQuery = useDeferredValue(query); }
4、useId
useId支持同一個組件在客戶端和服務端生成相同的唯一的 ID,原理就是每個 id 代表該組件在組件樹中的層級結構
四、19更新
1、支持元數據標簽
文檔元數據支持:支持在組件中渲染 <title>
、<link>
和 <meta>
標簽,并自動提升到文檔的 <head>
部分
2、 新增Hooks
-
useOptimistic
用于管理樂觀更新。當執行某個操作時,可以先假設操作成功,并立即更新 UI,然后在操作完成后根據實際結果調整狀態。比如點贊、評論、加入購物車等功能,我們都可以先假設成功,再根據接口返回來調整。 -
useActionState
用于管理與用戶操作相關的狀態。它能夠記錄和回放用戶操作,幫助實現更復雜的交互和調試功能。function ChangeName({ currentName }) {const [error, submitAction, isPending] = useActionState(async (prev, formData) => {const error = await updateName(formData.get("name"));if (error) return error;return null;});return (<form action={submitAction}><input type="text" name="name" defaultValue={currentName} /><button type="submit" disabled={isPending}>Update</button>{error && <p>{error}</p>}</form>);}
-
use
它可以讓你讀取類似于 Promise 或 context 的資源的值。可以在if中使用import { use } from 'react'; function MessageComponent({ messagePromise }) {const message = use(messagePromise);const theme = use(ThemeContext);
-
優化
React 編譯器可以自動緩存計算結果來優化組件,減少不必要的重新讀取。在許多情況下,開發人員無需明確使用記憶化鉤子,如 useMemo 和 useCallback。例如,以往需要手動使用 useMemo 來緩存昂貴的計算結果,現在可以直接寫代碼,編譯器會自動優化。 -
棄用forwardRef
const MyInput = forwardRef(function MyInput(props, ref) {return (<label>{props.label}<input ref={ref} /></label>); });
可以直接使用ref
function MyInput(props) {return (<label>{props.label}<input ref={props.ref} /></label>); }
-
寫法優化
<Context>
作為提供者,可以直接使用<Context>
作為提供者,而不是<Context.Provider>
。
總結
React 16:引入 Fiber 架構,提升渲染性能,新增錯誤邊界等功能。
React 17:事件系統重構,新的 JSX 轉換機制,優化事件處理。
React 18:并發渲染、自動批處理、服務器組件,性能大幅提升。
React 19:引入 Actions 和表單狀態管理 API,自動優化性能,簡化開發體驗