目錄
一、React
函數組件
Fiber架構
組件重新渲染
組件通信
為什么不能在if中使用hook
useEffect與useLayoutEffect區別
性能優化hooks
受控組件與非受控組件
redux與zustand區別
二、Vue
vue2與vue3區別
生命周期
computed與watch區別
v-if與v-show區別
v-model如何實現
ref與reactive區別
組件通信
key的作用,為什么不建議用index作為key
虛擬dom與diff算法
響應式原理與proxy
對keep-alive的理解
vue與react中diff算法區別
vue與react區別
一、React
函數組件
函數式編程摒棄傳統的面向對象思想,比起指令式編程,函數式編程更強調程序執行的結果,而不是過程。
一個簡單的函數如何實現復雜的功能?
React將HTML、js全部寫在函數組件中,函數組件的返回值就是要渲染的UI,這種設計更注重于js邏輯,而無需像vue一樣提供各種模板語法。
還有就是狀態,需要邏輯狀態改變來觸發頁面的更新。函數組件是一個純函數,通過函數自身的執行去渲染界面,本身無法存留狀態,所以采用hooks的方式為組件提供狀態,既不破壞純函數的特性,又能在狀態變化時讓函數組件重新執行并更新頁面。
還有生命周期,React函數組件摒棄了這個概念,取而代之的是使用useEffect在特定時候讓函數組件重新執行并渲染。
React更愿意在函數上做文章,用函數作為組件的基礎,用純函數簡單的重復執行來代替復雜的視圖更新流程,Hooks也是函數,useState觸發函數組件的執行,并作為純函數執行的不同輸入,useEffect將函數組件中的所有副作用從函數中隔離出去,自定義hooks也是函數。React看起來如此復雜的框架,歸根結底只是通過函數的執行去渲染出視圖而已,這也是React如此簡潔和優雅的原因。
Fiber架構
Fiber 架構實現可中斷的異步渲染,解決了傳統同步渲染阻塞主線程的問題。
Fiber 節點結構
每個組件對應一個 Fiber 節點,構成鏈表樹。節點包含組件類型、狀態、副作用標記,節點指針等。
主要流程
狀態更新觸發組件重新渲染,啟動更新流程。
Render 渲染階段構建 Fiber 樹
利用時間切片將渲染任務拆分為微任務,通過調度器按優先級異步執行,渲染過程可被高優先級任務中斷。
從根節點開始采用深度優先遍歷,遞歸處理每個 Fiber 節點,逐步構建 Fiber 鏈表樹。
diff 算法對比新舊虛擬 DOM,復用 key 和類型相同的節點,標記新增 / 更新 / 刪除等副作用,處理子節點時需要兩次遍歷,第一次遍歷進行匹配和復用,第二次遍歷進行增刪和移動。
當節點沒有子節點時,自底向上收集副作用,將子節點的副作用合并到父節點,最終形成從根節點到子節點的副作用鏈表。
每處理完一個 Fiber 節點,就檢查時間片是否耗盡,如果耗盡就暫停任務并保存進度,等瀏覽器空閑時繼續執行渲染任務。
Commit 提交階段更新真實 DOM
遍歷副作用鏈表,批量執行 DOM 操作,完成視圖渲染。
構建完成的 Fiber 鏈表樹替換當前的 Fiber 鏈表樹,保證視圖連續性。它們通過一個指針相關聯,以便復用節點數據。
時間切片/調度器:設置任務優先級,將任務拆分為微任務,每次執行完微任務后,檢查是否有高優先級任務插隊執行,然后通過 MessageChannel 實現異步調度,利用瀏覽器空閑時間執行。
簡要概述:
Render渲染階段用時間切片將渲染任務拆分為微任務來執行,用深度優先遍歷構建 Fiber 鏈表樹,Diff 算法對比新舊 Fiber 節點,然后標記副作用,自底向上收集副作用形成副作用鏈表,每處理完一個 Fiber 節點就檢查是否有高優先級任務,如果有就暫停渲染并保存進度,等瀏覽器空閑時繼續執行渲染任務。
??Commit提交階段遍歷副作用鏈表,批量執行 DOM 操作,完成視圖渲染。
組件重新渲染
- 狀態更新
- 父組件傳遞來的props變化
- 強制更新ForceUpdate
- 父組件重新渲染
- 上下文Context變化
組件通信
- 父傳子props
- 子傳父回調函數
- 跨層級context API
- 復雜狀態管理使用redux或zustand
- 事件總線event bus可跨層級但破壞單向數據流
- ref使用子組件
為什么不能在if中使用hook
React頂層調用規則,要求每次渲染時hooks的調用順序必須完全一致。
React使用鏈表來管理hooks狀態,每個hook根據它在鏈表中的位置來標識。每次渲染時,react會嚴格記錄hooks的調用順序,組件重新渲染時react會按上一次渲染時hooks的執行順序來匹配對應狀態,如果使用if等語句,可能會導致hooks在鏈表中的順序發生變化,使得react的無法關聯正確的狀態。
useEffect與useLayoutEffect區別
useEffect 的設計是用來執行一段和當前渲染無關的副作用代碼,同時解決了和生命周期相關的一些問題,通過依賴項數組的不同,實現不同的功能,使用return來清理副作用。
useLayoutEffect 執行需同步執行的邏輯,如DOM測量或修改。
useEffect 瀏覽器繪制后??異步執行,不阻塞渲染?;useLayoutEffect:瀏覽器繪制前同步執行,阻塞渲染?
性能優化hooks
useCallback 與 useMemo 緩存
useCallback 緩存一個方法,useMemo 緩存一個計算后的值,當它們的依賴無變化時就不會重新聲明或計算
因為函數組件通過重復執行的方式不停的執行自身渲染視圖,在函數組件內部聲明的方法每次都會被重新聲明,函數組件中寫的邏輯每次都會被重新執行,可能會帶來一定的浪費,極端情況下會影響頁面性能。
React.memo
防止子組件不必要的渲染,當父組件重新渲染時,如果父組件傳遞給子組件的props沒有變化,則子組件不會重新渲染。
但這并不意味著需要濫用這些性能優化方法,應當在真的存在問題的時候再去考慮用它解決。比如真的在函數組件里寫了一個cpu計算超級復雜的邏輯,頻繁執行確實會影響組件的渲染,此時再考慮用 useMemo 去緩存計算結果。
受控組件與非受控組件
React中的組件根據是否受React控制可分為受控組件和非受控組件。
受控組件的值由React組件的狀態控制,由用戶輸入觸發React狀態更新,然后觸發重新渲染并更新DOM;
非受控組件的值不受React控制,由自身管理狀態,通常使用ref來獲取輸入值,由用戶輸入直接更新DOM。
一般推薦使用受控組件來實現表單,數據由React組件負責處理。
redux與zustand區別
Zustand提供了輕量級狀態管理,API簡潔易用;Redux 相比之下是重量級狀態管理,適用于大型或復雜項目,使用起來沒有Zustand簡潔。
狀態模型:?
在Zustand中直接通過設置set來更新狀態; Redux則使用action和reducer來管理狀態。
更新機制:
Redux 的更新機制會觸發全局檢查,依賴開發者用手動優化來避免無效渲染;而 Zustand 僅通知關聯字段的組件更新,實現真正按需渲染。
語法差異:
Redux中是不可變數據,也就是當更新狀態時不直接修改原數據??;而Zustand是可變語法,允許直接修改狀態屬性??,底層庫自動處理不可變狀態轉換。
??
二、Vue
vue2與vue3區別
vue2是選項式API,vue3是組合式API,提供了set up語法糖,還有響應式API:Ref和reactive,解決了vue2選項式API的邏輯碎片化問題。
Vue2使用object.defineProperty來實現對數據對象屬性的getter和setter進行攔截,從而實現雙向數據綁定及響應式更新。
Vue3使用ES6中的proxy來代理整個數據對象,提供完整的響應式機制,能更好的處理數組變化并監聽深層對象屬性變化。
生命周期鉤子做了調整,Vue3中的模板支持多個根節點,且更好的支持ts。
生命周期
Vue2選項式API生命周期鉤子:
beforeCreate → created → beforeMount → mounted → beforeUpdate → updated → beforeUnmount → unmounted
Vue3組合式API在 setup() 中使用生命周期鉤子,前綴為 on,beforeCreate 和 created 被 setup() 替代。
setup是組合式API的入口,用于定義響應式數據、方法和生命周期鉤子,setup中沒有this。
computed與watch區別
computed的用于派生數據,也就是依賴其他值計算的結果,且具有緩存機制,只有依賴值變化,才會重新計算,如果沒有變化則直接返回緩存的值;而且計算屬性本身就是響應式,改變會自動觸發依賴更新。
watch一般用于監聽特定的數據變化并執行副作用,比如異步請求或操作DOM等,支持深度監聽和立即執行,需要顯式監聽。
WatchEffect可以自動追蹤依賴,響應式數據變化時自動重新執行。
副作用
副作用是指在代碼執行過程中,對外部環境產生的可觀察變化,比如修改全局變量、操作DOM、發起網絡請求、定時器操作、打印日志等。
純函數:相同的輸入必有相同的輸出。
v-if與v-show區別
v-if條件渲染,元素不存在于DOM中,切換時觸發組件的創建或銷毀,適用于條件不經常變化或者很少改變的場景。
v-show通過CSS來控制隱藏和顯示,速始終存在于DOM中,適用于需要頻繁切換顯示狀態的場景。
v-if切換成本高,v-show初始渲染成本高。
v-model如何實現
?? v-model 的主要功能是在??表單輸入或??自定義 Vue 組件??上創建??雙向數據綁定??,它同時負責數據的讀取和數據的寫入。
在原生表單元素上的實現原理:
<input v-model="username">
等效于以下寫法:
<input?:value="username"?@input="username = $event.target.value">
在自定義組件上的實現原理:
<custom-input v-model="username">
等效于以下寫法:
<custom-input ?:model-value="username" ?@update:model-value="username = $event">
子組件通過 props 接收 modelValue,觸發 update:modelValue 事件,也可以用 emits 觸發更新。
組件內部實現:
props: ['modelValue'],
emits: ['update:modelValue'],
methods: {handleInput(e) {this.$emit('update:modelValue', e.target.value)}
}
ref與reactive區別
ref可以用于基本數據類型,也可以用于引用類型,而reactive只能用于對象。
使用ref需要加value,如果將ref用于對象,則會自動轉化為reactive包裝的。
如果直接替換一整個reactive對象,會失去響應式;直接解構也會失去響應式,需要使用toRefs。
組件通信
- props/自定義事件,v-model,父子間通信
- provide/inject,跨層級通信
- 事件總線,任意組件間通信
- 使用pinia管理狀態
- 父組件使用子組件的ref
- 路由參數,用于頁面間傳遞數據
- 插槽,普通插槽是父組件傳數據給子組件,作用域插槽是子組件數據傳遞給父組件
- 瀏覽器本地存儲
對keep-alive的理解
keep-alive用于緩存組件實例,避免重復銷毀和渲染。
被包裹的組件會觸發特殊生命周期,切換時執行 deactivated 鉤子,不會被銷毀,再次激活時執行 activated。使用LRU算法(最近最少使用)自動清理緩存。
??可以指定緩存規則和最大緩存實例數。
key的作用,為什么不建議用index作為key
key的作用是幫助diff算法識別節點的身份,通過復用相同key的節點,來優化渲染性能。
如果用index作為key,如果列表中的某項被刪除,則后面的元素index全都會改變,可能導致原本可以復用的節點被重新創建。
如果列表項有輸入框內容,用index會導致狀態錯亂。
正確做法是使用能夠唯一標識的值作為key,比如數據庫ID。
虛擬DOM與diff算法
虛擬DOM是什么?
虛擬DOM是真實DOM的輕量級表示,它通過js的對象數來描述真實DOM的結構和屬性。
為什么要有虛擬DOM?
因為直接操作DOM的代價比較高,涉及到瀏覽器的回流和重繪,頻繁操作會有性能問題。因此虛擬DOM作為緩沖層,通過diff算法對比新舊虛擬DOM樹,找到最小化的更新操作,批量更新真實DOM,減少直接操作次數。
局限性:內存開銷,簡單場景可能性能不足。
Diff算法用來對比新舊虛擬DOM樹的差異,找到最小化的更新操作,應用到真實DOM。
Diff算法僅比較同一層級的節點,不跨層級對比,若節點類型不同,則銷毀舊子樹并創建新子樹。如果類型相同,則會對比并更新屬性,然后遞歸處理子節點。
在列表渲染中,通過key標識節點身份,如果沒有key就會按索引順序對比。
vue與react中diff算法區別
列表對比策略與節點移動優化不同
Vue3采用雙端對比+LIS算法優化,React采用單向遞歸+key機制。
Vue3從列表頭尾同步開始對比,預處理相同節點,對于剩下的亂序節點,通過最長遞歸子序列算法計算最小移動路徑。
React采用兩輪遍歷,首輪按索引順序更新可復用節點,次輪處理剩余節點的新增、刪除或移動,因為fiber節點為單向鏈表。
響應式原理與proxy
vue3的響應式原理基于js的proxy對象來實現。
當使用reactive包裝一個普通對象時,vue會創建一個代理對象Proxy,該對象會攔截所有對原始對象的訪問和修改。
當訪問對象的屬性時,Proxy的get攔截器會被觸發,然后執行依賴收集,將當前正在執行的代碼,也就是副作用函數與該屬性建立聯系。
如果訪問的是對象,vue會遞歸的將其轉換為響應式對象。
當修改對象的屬性時,Proxy的set攔截器會被觸發,vue會檢查新值與原值是否相同,如果不相同vue會觸發更新,通知所有依賴該屬性的副作用函數重新執行。
只有實際被使用的屬性才會建立依賴。
ref將基本值包裝到一個對象中,這個對象有一個.value屬性。
訪問.value的值時會觸發getter,進行依賴收集;修改.value的值時會觸發setter,檢查值是否變化然后觸發更新。
vue與react區別
相同點:都采用組件化,都使用虛擬DOM和diff算法,都實現數據驅動視圖。都有父子組件傳參數據狀態管理和路由等。
核心理念不同
vue的核心理念是降低前端開發門檻,是一個靈活易用的漸進式框架,提供雙向數據綁定。
React的核心思想是聲明是聲明式編程和單向數據流。
組件化差異
Vue將HTML CSS和js寫在同一個文件中,就是.vue文件,使用的是組合式API。
React則把HTML和CSS全部寫進js中,使用.jsx文件,使用的是函數組件。
Diff算法不同
Vue使用雙端對比以及LIS算法,而react是單向遞歸與fiber架構。
Vue內置了很多黑魔法,而react則是社區生態豐富。
Vue上手簡單,React更靈活。