入口: const root = ReactDOM.createRoot(document.getElementById('root'))root.render(類組件)?? 類組件內部render() {return (<div>12</div>)}?? (經過babel-preset-react-app 把jsx語法,編譯為h函數形式) React.createElement('div', null, '12')?? ( React.createElement返回值是一個普通 JavaScript 對象,即 React 元素)該元素是對UI結構以及屬性的描述。{ type: 'div',props: { children: '123' },key: null,ref: null,_owner: null,_store: {}}?? 然后render 將 react元素 生成為虛擬DOM 再由react內部將虛擬dom構建為樹,進行diff計算等,再創建真實dom?? componentDidMount生命周期鉤子 組件掛載完成
代碼定義 → JSX轉換 → 創建元素 → Fiber樹構建 → 協調(Diff) → DOM更新 → 生命周期回調
class vote extends React.Component{render() {return (<div>....</div>
}
}
- 使用vote組件
import voteconst root = ReactDOM.createRoot(document.getElementById('root'))root.render(<Vote {title = 'xxx'}/>)
- ????注意: react18中,setState 本身并不是傳統意義上的“異步操作”(如 Promise 或 setTimeout),而是
?批量更新(Batching)?機制
的表現。React 內部維護一個更新隊列
,在 ?同一渲染周期內 的多個 setState 會被合并處理,多個狀態更新合并(批量處理)后再統一計算新狀態和觸發渲染,這可能導致狀態更新看起來“延遲生效”。
所以本文中講的異步操作都理解為?批量更新(Batching)?機制
- 🆘 react16 的setState 在
合成事件
又有區別
handleVue() {console.log(this)}handleVue2 = (x, y) => {console.log(x, y, this)}render() {console.log('渲染完成')return (<div className="vote"><button onClick={this.handleVue}>vue</button> {/* this指向undefined */}<button onClick={() => this.handleVue()}>vue</button> {/* this指向組件實例 */}<button onClick={this.handleVue.bind(this)}>vue</button> {/* this指向組件實例 */}<button onClick={this.handleVue2.bind(1, 2)}>vue</button> {/* this指向組件實例,且預傳參 */}</div>)}
- 合成事件的事件委托機制
合成事件都會委托給入口文件的掛載結點 #root上,合成事件與原生事件能在同一節點并行,到那時合成事件的綁定要早于原生事件,因為合成事件是react內部綁定的。
?? 注意:
事件傳播分離:合成事件與原生事件的傳播路徑獨立。阻止合成事件冒泡不會影響原生事件,反之亦然。
- purecomponent 是基于堆內存地址的淺比較,一個對象的某個深層屬性更改,它判定為地址不變,不會觸發頁面更新 ?,這是錯誤的。所以它不適合深層次的屬性的 state 或 props 的組件。
💯Hooks組件
💝 this: 因為類組件
需要 react內部new 來實例化,所以要處理this綁定的問題,而函數組件
是 純函數,每次渲染都會重新執行該函數組件,產生一個私有上下文,所以不涉及this的處理。
???🔥 想了解hooks組件的更新機制,先理解一下Fiber
Fiber 是 React 維護的一種數據結構,包含組件相關的所有信息。
每個React (函數 ,類) 組件都對應 一個Fiber 節點,所有的組件組成了 完整的Fiber樹。
- React 維護兩棵 Fiber 樹:?Current Tree?(當前屏幕顯示)和 ?WorkInProgress Tree?(正在構建的更新)。
- 狀態變更時,兩棵樹變更的節點 進行對比,復用大部分屬性?(如 type、key),僅更新變化的 props 或 state。
fiber = {type: ComponentFunction, 組件類型(函數組件)memoizedState: hook1, **指向 Hook 鏈表的頭部**stateNode: {}, 組件實例(類組件)或 DOM 節點// 其他調度相關屬性(如優先級、副作用鏈表等)
}
???🔥 再了解一下Hook
Hook(鉤子),eg:useState
為一個hook函數,它的每一次調用,都會生成一個React內部維護的hook對象
const [count, setCount] = useState(0)hook對象 = {memoizedState: 0, 當前頁面展現的值baseState: 0, 所有更新(queue)處理后的新基準.當所有已處理的更新(queue 中的更新)被消費后,baseState 會被更新為最新的 memoizedState**但是** 渲染被中斷(如高優先級任務插隊),React 會回退到 baseState 重新計算狀態。queue: null, 更新隊列(存儲 setCount 觸發的更新動作)next: null, 指向下一個 Hook 的指針
};
hook對象的queue隊列作用如下:
const [count, setCount] = useState(0);
const handleClick = () => {setCount(1); setCount(2);
};// handleClick()后的hook對象
hook = {memoizedState: 0, // 當前狀態值baseState: 0, // 基礎狀態(初始值)queue: { setCount(1),setCount(2); }, // 更新隊列next: null, // 指針
}
每次調用hook的副作用函數后,就會加入到對應hook對象的更新隊列中,等待react 內部更新。
每一個hook函數自上而下產生的hook對象 ,都會以鏈表的形式維護在 fiber
的memoizedState
中
function Component() {const [name, setName] = useState("Alice"); hook對象1const [age, setAge] = useState(30); hook對象2const [location, setLocation] = useState("Nowhere"); hook對象3
}
首次渲染(Mount)鏈表指向
Fiber.memoizedState → Hook對象1 → Hook對象2 → Hook對象3
后續渲染(Update)時,復用hook對象
Fiber.memoizedState → Hook對象1 → Hook對象2 → Hook對象3
React 再次執行組件函數,?按相同順序調用 useState
但不會重新創建 Hook 對象,而是按順序遍歷 Fiber鏈表
💘 setState對象形式 通過「閉包」鏈接hook對象
為什么要閉包?
閉包的作用是確保同一組件實例在不同渲染周期中的狀態和上下文被正確隔離
是實例狀態持久化的唯一手段。
每一個useState 執行產生的hook對象,hook中變量對外暴露,外部引用了該變量, 為一個閉包的產生。
const [count, setCount] = useState(0) 來自React內部存儲的count,而不是組件函數的局部變量。const handleClick = () => {setCount(count + 1) 引用了count,閉包產生}
useState 簡要更新邏輯
let hook = [memoizedState: any, // 當前狀態值(對于 useState,保存的是 state)baseState: any, // 基礎狀態(用于計算更新)baseQueue: Update<any> | null, // 待處理的更新隊列queue: UpdateQueue<any> | null, // 狀態更新隊列(保存 setState 觸發的更新)next: Hook | null,];function useState(newValue) {// 定義 setState 函數const setState = (newValue) => {// 收集更新階段:將更新加入當前 Hook 的隊列// 加入隊列hooks.queue.push(newValue);
------------------------------------------上下為兩個不同的階段// 處理更新階段for (const update of hooks.queue) {currentState = typeof update === 'function' ? update.action(currentState) // 函數式更新,傳入當前狀態: newState = update.action; // 對象式更新,直接替換值}hooks.memoizedState = currentState; // 更新最終狀態hooks.queue = []; // 觸發重新渲染(模擬 React 的更新機制)scheduleRender();};// 返回值 和 副作用函數 setStatereturn [hook.memoizedState, setState];
}// 模擬重新渲染
function scheduleRender() {currentHookIndex = 0; // 重置索引,準備下一次渲染render();
}
?:Fiber有了,hook對象有了,閉包有了,可以來梳理 hook 是如何讓函數組件動態起來了。
1. 組件首次渲染(Mount):- Fiber 創建 → 初始化 Hook 鏈表(useState) → setState 閉包綁定 Fiber 和 Hook。2. 用戶觸發 setState:- setState 通過閉包找到對應的 Hook → 將更新加入 Hook.queue。→ 調度更新(scheduleUpdateOnFiber)。3. React 調度更新:- 進入渲染階段 → 從 Fiber 讀取 Hook 鏈表 → 按順序處理每個 Hook 的隊列。→ 計算新狀態 → 更新 Hook.memoizedState。4. 組件函數重新執行-渲染(Update):- 使用最新的狀態生成新 VDOM → 協調(Reconciliation) → 提交(Commit)到 DOM。
?? useState 返回的變量, 是函數組件外, React內部狀態在該次渲染中的狀態值快照
當組件執行,并引用了這個變量時,閉包就會形成,
所以調用棧的同步代碼,或是將要進入任務隊列的回調,都會提前綁定好該閉包的引用
當任務隊列的事件推到調用棧時,直接拿到前面已經綁定的閉包值來使用。
useState 在每次組件渲染時都會被調用,但不會重新初始化狀態(除非組件重新掛載)。它的作用是返回當前狀態值,而不是重新執行初始化邏輯
?? 注意
執行到 setstate() 這一行時,會把它注冊到 hook.queue 對象中,且不會立馬執行 (該hook由react維護,不進入堆棧,任務隊列)
接著執行后續代碼,同步的直接執行,異步的進入隊列
當調用棧清空
此時hook.queue 會先于任務隊列執行,
當hook.queue中的更新動作全部消費完,就會觸發組件重新調用渲染,
然后再執行剛剛加入任務隊列的事件。
💝所以,可以理解為一輪事件周期內,queue中的setstate() 的消費,要快于任務隊列。
???🔥 看代碼理解
const [count, setCount] = useState(0)
const handleClick = () => {setCount(count + 1)setTimeout(() => {console.log(count, 'count')})}
- handleclick 入調用棧
- setcount調用,進入hook.queue 等待批量更新,引用了count ,在堆中產生count的閉包
- settimeout 的回調綁定count的閉包引用(定義時綁定),再加入任務隊列
- 消費hook.quene ,觸發Fiber調度,更新count,渲染頁面
- 拉取任務隊列的定時器回調,并執行。
🧪 setState 函數式更新
與對象式不同的唯一一點就是變量的獲取
- 對象式直接是綁定閉包引用來進行計算
- 而函數式, 回調函數在鏈表中執行時,永遠上一個的執行后的pedding狀態,是下一個回調任務的參數。稱為鏈式狀態傳遞。
? 簡單的 setState 更新模型。
flushsync
flushSync會繞過 React 的自動批處理,強制同步處理狀態更新,但僅在新舊狀態值實際變化時觸發組件渲染。
?? useState 自帶性能優化
-
?短路優化:當新狀態與舊狀態相同(Object.is 比較)時,React 會跳過渲染。
-
更新隊列同一批中,多個函數式的,會基于前一個pending的狀態更改(不論是否函數式), 將最新值穿給下一個,最后觸發一次更新渲染
例如,假設當前count是0:調用setCount(0 +1) → 計劃更新到1接著調用setCount(prev => prev +10),這里的prev是前一個pending計劃的狀態,即1,所以結果為11。
-
更新隊列同一批中,多個對象形式的,后面的覆蓋前面的,僅采取最后一次action觸發一次更新渲染
export default function Counter() {console.log('render')const [count, setCount] = useState(0)const handleClick = () => {for (let index = 0; index < 10; index++) {setCount(count + 5)}
}
1:點擊按鈕,render執行幾次?count最終為?
export default function Counter() {console.log('render')const [count, setCount] = useState(0)const handleClick = () => {for (let index = 0; index < 10; index++) {setCount(count + index)}return (<div><p>Count: {count}</p><button onClick={handleClick}>按鈕</button></div>)
}
2:點擊按鈕,render執行幾次?count最終為?
export default function Counter() {console.log('render')const [count, setCount] = useState(0)const handleClick = () => {for (let index = 0; index < 10; index++) {setCount(prev => prev + 10)}return (<div><p>Count: {count}</p><button onClick={handleClick}>按鈕</button></div>)
}
3:點擊按鈕,render執行幾次?count最終為?
export default function Counter() {console.log('render')const [count, setCount] = useState(0)const handleClick = () => {for (let index = 0; index < 10; index++) {setCount(prev => prev + index)}return (<div><p>Count: {count}</p><button onClick={handleClick}>按鈕</button></div>)
}
4:點擊按鈕,render執行幾次?count最終為?
export default function Counter() {console.log('render')const [count, setCount] = useState(0)const handleClick = () => {for (let index = 0; index < 10; index++) {flushSync(() => {setCount(count + 1)})}return (<div><p>Count: {count}</p><button onClick={handleClick}>按鈕</button></div>)
}
5:點擊按鈕,render執行幾次?count最終為?
export default function Counter() {console.log('render')const [count, setCount] = useState(0)const handleClick = () => {for (let index = 0; index < 10; index++) {flushSync(() => {setCount(prev => prev + index)})}return (<div><p>Count: {count}</p><button onClick={handleClick}>按鈕</button></div>)
}
6:點擊按鈕,render執行幾次?count最終為?
export default function Counter() {console.log('render')const [count, setCount] = useState(0)const handleClick = () => {for (let index = 0; index < 10; index++) {setCount(count + 1)setCount(prev => prev + 10)}return (<div><p>Count: {count}</p><button onClick={handleClick}>按鈕</button></div>)
}
6:點擊按鈕,render執行幾次?count最終為?
export default function Counter() {console.log('render')const [count, setCount] = useState(0)const handleClick = () => {for (let index = 0; index < 10; index++) {flushSync(() => {setCount(count + index)})}return (<div><p>Count: {count}</p><button onClick={handleClick}>按鈕</button></div>)
}
7:點擊按鈕,render執行幾次?count最終為?