組件
在 React 中,組件(Component) 是 UI 的基本構建塊。可以把它理解為一個獨立的、可復用的 UI 單元,類似于函數,它接受輸入(props),然后返回 React 元素來描述 UI。
組件的簡單使用
在 React 中,組件主要分為兩種:類組件和函數組件。但是在 React 18+ 之后,以不推薦使用類組件,而是推薦使用函數組件。
函數組件本質上是一個 Javascript 函數,然后返回一個 JSX 結構。函數組件的名字必須以大寫字母開頭,否則 React 會將其視為一個 HTML 標簽。
函數組件更簡單,它沒有 this, 編寫和閱讀更簡單,而狀態則是通過 React Hooks 來實現的。下面是一個簡單的函數組件的例子:
// 定義組件
function Button() {// 組件內部的邏輯return (<button>Click me</button>);
}
這樣我們就創建了一個 React 組件,接下來,我們可以在 App 組件中使用它:
function App() {return (<div><Button /></div>)
}
除了 Button 組件,App 組件本身也是組件,也就是說組件可以嵌套使用。
組件的特點
- 組件必須返回一個 JSX 結構(或 null)
- 組件名稱必須大寫(如 MyComponent,不能寫 myComponent)
- 可以組合嵌套(組件可以包含其他組件)
- 可以接收 props(用于傳遞數據)
組件的狀態
在 React 組件中,狀態(State) 是指組件內部可變的數據,用于控制組件的行為和 UI 的變化。當 state 發生變化 時,React 會自動重新渲染組件,更新 UI。
為什么需要狀態?
有些數據不會變,比如 props 傳遞過來的值,但有些數據是會隨用戶交互變化的,比如:
- 計數器的數值
- 按鈕的開關狀態
- 輸入框中的內容
這些會變化的數據 就應該存到組件的 state 中。例如:
import { useState } from "react";function Counter() {// useState(0) 表示 count 初始值為 0const [count, setCount] = useState(0);return (<div><p>當前計數:{count}</p>{/* 點擊按鈕時,修改 count 值 */}<button onClick={() => setCount(count + 1)}>+1</button></div>);
}export default Counter;
當點擊 +1 按鈕時,會調用 setCount 方法,將 count 的值加 1,然后 React 會重新渲染組件,更新 UI。具體來說當組件的 state 發生變化時,React 執行以下流程:
- 調用 setState 或 useState 進行狀態更新
- React 觸發組件的重新渲染(函數組件會重新執行,類組件會觸發 render 方法)
- 生成新的 Virtual DOM(虛擬 DOM)
- 比較新的 Virtual DOM 和舊的 Virtual DOM(Diffing 算法)
- 計算差異后更新真實 DOM(Reconciliation 過程)
React 通過 useState(或 setState)觸發狀態更新,然后采用 調度機制(Scheduler)+ 批量更新(Batching) 來通知瀏覽器進行重新渲染。這種機制類似于 Qt 的信號槽或者是發布-訂閱模式。但是 React 的更新機制與 Qt 的信號槽不同的是,React 的更新機制是異步的,而 Qt 的信號槽是同步的。
Virtual DOM 機制
React 并不會直接操作 真實 DOM(Real DOM),而是使用 Virtual DOM 來提升性能。
虛擬 DOM(Virtual DOM) 是 React 在內存中的 JavaScript 對象,它描述了 UI 結構。
每次 state 變化時,React 會重新創建一個新的 Virtual DOM。
Diffing 算法(Diff 算法)
React 通過 Diffing 算法 比較「新的 Virtual DOM」和「舊的 Virtual DOM」,找出不同點,只更新發生變化的部分,避免整個頁面的重新渲染。
Diff 算法流程
-
樹形對比
如果新的 Virtual DOM 和舊的 Virtual DOM 不是相同的組件,則直接銷毀舊組件,創建新組件。如果它們是同一個組件,則繼續向下比較子元素。
-
屬性對比(Props Diffing)
如果 props 發生變化,則更新對應的 DOM 屬性。
-
子元素對比(Children Diffing)
使用Key 機制優化列表渲染(key 幫助 React 識別哪些元素是新增、刪除或移動的,我們在列表渲染中接觸過 key 機制)。
Reconciliation(協調過程)
React 在 Diffing 之后,使用 Reconciliation(協調過程) 將「最小的變更」應用到真實 DOM:
- 僅修改需要更新的 DOM 節點
- 不會重新創建整個 DOM 結構
- 性能更高,避免不必要的操作
React 如何知道狀態發生了變化?
當我們調用 setState(類組件)或 useState(函數組件)時,React 內部執行以下操作:
-
記錄狀態變更
React 維護著一個 state 和 nextState。
當調用 setState(newState) 時,React 不會立即修改 state,而是先把 newState 放入更新隊列。
-
觸發調度(Scheduler 機制)
React 不是立刻重新渲染,而是將更新任務提交給 Scheduler(調度器),并進行批處理優化。
-
標記 Fiber 節點為「需要更新」
React 使用 Fiber 架構,每個組件對應一個 Fiber 節點,setState 會讓 Fiber 節點進入「更新狀態」,等待下一次渲染。
為什么 React 不是立即更新?
React 采用 批量更新(Batching) 機制:
- 同一個事件循環內的多個 setState 只會觸發一次渲染,減少不必要的更新,提高性能。
- React 18 引入 Concurrent Mode,可以延遲低優先級的更新,提高流暢度。
useState 修改狀態
我們還是以一個簡單的 Demo 為例,展示如何使用 useState 修改狀態:
import { useState } from "react"; // 引入 useState
function Counter() {const [count, setCount] = useState(0); // 初始化 count 為 0return (<div><p>當前計數:{count}</p><button onClick={() => setCount(count + 1)}>+1</button><button onClick={() => setCount(count - 1)}>-1</button></div>)
}
const [count, setCount] = useState(0);
這行代碼就類似于發布訂閱模式中,訂閱者注冊自己所需要關注的事件或消息。這里可以理解為訂閱者注冊了 count 變化時,會收到通知,然后更新 count 的值。
在 React 中,useState 是一個 Hook,用于在函數組件中添加狀態。它返回一個數組,數組的第一個元素是當前狀態的值,第二個元素是一個函數,用于更新狀態。
直接改變 count 的值,并不會觸發組件的重新渲染,因為 React 是通過 setState 來觸發組件的重新渲染的。因此需要調用 setCount 來更新 count 的值,然后 React 會重新渲染組件,更新 UI。
多個狀態值管理
你可以在 useState 中存儲多個值,例如:
function UserInfo() {const [user, setUser] = useState({ name: "Alice", age: 25 });return (<div><p>姓名:{user.name}</p><p>年齡:{user.age}</p><button onClick={() => setUser({ ...user, age: user.age + 1 })}>生日+1</button></div>);
}
這里使用了 …user(展開運算符)來避免覆蓋 name,僅修改 age。
組件樣式控制
在 React 中,控制組件的樣式,可以直接在行內樣式中設置,也可以使用 className 屬性來設置類名,然后在 CSS 文件中定義樣式。在工程化中,一般是使用 className 來控制樣式。
行內樣式
在 React 中,你可以在行內樣式中設置樣式,例如:
<div style={{ color: 'red', fontSize: '16px' }}>Hello World</div>
在上面的例子中,style 屬性是一個對象,其中包含了 CSS 屬性及其值。你可以使用駝峰命名法來設置 CSS 屬性,例如 color 和 fontSize。如果覺得 style 屬性太長,可以定義一個變量來存儲樣式對象,然后使用變量來設置樣式。例如:
const style = {color: 'red',fontSize: '16px'
}
<div style={style}>Hello World</div>
通過 className 來控制樣式
在 React 中,你可以使用 className 屬性來控制組件的樣式,例如:
在 CSS 文件中,你可以定義一個類名為 my-class 的樣式,例如:
.my-class {color: red;font-size: 16px;
}
在組件中,你可以使用 className 屬性來設置類名,例如:
import './App.css';
<div className="my-class">Hello World</div>