React知識點梳理
本文適合對React知識點存在疑惑的小伙伴閱讀
歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進階~
一、前言

二、正文
react 生命周期函數(shù)有哪些
React生命周期有哪些坑,如何避免
1. getDerivedStateFromProps 容易編寫反模式代碼,使受控組件與非受控組件區(qū)分模糊。
2. componentWillMount 在 React 中已被標記棄用,不推薦使用,主要原因是新的異步渲染架構(gòu)會導致它被多次調(diào)用。所以網(wǎng)絡請求及事件綁定代碼應移至 componentDidMount 中。
3. componentWillReceiveProps 同樣被標記棄用,被 getDerivedStateFromProps 所取代,主要原因是性能問題(componentWillReceiveProps 更新狀態(tài)是同步進行的,而 getDerivedStateFromProps ?的設計更適用于異步場景。)。
4. shouldComponentUpdate 通過返回 true 或者 false 來確定是否需要觸發(fā)新的渲染。主要用于性能優(yōu)化。
5. componentWillUpdate 同樣是由于新的異步渲染機制,而被標記廢棄,不推薦使用,原先的邏輯可結(jié)合
getSnapshotBeforeUpdate 與 componentDidUpdate 改造使用。
6. 如果在 componentWillUnmount 函數(shù)中忘記解除事件綁定,取消定時器等清理操作,容易引發(fā) bug。
7. 如果沒有添加錯誤邊界處理,當渲染發(fā)生異常時,用戶將會看到一個無法操作的白屏,所以一定要添加。
React 的請求應該放在哪里,why?
對于異步請求,應該放在 componentDidMount 中去操作。從時間順序來看,除了 componentDidMount 還可以有以下選擇:
componentWillMount:已被標記廢棄,在新的異步渲染架構(gòu)下會觸發(fā)多次渲染,容易引發(fā) Bug,不利于未來 React 升級后的代碼維護。
所以React 的請求放在 componentDidMount 里是最好的選擇。
hooks使用中有什么要注意的?
//?initialState
const?[visible,?setVisible]?=?useState(false)
//?modify?the?state
//?bad?
setVisible(!visible)
//?good
setVisible(visible?=>?!visible)
類組件與函數(shù)組件有什么區(qū)別?
共同點:
不同點:
基礎(chǔ)認識
函數(shù)組件的根基是 FP,也就是函數(shù)式編程。它屬于“結(jié)構(gòu)化編程”的一種,與數(shù)學函數(shù)思想類似。也就是假定輸入與輸出存在某種特定的映射關(guān)系,那么輸入一定的情況下,輸出必然是確定的。
獨特性
使用場景
但在 recompose 或 Hooks 的加持下,這樣的邊界就模糊化了,類組件與函數(shù)組件的能力邊界是完全相同的,都可以使用類似生命周期等能力。
設計模式
性能優(yōu)化
而函數(shù)組件一般靠 React.memo 來優(yōu)化。
hooks中為什么不能使用if else 邏輯判斷?
平時如何設計 React 組件?
組件分為:展示組件與靈巧組件
setState 是同步更新還是異步更新?
那么什么情況下 isBatchingUpdates 會為 true 呢?在 React 可以控制的地方,就為 true,比如在 React 生命周期事件和合成事件中,都會走合并操作,延遲更新的策略。
啟用并發(fā)更新,完成異步渲染。
setState 是同步還是異步的核心關(guān)鍵點:更新隊列。
如何面向組件跨層級通信?
在子與父的情況下,有兩種方式,分別是回調(diào)函數(shù)與實例函數(shù)。回調(diào)函數(shù),比如輸入框向父級組件返回輸入內(nèi)容,按鈕向父級組件傳遞點擊事件等。實例函數(shù)的情況有些特別,主要是在父組件中通過 React 的 ref API 獲取子組件的實例,然后是通過實例調(diào)用子組件的實例函數(shù)。這種方式在過去常見于 Modal 框的顯示與隱藏。這樣的代碼風格有著明顯的 jQuery 時代特征,在現(xiàn)在的 React 社區(qū)中已經(jīng)很少見了,因為流行的做法是希望組件的所有能力都可以通過 Props 控制。
Virtual DOM 的原理是什么?
虛擬 DOM 的工作原理是通過 JS 對象模擬 DOM 的節(jié)點。在 Facebook 構(gòu)建 React 初期時,考慮到要提升代碼抽象能力、避免人為的 DOM 操作、降低代碼整體風險等因素,所以引入了虛擬 DOM。
虛擬 DOM 在實現(xiàn)上通常是 Plain Object,以 React 為例,在 render 函數(shù)中寫的 JSX 會在 Babel 插件的作用下,編譯為 React.createElement 執(zhí)行 JSX 中的屬性參數(shù)。
React.createElement 執(zhí)行后會返回一個 Plain Object,它會描述自己的 tag 類型、props 屬性以及 children 情況等。這些 Plain Object 通過樹形結(jié)構(gòu)組成一棵虛擬 DOM 樹。當狀態(tài)發(fā)生變更時,將變更前后的虛擬 DOM 樹進行差異比較,這個過程稱為 diff,生成的結(jié)果稱為 patch。計算之后,會渲染 Patch 完成對真實 DOM 的操作。
虛擬 DOM 的優(yōu)點主要有三點:改善大規(guī)模 DOM 操作的性能、規(guī)避 XSS 風險、能以較低的成本實現(xiàn)跨平臺開發(fā)。
虛擬 DOM 的缺點在社區(qū)中主要有兩點。
內(nèi)存占用較高,因為需要模擬整個網(wǎng)頁的真實 DOM。
高性能應用場景存在難以優(yōu)化的情況,類似像 Google Earth 一類的高性能前端應用在技術(shù)選型上往往不會選擇 React。
React 的 diff 算法有什么不同?
diff 算法是指生成更新補丁的方式,主要應用于虛擬 DOM 樹變化后,更新真實 DOM。所以 diff 算法一定存在這樣一個過程:觸發(fā)更新 → 生成補丁 → 應用補丁。
React 的 diff 算法,觸發(fā)更新的時機主要在 state 變化與 hooks 調(diào)用之后。此時觸發(fā)虛擬 DOM 樹變更遍歷,采用了深度優(yōu)先遍歷算法。但傳統(tǒng)的遍歷方式,效率較低。為了優(yōu)化效率,使用了分治的方式。將單一節(jié)點比對轉(zhuǎn)化為了 3 種類型節(jié)點的比對,分別是樹、組件及元素,以此提升效率。
樹比對:由于網(wǎng)頁視圖中較少有跨層級節(jié)點移動,兩株虛擬 DOM 樹只對同一層次的節(jié)點進行比較。
組件比對:如果組件是同一類型,則進行樹比對,如果不是,則直接放入到補丁中。
元素比對:主要發(fā)生在同層級中,通過標記節(jié)點操作生成補丁,節(jié)點操作對應真實的 DOM 剪裁操作。
以上是經(jīng)典的 React diff 算法內(nèi)容。自 React 16 起,引入了 Fiber 架構(gòu)。為了使整個更新過程可隨時暫停恢復,節(jié)點與樹分別采用了 FiberNode 與 FiberTree 進行重構(gòu)。fiberNode 使用了雙鏈表的結(jié)構(gòu),可以直接找到兄弟節(jié)點與子節(jié)點。
整個更新過程由 current 與 workInProgress 兩株樹雙緩沖完成。workInProgress 更新完成后,再通過修改 current 相關(guān)指針指向新節(jié)點。
然后拿 Vue 和 Preact 與 React 的 diff 算法進行對比。
Preact 的 Diff 算法相較于 React,整體設計思路相似,但最底層的元素采用了真實 DOM 對比操作,也沒有采用 Fiber 設計。Vue 的 Diff 算法整體也與 React 相似,同樣未實現(xiàn) Fiber 設計。
然后進行橫向比較,React 擁有完整的 Diff 算法策略,且擁有隨時中斷更新的時間切片能力,在大批量節(jié)點更新的極端情況下,擁有更友好的交互體驗。
Preact 可以在一些對性能要求不高,僅需要渲染框架的簡單場景下應用。
Vue 的整體 diff 策略與 React 對齊,雖然缺乏時間切片能力,但這并不意味著 Vue 的性能更差,因為在 Vue 3 初期引入過,后期因為收益不高移除掉了。除了高幀率動畫,在 Vue 中其他的場景幾乎都可以使用防抖和節(jié)流去提高響應性能。
react更新的流程是怎樣的?
React在props或state發(fā)生改變時,會調(diào)用render()方法,創(chuàng)建一棵不同的樹。

同層節(jié)點之間相互比較,不會垮節(jié)點比較 不同類型的節(jié)點,產(chǎn)生不同的樹結(jié)構(gòu) 通過key來指定哪些節(jié)點在不同的渲染下保持穩(wěn)定
Diffing算法(調(diào)和算法):
詳細描述下React 的渲染流程

圖片來源網(wǎng)絡
React 的渲染過程大致一致,但協(xié)調(diào)并不相同,以 React 16 為分界線,分為 Stack Reconciler 和 Fiber Reconciler。這里的協(xié)調(diào)從狹義上來講,特指 React 的 diff 算法,廣義上來講,有時候也指 React 的 reconciler 模塊,它通常包含了 diff 算法和一些公共邏輯。
回到 Stack Reconciler 中,Stack Reconciler 的核心調(diào)度方式是遞歸。調(diào)度的基本處理單位是事務,它的事務基類是 Transaction,這里的事務是 React 團隊從后端開發(fā)中加入的概念。在 React 16 以前,掛載主要通過 ReactMount 模塊完成,更新通過 ReactUpdate 模塊完成,模塊之間相互分離,落腳執(zhí)行點也是事務。
在 React 16 及以后,協(xié)調(diào)改為了 Fiber Reconciler。它的調(diào)度方式主要有兩個特點,第一個是協(xié)作式多任務模式,在這個模式下,線程會定時放棄自己的運行權(quán)利,交還給主線程,通過requestIdleCallback 實現(xiàn)。第二個特點是策略優(yōu)先級,調(diào)度任務通過標記 tag 的方式分優(yōu)先級執(zhí)行,比如動畫,或者標記為 high 的任務可以優(yōu)先執(zhí)行。Fiber Reconciler的基本單位是 Fiber,F(xiàn)iber 基于過去的 React Element 提供了二次封裝,提供了指向父、子、兄弟節(jié)點的引用,為 diff 工作的雙鏈表實現(xiàn)提供了基礎(chǔ)。
在新的架構(gòu)下,整個生命周期被劃分為 Render 和 Commit 兩個階段。Render 階段的執(zhí)行特點是可中斷、可停止、無副作用,主要是通過構(gòu)造 workInProgress 樹計算出 diff。以 current 樹為基礎(chǔ),將每個 Fiber 作為一個基本單位,自下而上逐個節(jié)點檢查并構(gòu)造 workInProgress 樹。這個過程不再是遞歸,而是基于循環(huán)來完成。
在執(zhí)行上通過 requestIdleCallback 來調(diào)度執(zhí)行每組任務,每組中的每個計算任務被稱為 work,每個 work 完成后確認是否有優(yōu)先級更高的 work 需要插入,如果有就讓位,沒有就繼續(xù)。優(yōu)先級通常是標記為動畫或者 high 的會先處理。每完成一組后,將調(diào)度權(quán)交回主線程,直到下一次 requestIdleCallback 調(diào)用,再繼續(xù)構(gòu)建 workInProgress 樹。
在 commit 階段需要處理 effect 列表,這里的 effect 列表包含了根據(jù) diff 更新 DOM 樹、回調(diào)生命周期、響應 ref 等。
但一定要注意,這個階段是同步執(zhí)行的,不可中斷暫停,所以不要在 componentDidMount、componentDidUpdate、componentWiilUnmount 中去執(zhí)行重度消耗算力的任務。
React Hook 限制了哪些內(nèi)容?
主要有兩條:
1、不要在循環(huán)、條件或嵌套函數(shù)中調(diào)用 Hook;
2、在 React 的函數(shù)組件中調(diào)用 Hook。
了解React18哪些內(nèi)容
react-dom/client中的createRoot
自動批量處理 Automatic Batching
transition
Suspense
SuspenseList
useId
useSyncExternalStore
useInsertionEffect?
推薦閱讀:

面試題庫推薦



三、最后
希望本文對小伙伴們尋找新機會有所幫助~加油
歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進階~

