1.React Hooks帶來了什么便利?
React Hooks是React16.8版本中引入的新特性,它帶來了許多便利。
-
更簡單的狀態管理
使用useState Hook可以在函數組件中方便地管理狀態,避免了使用類組件時需要繼承React.Component的繁瑣操作。 -
避免使用類組件:函數式組件的書寫方式更加簡單、直觀,避免了類組件中this指針的混亂問題。
-
更少的重復代碼:使用useEffect Hook可以方便地實現數據獲取、DOM操作等副作用相關的操作,避免了在不同的生命周期函數中重復編寫相似的代碼。
-
更好的代碼復用:自定義Hook可以將一些可復用的邏輯封裝起來,方便在不同的組件中復用。
-
更好的邏輯分離:使用useContext、useReducer和自定義Hook等可以幫助將邏輯分離到獨立的模塊中,提高代碼的可維護性和可擴展性。
總之,React Hooks帶來了更加簡單、直觀、高效的編程方式,可以讓開發者更加專注于組件的邏輯實現。使得React的函數組件也具備了類組件的一些特性。
2. 列舉幾個常見的 Hook?
-
useState Hook:用于在函數組件中管理狀態,可以通過調用useState Hook來聲明一個狀態變量和更新函數,例如:
const [count, setCount] = useState(0);
-
useEffect Hook:用于在函數組件中處理副作用,可以傳入一個回調函數和一個依賴數組,例如:
useEffect(() => {// 處理副作用 }, [dependency]);
-
useContext Hook:用于在函數組件中訪問React Context中的值,例如:
3.1. 在MyContext.js中定義MyContext上下文對象:
import { createContext } from 'react';const MyContext = createContext();
export default MyContext;
3.2. 在App.js中使用MyContext.Provider包裹Child組件,傳遞要共享的數據
import MyContext from './MyContext';
import Child from './Child';function App() {const data = 'hello world';return (<MyContext.Provider value={data}><Child /></MyContext.Provider>);
}
3.3 在Child.js中使用useContext函數獲取到MyContext傳遞的值:
import MyContext from './MyContext';function Child() {const data = useContext(MyContext);return <h1>{data}</h1>;
}export default Child;
- useReducer Hook:用于在函數組件中管理復雜狀態,可以將一個reducer函數和初始狀態傳入useReducer Hook,返回一個狀態和派發更新的函數,例如:
import React, { useReducer } from 'react';const initialState = { count: 0 };function reducer(state, action) {switch (action.type) {case 'increment':return { count: state.count + 1 };case 'decrement':return { count: state.count - 1 };default:throw new Error();}
}function Counter() {const [state, dispatch] = useReducer(reducer, initialState);return (<div>Count: {state.count}<button onClick={() => dispatch({ type: 'increment' })}>+</button><button onClick={() => dispatch({ type: 'decrement' })}>-</button></div>);
}
- useCallback Hook:用于在函數組件中緩存回調函數,避免因為每次渲染都重新創建回調函數導致子組件重復渲染,例如:
import React, { useState, useCallback } from 'react';function MyComponent() {const [count, setCount] = useState(0);const handleClick = useCallback(() => {setCount(count + 1);}, [count]);return (<div><p>Count: {count}</p><button onClick={handleClick}>Increment Count</button></div>);
}
在這個示例中,我們使用了 useCallback Hook 緩存了 handleClick 函數。handleClick 會在點擊按鈕時被調用,并將 count 的值加 1。我們將 count 作為依賴數組傳入 useCallback 中,確保每次 count 發生變化時,handleClick 函數都會被更新。
使用 useCallback Hook 可以避免在每次渲染時都創建新的函數引用,從而提高性能。這對于傳遞給子組件的回調函數尤其有用,確保子組件不會不必要地重新渲染。同時,也可以使用 useMemo Hook 緩存計算結果,從而進一步提高性能。
- useMemo Hook:提供的一個 Hook,用于緩存計算結果,避免在每次渲染時都重新計算,從而提高性能。它接收一個計算函數和一個依賴數組作為參數,返回緩存的計算結果。
例如:
import React, { useMemo, useState } from 'react';function MyComponent() {const [count, setCount] = useState(0);const expensiveCalculation = useMemo(() => {console.log('Calculating...');let result = 0;for (let i = 0; i < count * 1000000; i++) {result += i;}return result;}, [count]);return (<div><p>Count: {count}</p><p>Expensive Calculation: {expensiveCalculation}</p><button onClick={() => setCount(count + 1)}>Increment Count</button></div>);
}
- useRef: 獲取組件的真實節點或存儲一些不常更新的數據,這些數據不受組件重新渲染影響。
- 獲取真實節點
const ref = useRef(null)
<div ref ={ref} > </div>
// 設置真實節點屬性 .current為固定用法
ref.current.style.color = 'red'
- 存儲數據
const ref_obj = useRef({name:'icy',age:23
})
// 改變存儲的數據
ref.obj.current.name = 'icy-godlike'
還有其他常用的Hook函數,如useImperativeHandle、useLayoutEffect等,開發者可以根據具體的需求選擇不同的Hook函數。
3. 使用React Hooks有什么優勢?
簡化組件的復雜度:React Hooks可以幫助組件更加簡潔明了,避免類組件中的一些復雜的生命周期函數。
更容易共享邏輯:React Hooks可以將組件中的狀態和邏輯提取出來,通過自定義Hook在不同的組件中進行共享。
更容易測試:React Hooks可以將狀態和邏輯的處理分離,使得測試變得更容易。
更好的性能:React Hooks可以避免類組件中因為響應式更新造成的額外渲染,從而提高應用的性能。
更快的開發速度:React Hooks能夠幫助開發者更快地構建出復雜的UI組件,從而提高開發效率。
4. 簡單介紹下React中diff算法?
在 React 中,當組件的狀態發生變化時,React 會重新渲染組件。為了提高渲染效率,React 會使用一種叫做 diff 算法(也稱為協調算法)來計算出哪些部分需要更新,哪些部分不需要更新。
簡單來說,diff 算法就是比較兩棵樹的差異,然后將差異應用到真實的 DOM 樹上,從而實現只更新必要的部分,避免全量更新。
React 中的 diff 算法具體實現如下:
對比兩棵樹的根節點,如果類型不同,則直接替換整個節點及其子節點,不再進行進一步比較;如果類型相同,則進入下一步。
對比節點的屬性,如果發生變化,則更新節點的屬性;如果沒有變化,則進入下一步。
對比子節點,React 使用一種叫做 key 的機制來判斷哪些節點需要更新,哪些節點需要刪除,哪些節點需要新增。如果節點的 key 相同,則認為是同一節點,進行進一步比較;如果節點的 key 不同,則直接替換整個節點及其子節點,不再進行進一步比較。
對比完成后,React 會根據計算出的差異,生成一份更新計劃(也稱為變更記錄),然后根據這份更新計劃,進行 DOM 操作,將變更應用到真實的 DOM 樹上。
通過使用 diff 算法,React 可以避免全量更新,從而提高渲染效率,使得 React 在大規模數據渲染的場景下仍然能夠保持流暢的性能。
5. React中,能否直接將 props 的值復制給 state?
可以,但是應該避免這種寫法:
constructor(props) {super(props);// 不要這樣做this.state = { color: props.color };
}
只有在初始化組件狀態時才能這樣做。在組件的生命周期中,props 的值是不會自動更新到 state 中的,因此如果需要在組件運行時更新 state,需要使用setState()方法。
6. React Hooks當中的useEffect是如何區分生命周期鉤子的
useEffect 鉤子函數可以接收兩個參數,第一個參數是一個函數,稱為 effect 函數,第二個參數是一個數組,稱為依賴項數組。
在 React 中,每一個組件都有不同的生命周期鉤子,例如 componentDidMount,componentDidUpdate,componentWillUnmount 等。useEffect 鉤子函數可以在一個函數中處理這些不同的生命周期鉤子。
當使用 useEffect 鉤子函數時,React 會自動根據參數來判斷當前需要使用哪個生命周期鉤子。
當依賴項數組為空時,useEffect 鉤子函數的行為類似于 componentDidMount 和 componentWillUnmount 的結合體,即只在組件掛載時執行一次,并在組件卸載時執行清理操作。
當依賴項數組不為空時,useEffect 鉤子函數的行為類似于 componentDidUpdate,即在組件掛載時執行一次,之后每次依賴項發生變化時都會執行一次,最后在組件卸載時執行清理操作。
因此,useEffect 鉤子函數的行為會根據傳入的依賴項數組的變化而變化,從而實現了不同的生命周期鉤子的功能。
useEffect(() => {console.log('mounted'); // 依賴數組為[]時,componentDidMount | 依賴數組不為空,且發生變化時 componentDidUpdatereturn () => {console.log('willUnmount'); // 依賴數組為[]時,componentWillUnmount }}, [source]);
7. 為什么不能用數組下標來作為react組件中的key?
在 React 中,key 屬性用于標識組件在列表中的順序,并用于優化組件的重新渲染。key 必須是唯一的,并且在組件列表中始終保持不變。
不能使用數組下標作為 key 屬性是因為數組下標與組件的實際內容沒有關系。例如,如果你在一個列表中刪除第一個元素,React 將重新生成列表中所有元素的 key,因為數組下標已經改變,導致 React 需要重新渲染所有組件。
此外,使用數組下標作為 key 屬性可能會導致錯誤的渲染結果。例如,如果你有兩個組件,它們的 key 屬性分別為 0 和 1,并且你想交換它們的順序,則交換它們在數組中的位置可能會導致 key 屬性被錯誤地匹配到錯誤的組件上,從而導致渲染錯誤。
因此,為了避免以上問題,key 屬性應該是與組件的實際內容相關的唯一標識符,例如組件在數據庫或服務端的 ID。
8. React Fiber是什么?
React Fiber 是 React 16 中引入的新的協調引擎。React Fiber 是一種新的、可中斷的調度算法,可以更好地支持漸進式渲染、處理大型組件樹以及優化代碼的可維護性。
React Fiber 的主要目標是提高 React 應用程序的性能和交互性。具體來說,React Fiber 可以:
實現異步渲染,將渲染任務分割成多個小任務,并調度它們的執行順序,以提高應用程序的響應性和流暢性。
支持可中斷的渲染,允許 React 在執行長時間的計算或等待異步數據時中斷渲染,并在后臺執行其他任務,以避免應用程序的停頓或卡頓。
支持漸進式渲染,允許 React 逐步地將組件樹渲染到屏幕上,從而更快地響應用戶輸入,并提高應用程序的交互性。
改進錯誤處理和調試功能,使得開發人員可以更輕松地調試和修復錯誤。
總之,React Fiber 是 React 16 中的一個重要特性,可以顯著提高 React 應用程序的性能、交互性和可維護性。
9. 虛擬DOM一定更快嗎?
虛擬 DOM 并不一定比直接操作 DOM 更快,因為在生成虛擬 DOM 的過程中也需要進行計算和操作。但是,使用虛擬 DOM 有以下幾個優點:
減少不必要的操作:由于 React 使用了虛擬 DOM,可以將多次 DOM 操作合并為一次,從而減少了不必要的 DOM 操作,提高了性能。
避免重復渲染:React 會比較新舊虛擬 DOM 樹的差異,只更新差異部分對應的真實 DOM,避免了重復渲染整個組件,提高了性能。
跨平臺支持:虛擬 DOM 是 React 在 Web、Native 等多個平臺都可以使用的重要原因之一。
當然,在某些場景下,直接操作 DOM 也可能比使用虛擬 DOM 更快,例如非常簡單的組件或需要頻繁更新的組件。但總的來說,在大多數情況下,使用虛擬 DOM 可以提高 React 應用程序的性能和可維護性。
10. 不同版本的 React 都做過哪些優化?
React 16 引入了 Fiber 架構,以提高應用程序的性能和交互性。
React 16.3 引入了新的 Context API,以更方便地共享數據和狀態。
React 16.6 引入了 memo 和 lazy 函數,以更有效地處理組件性能和代碼分離。
React 16.8 引入了 Hooks,以更方便地處理組件之間的狀態和邏輯。
React 17 引入了新的事件系統以及一些其他的優化,例如對異常的處理方式等等。
除此之外,React 還對一些其他的細節進行了優化,例如對組件生命周期方法的改進、對 Virtual DOM 的優化、對 React Native 的優化等等。
React渲染頁面的兩個階段:
調度階段(reconciliation):在這個階段 React 會更新數據生成新的 Virtual DOM,然后通過Diff算法,快速找出需要更新的元素,放到更新隊列中去,得到新的更新隊列。
渲染階段(commit):這個階段 React 會遍歷更新隊列,將其所有的變更一次性更新到DOM上。
React 15 架構
React15架構可以分為兩層:
Reconciler(協調器)—— 負責找出變化的組件;
Renderer(渲染器)—— 負責將變化的組件渲染到頁面上;
在React15及以前,Reconciler采用遞歸的方式創建虛擬DOM,遞歸過程是不能中斷的。如果組件樹的層級很深,遞歸會占用線程很多時間,遞歸更新時間超過了16ms,用戶交互就會卡頓。
為了解決這個問題,React16將遞歸的無法中斷的更新重構為異步的可中斷更新,由于曾經用于遞歸的虛擬DOM數據結構已經無法滿足需要。于是,全新的Fiber架構應運而生。
React 16 架構
為了解決同步更新長時間占用線程導致頁面卡頓的問題,也為了探索運行時優化的更多可能,React開始重構并一直持續至今。重構的目標是實現Concurrent Mode(并發模式)。
從v15到v16,React團隊花了兩年時間將源碼架構中的Stack Reconciler重構為Fiber Reconciler。
React16架構可以分為三層:
Scheduler(調度器)—— 調度任務的優先級,高優任務優先進入Reconciler;
Reconciler(協調器)—— 負責找出變化的組件:更新工作從遞歸變成了可以中斷的循環過程。Reconciler內部采用了Fiber的架構;
Renderer(渲染器)—— 負責將變化的組件渲染到頁面上。
React 17 優化
React16的expirationTimes模型只能區分是否>=expirationTimes決定節點是否更新。React17的lanes模型可以選定一個更新區間,并且動態的向區間中增減優先級,可以處理更細粒度的更新。
Lane用二進制位表示任務的優先級,方便優先級的計算(位運算),不同優先級占用不同位置的“賽道”,而且存在批的概念,優先級越低,“賽道”越多。高優先級打斷低優先級,新建的任務需要賦予什么優先級等問題都是Lane所要解決的問題。
Concurrent Mode的目的是實現一套可中斷/恢復的更新機制。其由兩部分組成:
一套協程架構:Fiber Reconciler
基于協程架構的啟發式更新算法:控制協程架構工作方式的算法