1.談談你對HOC的理解
定義: 高階組件是一個接收組件作為參數并返回新組件的函數,用于復用組件邏輯,遵循純函數特性(無副作用,輸出僅依賴輸入)。
- 組合性:可嵌套使用多個 HOC。
HOC(Higher-Order Component,高階組件)是 React 中的一種設計模式,它本質上是一個函數,接受一個組件作為參數,返回一個新的組件。這個新組件通常會添加一些額外的功能或者修改原有組件的行為,而不直接修改原組件的代碼。
屬性代理props,state,反向繼承(生命周期劫持,方法重寫)
主要特點:
- 增強組件功能:HOC 允許你在不修改原組件的情況下,給它添加額外的邏輯或功能。比如:權限控制、數據獲取、日志記錄、條件渲染等。
- 純函數:HOC 只是一個函數,它不改變原組件的實例,而是返回一個新的組件。傳入的原組件將成為 HOC 的輸入,返回的新組件是帶有附加功能的組件。
- 組合性:多個 HOC 可以被組合在一起,形成一個強大的功能組合。這使得 React 的組件變得更加靈活和可復用。
常見應用場景:
- 狀態共享:多個組件之間可以通過 HOC 共享相同的狀態邏輯。
- 權限控制:HOC 可以用于根據用戶權限來渲染不同的 UI。
- 生命周期管理:在 HOC 中添加鉤子函數,可以封裝組件的生命周期操作。
- 代碼復用:例如,處理 API 請求的 HOC 可以應用于多個組件,而不需要每個組件都重復編寫相同的請求邏輯。
優缺點:
優點:
- 增強可復用性:將常見的邏輯封裝成 HOC,可以在多個地方復用。
- 邏輯與視圖分離:HOC 使得 UI 和邏輯功能分離,提高代碼的可維護性和可測試性。
- 組合性強:HOC 可以通過組合多個不同的功能來增強組件的功能。
缺點:
- 命名沖突:HOC 可能會給組件的屬性命名帶來沖突,尤其是在 HOC 之間傳遞 props 時。
- 復雜性增加:如果過度使用 HOC,可能會導致組件樹變得復雜,難以調試和維護。
- 性能問題:每次通過 HOC 包裝一個組件時,都會返回一個新的組件,這可能導致不必要的渲染,影響性能。
HOC詳細點擊查看
2.談談你對React Fiber的理解
先概述它的基本概念,Fiber是什么、為什么提出Fiber,主要特點是什么,解決什么問題、以及它如何影響 React 的工作方式。然后,我會深入講解它的核心特性和實現原理,最后給出一個應用場景,展示我對它的實際理解。
React Fiber 是 React 內部的一個新的調度引擎,旨在優化 React 的渲染過程,提高渲染的可控性和性能,尤其是在處理復雜 UI 和高頻率更新時。目標是使React能夠更好地處理大型應用和動態更
1. 為什么需要 Fiber?
JavaScript引擎和頁面渲染引擎兩個線程是互斥的,當其中一個線程執行時,另一個線程只能掛起等待 如果 JavaScript線程長時間地占用了主線程,那么渲染層面的更新就不得不長時間地等待,界面長時間不更新,會導致頁面響應度變差,用戶可能會感覺到卡頓 也就是早期版本中使用的叫做“棧式調度”的渲染算法。這個算法是同步的,也就是說,React 渲染一個組件時,會阻塞后續的工作,直到渲染過程完成。當React在渲染組件時,從開始到渲染完成整個過程是一氣呵成的,無法中斷 如果組件較大,那么js線程會一直執行,然后等到整棵VDOM樹計算完成后,才會交給渲染的線程 這就會導致一些用戶交互、動畫等任務無法立即得到處理,導致卡頓的情況,
JavaScript執行Javascript引擎和頁面渲染在同一個線程中,GUI渲染和Javascript執行兩者之間是互斥的 工 如果某個任務執行時間過長,瀏覽器就會推遲渲染。這就引入了Fiber
Fiber 作為 React 的新架構,主要是為了引入“增量渲染”,使得渲染過程可以被中斷并分片執行,允許 React 在渲染期間執行其他更緊急的任務,從而改善性能。
2. Fiber 解決了什么問題?
- 異步渲染:Fiber 使得 React 渲染過程可以被分割成多個小任務,任務之間可以被中斷和重新調度。這樣,當 React 正在渲染時,它可以暫停當前的渲染工作,去處理一些更重要的任務(比如用戶輸入、動畫等)。
- 優先級調度:通過 Fiber,React 能夠為不同的渲染任務設置優先級。例如,用戶輸入和動畫可以擁有更高的優先級,而不那么重要的任務(如更新某個非關鍵的 UI 元素)可以有更低的優先級,從而保證高優先級任務盡快完成。
- 改進的生命周期管理:React 通過 Fiber 可以更好地管理組件生命周期,處理復雜的場景,如 React Suspense 和 Concurrent Mode,這些特性在 Fiber 的架構下能夠得到更好的支持。
3. Fiber 的核心特性和實現:
-
增量渲染(Incremental Rendering) :Fiber 將渲染過程分解為多個小任務,每個任務都可以中斷和恢復。通過這種方式,React 可以在渲染中間進行調度,優先處理高優先級的任務,如用戶交互。
-
優先級調度(Prioritization) :Fiber 引入了優先級的概念。不同的渲染任務可以根據它們的優先級被調度。比如:
- 用戶交互(例如點擊、滾動等)通常是高優先級的。
- 狀態更新或背景渲染可能是低優先級的。
-
Fiber 樹:Fiber 引入了一種新的數據結構——Fiber 樹,它是虛擬 DOM 的一個升級版。每個 Fiber 節點都表示一個組件實例,包含了關于組件的所有信息,包括它的狀態、渲染結果、生命周期方法等。
- Work Units:每個 Fiber 節點對應一個“工作單元”,這些單元可以被異步執行。React 可以把渲染工作分成更小的單位,按需處理。
-
Time Slicing:通過 Fiber,React 可以將大任務切割成多個小任務,在渲染的過程中“切片”時間,讓瀏覽器有機會處理其他任務,比如用戶輸入、動畫等,從而避免界面卡頓。
4. Fiber 與 React 之前版本的區別:
- 同步 vs 異步:在 React Fiber 之前,React 的渲染過程是同步的。也就是說,組件渲染是阻塞式的,直到整個渲染完成。在 Fiber 中,渲染過程被拆解成多個小任務,可以異步執行。
- 改進的生命周期:Fiber 引入了新的生命周期方法,特別是針對異步渲染的生命周期方法(如
getDerivedStateFromProps
和getSnapshotBeforeUpdate
),這些方法有助于提升組件的性能和響應性。 - 并發渲染:Fiber 使得 React 能夠支持并發渲染(Concurrent Rendering)。這意味著 React 可以在多個任務之間切換,優先處理用戶交互、動畫等高優先級任務,降低長時間渲染對用戶體驗的影響。
5. Fiber 在實際應用中的優勢:
- 改善復雜動畫:對于需要頻繁更新的動畫或交互式 UI,Fiber 通過異步渲染和優先級調度可以避免動畫卡頓,提升流暢度。
- React Suspense:Fiber 是 React Suspense 功能的基礎。它允許 React 在數據加載時“暫停”渲染,等數據準備好后再繼續渲染,提升了數據驅動應用的響應速度和流暢性。
- 并發模式(Concurrent Mode) :Fiber 為并發模式奠定了基礎,使得 React 可以同時渲染多個版本的 UI,進一步提升性能和用戶體驗。
總結:
React Fiber 是 React 渲染引擎的一次重大升級,通過引入異步渲染、優先級調度和增量渲染,極大提升了 React 的性能和靈活性。它為未來的 React 特性(如并發模式和 Suspense)提供了基礎,同時也優化了復雜 UI 更新和高頻交互的性能。雖然 Fiber 的實現較為復雜,但它為 React 提供了更強大的能力,尤其是在需要精細控制渲染過程的場景中。
具體fiber原理見:https://blog.csdn.net/qq_34645412/article/details/145886426?spm=1001.2014.3001.5501
3.說說對React的理解?有哪些特性
React 是一個用于構建用戶界面的 JavaScript 庫,主要特點包括:
- 組件化,可組合和嵌套:React 將 UI 劃分為獨立的、可復用的組件,每個組件可以有自己的狀態和生命周期。組件化的結構讓代碼更具可維護性和可復用性。
- 虛擬 DOM:React 通過虛擬 DOM 來優化性能,減少對真實 DOM 的直接操作。每次狀態更新,React 會先在虛擬 DOM 中計算差異,然后高效地更新實際 DOM。
- 單向數據流:React 使用單向數據流,父組件通過 props 向子組件傳遞數據,子組件不能直接修改父組件的狀態,確保數據流向清晰,管理更簡單。
- JSX:JSX 是 React 使用的語法擴展,它讓開發者能夠在 JavaScript 中直接寫 HTML 結構,提高了代碼的可讀性和開發效率。
- 聲明式編程:React采用聲明范式,可以輕松描述應用。開發者只需描述UI應該是什么樣子,React會負責實際渲染工作
- Hooks:React 16.8 引入的 Hooks 允許函數組件管理狀態和副作用,簡化了類組件中復雜的生命周期管理。
- React Router 和 Context:React 通過
React Router
實現單頁面應用的路由功能,通過React Context
提供跨組件的數據傳遞。
這些特性使得 React 在構建高效、可維護的用戶界面時非常強大,特別是在構建大型應用時,可以大大提升開發效率和應用性能。
4.說說你對React的state和props有什么區別
突出 state 和 props 的區別
面試官,state
和 props
都是 React 中用于管理和傳遞數據的方式,但它們有一些重要的區別:
-
state
(狀態) :state
是組件內部管理的數據,它決定了組件的可變狀態。- 組件可以通過
this.setState
(類組件)或者useState
(函數組件)來更新state
,從而觸發組件重新渲染。 - 每個組件有自己的
state
,它是可變的,因此state
主要用于存儲需要隨時間變化的數據,如用戶輸入、交互狀態等。
-
props
(屬性) :props
是父組件傳遞給子組件的數據,它是只讀的,子組件不能修改自己的props
。props
用于組件間的數據傳遞和共享,是組件之間的通信方式。props
是不可變的,父組件通過更新props
來控制子組件的數據。
關鍵區別:
- 來源:
state
來自組件內部,props
來自父組件。 - 可變性:
state
是可變的,props
是只讀的。 - 用途:
state
用于組件內部的數據管理,props
用于組件間的數據傳遞。
5.說說你對React的super和super(props)有什么區別
在 React 中,super
和 super(props)
都是與類組件的構造函數相關的,但是它們有細微的區別。
-
super
:super
是調用父類的構造函數。在 React 中,所有的組件類都繼承自React.Component
或React.PureComponent
,因此在定義構造函數時,我們需要調用super()
來初始化父類。- 如果沒有調用
super()
,子類的構造函數就無法正確執行,會導致錯誤。
class MyComponent extends React.Component {constructor() {super(); // 調用父類構造函數this.state = { count: 0 };} }
-
super(props)
:super(props)
不僅調用父類的構造函數,還將父組件傳遞的props
傳遞給React.Component
的構造函數。- React 需要通過
props
初始化組件的狀態或其他操作,因此如果我們想在構造函數中使用this.props
,就必須調用super(props)
。
class MyComponent extends React.Component {constructor(props) {super(props); // 調用父類構造函數并傳遞 propsthis.state = { count: 0 };} }
關鍵區別:
super()
:僅僅調用父類的構造函數,不傳遞props
。super(props)
:調用父類的構造函數,并將父組件傳遞的props
傳遞給父類,這樣子類的構造函數中就可以訪問this.props
。
在使用 React.Component
或 React.PureComponent
時,如果希望在構造函數中訪問 this.props
,應該使用 super(props)
。
6.說說你對react中類組件和函數組件的理解,有什么區別?
在React中,類組件和函數組件是兩種主要的組件形式,它們有以下區別:
類組件
- 定義方式:
- 類組件是基于ES6的類語法定義的,需要繼承自
React.Component
。
- 生命周期方法:
- 類組件可以使用React提供的各種生命周期方法,如
componentDidMount
、componentDidUpdate
和componentWillUnmount
等。
- 狀態管理:
- 類組件有自己的狀態(
this.state
),可以通過this.setState()
方法來更新狀態。
- this關鍵字:
- 類組件中需要使用
this
關鍵字來訪問組件的屬性和方法。
- 性能優化:
- 可以使用
shouldComponentUpdate
生命周期方法來進行性能優化,避免不必要的渲染。
- 代碼復雜性:
- 類組件的代碼通常比函數組件更復雜,尤其是在處理多個生命周期方法和狀態更新時。
函數組件
- 定義方式:
- 函數組件是一個簡單的JavaScript函數,接收
props
作為參數并返回React元素。
- Hooks支持:
- 自React 16.8起,函數組件可以使用Hooks(如
useState
、useEffect
等)來管理狀態和副作用。
- 狀態管理:
- 使用
useState
Hook可以在函數組件中添加和管理狀態。
- 簡潔性:
- 函數組件通常更簡潔,易于理解和維護。
- 性能優化:
- React團隊為函數組件引入了
React.memo
高階組件來進行性能優化,避免不必要的渲染。
- 代碼簡潔性:
- 函數組件的代碼通常更加簡潔,尤其是在使用Hooks之后,可以避免類組件中的一些樣板代碼。
總結
- 類組件適合那些需要使用復雜生命周期方法或者需要在多個生命周期方法中維護狀態的場景。
- 函數組件隨著Hooks的引入,已經變得非常強大,可以處理大多數場景,包括狀態管理和副作用處理。函數組件通常更簡潔、易于測試和維護。
隨著React的發展,函數組件和Hooks已經成為主流,許多新的特性和優化都是圍繞它們展開的。因此,現代React開發中,推薦優先使用函數組件和Hooks。
7.說說你對react中受控組件和非受控組件的理解?應用場景
面試官,在 React 中,受控組件和非受控組件主要的區別在于數據的控制和管理方式。
1. 受控組件(Controlled Components) :
-
定義:受控組件是指那些通過 React 的
state
來管理其值的組件。組件的值由父組件的狀態來控制,用戶的輸入通過事件處理程序更新組件的state
,從而觸發重新渲染。 -
實現:在受控組件中,表單元素(如
<input>
、<textarea>
、<select>
等)的值由組件的state
控制,onChange
事件用來更新state
,確保 React 控制表單元素的值。function ControlledInput() {const [value, setValue] = useState('');const handleChange = (e) => {setValue(e.target.value);};return (<input type="text" value={value} onChange={handleChange} />); }
-
特點:
- React 完全控制組件的狀態和行為。
- 可以方便地進行表單驗證、動態顯示錯誤信息等。
- 更易于調試,因其數據是受控的。
2. 非受控組件(Uncontrolled Components) :
-
定義:非受控組件是指那些不直接通過 React 的
state
來控制其值的組件。相反,組件的值由 DOM 本身管理,而 React 通過ref
來訪問表單元素的值。 -
實現:在非受控組件中,表單元素的值并不由 React 的狀態管理,而是依賴于 DOM 自身的狀態。你可以通過
ref
獲取該值。function UncontrolledInput() {const inputRef = useRef();const handleSubmit = () => {alert('Input value: ' + inputRef.current.value);};return (<div><input type="text" ref={inputRef} /><button onClick={handleSubmit}>Submit</button></div>); }
-
特點:
- 組件的狀態不由 React 管理,而是由 DOM 自身維護。
- 使用
ref
來直接訪問 DOM 元素。 - 在某些簡單的場景中使用非受控組件可以減少額外的狀態管理,代碼更簡潔。
3. 受控組件與非受控組件的區別:
-
數據來源:
- 受控組件:表單元素的值由 React 的
state
控制。 - 非受控組件:表單元素的值由 DOM 控制,React 通過
ref
來訪問它。
- 受控組件:表單元素的值由 React 的
-
渲染方式:
- 受控組件的每次用戶輸入都會更新 React 的
state
,并觸發組件重新渲染。 - 非受控組件不會每次輸入都觸發渲染,只有在調用
ref
獲取值時才訪問 DOM。
- 受控組件的每次用戶輸入都會更新 React 的
-
靈活性:
- 受控組件能夠實現更多的功能(如表單驗證、動態更新等),更適合復雜的交互。
- 非受控組件適合那些沒有復雜交互邏輯的簡單表單,減少了不必要的狀態管理。
4. 應用場景:
-
受控組件:
- 適用于需要實時跟蹤用戶輸入、進行表單驗證、動態更新 UI 或處理表單數據提交的場景。
- 例如,復雜表單、表單驗證、交互式表單(例如,根據用戶選擇動態渲染其他輸入字段)。
-
非受控組件:
- 適用于簡單的場景,不需要頻繁跟蹤輸入值的變化。例如,簡單的表單或是只在表單提交時才獲取值的場景。
- 例如,表單只需要在提交時獲取數據,或是需要快速開發一個簡單的表單,不關心輸入的實時變化。
總結:
- 受控組件通過 React 的
state
來管理表單元素的值,適合需要高控制和實時反饋的場景。 - 非受控組件使用
ref
直接訪問 DOM 元素的值,適合簡單表單或需要簡化代碼的場景。
8.說說你對react事件綁定的方式有哪些?區別?
如果這是一個面試題,我會簡潔明了地回答 React 中事件綁定的方式,并突出每種方式的區別。以下是我可能的回答:
面試官,在 React 中,事件綁定主要有兩種方式:方法綁定(普通函數)和箭頭函數綁定。它們的區別在于上下文(this
)的綁定方式。以下是詳細解釋:
1. 使用 bind
方法綁定事件
- 定義:使用 JavaScript 的
bind()
方法在構造函數中顯式地綁定事件處理函數的this
上下文。 - 實現:在構造函數中通過
bind
方法將事件處理函數的this
綁定到當前實例。
class MyComponent extends React.Component {constructor(props) {super(props);this.state = { count: 0 };// 在構造函數中綁定事件處理函數this.handleClick = this.handleClick.bind(this);}handleClick() {this.setState({ count: this.state.count + 1 });}render() {return <button onClick={this.handleClick}>Click</button>;}
}
- 優點:事件處理函數中的
this
指向當前組件實例。 - 缺點:每次組件實例化時,
bind
會創建一個新的函數,可能導致性能問題,尤其是在渲染大量組件時。
2. 使用箭頭函數綁定事件
- 定義:在事件處理函數內部使用箭頭函數來自動綁定
this
。 - 實現:箭頭函數不需要顯式綁定
this
,因為箭頭函數會從定義位置(類組件)繼承this
。
class MyComponent extends React.Component {constructor(props) {super(props);this.state = { count: 0 };}handleClick = () => {this.setState({ count: this.state.count + 1 });}render() {return <button onClick={this.handleClick}>Click</button>;}
}
- 優點:代碼簡潔,
this
自動綁定,不需要顯式調用bind
。 - 缺點:每次渲染時都會創建一個新的箭頭函數,可能導致性能問題,尤其是在大量渲染時。
3. 直接傳遞事件處理函數(函數式組件)
- 定義:對于函數組件,直接傳遞事件處理函數即可,
this
不存在,事件處理函數直接引用即可。 - 實現:
function MyComponent() {const [count, setCount] = useState(0);const handleClick = () => {setCount(count + 1);};return <button onClick={handleClick}>Click</button>;
}
- 優點:沒有
this
,代碼更加簡潔和易于理解,且性能更好。 - 缺點:適用于函數組件,對于類組件來說不適用。
4. 直接調用事件處理函數
- 定義:直接在 JSX 中調用事件處理函數,不進行綁定。
- 實現:
class MyComponent extends React.Component {handleClick() {alert('Button clicked!');}render() {return <button onClick={() => this.handleClick()}>Click</button>;}
}
- 優點:代碼簡潔。
- 缺點:每次渲染都會創建一個新的函數,可能會影響性能,特別是在大量渲染時。
5. 傳遞參數給事件處理函數
- 定義:如果需要在事件處理函數中傳遞額外的參數,可以通過箭頭函數或
bind
來傳遞。
class MyComponent extends React.Component {handleClick = (param) => {alert(param);};render() {return <button onClick={() => this.handleClick('Hello!')}>Click</button>;}
}
- 優點:可以靈活地傳遞額外參數。
- 缺點:和直接調用一樣,每次渲染都會創建新的函數。
6. 事件處理函數的優化
React.memo
和useCallback
:對于性能要求較高的組件,可以使用React.memo
(函數組件)和useCallback
(函數組件)來避免不必要的渲染和重新綁定函數。這樣做可以確保在相同的輸入下,事件處理函數保持一致,避免每次渲染都創建新的函數。
總結:可直接回答總結部分
bind
:適用于類組件,在構造函數中綁定this
,但可能引發性能問題。- 箭頭函數:簡潔的寫法,自動綁定
this
,但也可能在渲染時創建新的函數,影響性能。 - 函數式組件:沒有
this
,直接傳遞事件處理函數,性能好且代碼簡潔。但不適用于類組件 - 直接調用:雖然簡潔,但每次渲染都會創建新的函數,性能較差。
- 傳遞參數:通過箭頭函數或
bind
,可以靈活傳遞額外參數,但要注意性能影響。
在實際開發中,通常推薦使用箭頭函數或者函數組件來簡化代碼,盡量避免不必要的性能開銷,尤其是在頻繁渲染的組件中。
9.說說react事件機制?
在React中,事件機制是一個重要的核心概念,它通過合成事件(SyntheticEvent) 和 事件委托(Event Delegation) 實現了跨瀏覽器一致性和性能優化。以下是詳細解析:
1. 合成事件(SyntheticEvent)
React的事件對象是對原生瀏覽器事件的跨瀏覽器封裝,提供了統一的API接口,確保在不同瀏覽器中行為一致。
-
特點:
- 跨瀏覽器兼容:例如,
event.preventDefault()
和event.stopPropagation()
在所有瀏覽器中行為一致。 - 性能優化:事件對象會被復用(事件池機制),在事件回調執行后,事件對象的屬性會被重置為
null
。若需異步訪問事件屬性,需調用event.persist()
。 - 事件類型:支持常見的DOM事件(如
onClick
、onChange
),也支持React特有的合成事件(如onDoubleClick
)。
- 跨瀏覽器兼容:例如,
-
示例:
function handleClick(event) {event.preventDefault(); // 阻止默認行為event.stopPropagation(); // 阻止冒泡console.log(event.target.value); // 訪問事件屬性 }
2. 事件委托(Event Delegation)
React將所有事件委托到根節點(React 17之前是document
,17+是ReactDOM.render
的容器節點),而非直接綁定到具體元素。
-
優勢:
- 內存優化:減少事件監聽器的數量,避免為每個元素單獨綁定事件。
- 動態元素支持:動態添加的子元素無需重新綁定事件。
-
示例:
// React內部自動處理事件委托,開發者只需編寫事件處理函數 <button onClick={handleClick}>Click Me</button>
3. 與原生事件的區別
- 命名方式:React事件使用駝峰命名(如
onClick
),而非原生的小寫(如onclick
)。 - 事件綁定:React通過JSX屬性綁定事件,而非
addEventListener
。 - 默認行為:React中需顯式調用
event.preventDefault()
,而原生事件可通過return false
阻止默認行為。
4. 事件處理中的this
綁定
在類組件中,事件處理函數需注意this
指向問題:
-
解決方法:
- 構造函數中綁定:
this.handleClick = this.handleClick.bind(this)
- 使用箭頭函數:
handleClick = () => { ... }
- 在JSX中直接綁定:
onClick={() => this.handleClick()}
(可能引起性能問題)
- 構造函數中綁定:
5. 事件池(Event Pooling)
-
機制:React會復用合成事件對象以提升性能,事件回調執行后,事件對象的屬性會被置為
null
。 -
異步訪問:若需在異步操作(如
setTimeout
或Promise
)中訪問事件屬性,需調用event.persist()
。function handleClick(event) {event.persist(); // 保留事件對象setTimeout(() => {console.log(event.target.value); // 正常訪問}, 1000); }
6. React 17+ 的變化
- 事件委托容器:事件不再委托到
document
,而是綁定到ReactDOM.render
的根容器節點,避免與外部DOM樹沖突。 - 移除事件池:React 17+ 移除了事件池優化,合成事件對象不再被復用,無需
event.persist()
即可異步訪問屬性。
7. 應用場景與最佳實踐
- 受控組件:使用
onChange
和state
管理表單輸入(實時驗證、提交)。 - 性能敏感場景:非受控組件結合
ref
直接操作DOM,減少渲染次數。 - 阻止冒泡:在嵌套組件中,通過
event.stopPropagation()
控制事件傳播。
總結
React的事件機制通過合成事件和事件委托,在簡化開發的同時保證了性能和跨瀏覽器一致性。理解其核心原理(如this
綁定、事件池、委托策略)能幫助開發者更高效地處理交互邏輯,避免常見陷阱(如異步訪問事件屬性)。隨著React 17+的更新,事件機制進一步簡化,更貼近原生行為。
10.說說react構建組件的方式有哪些?區別是?
在 React 中,構建組件的方式主要有以下幾種,它們各有特點并適用于不同的場景:
1. 類組件(Class Components)
-
定義方式:通過 ES6 的
class
語法定義,繼承自React.Component
。 -
核心特性:
- 使用
this.state
管理狀態。 - 通過生命周期方法(如
componentDidMount
、componentDidUpdate
)處理副作用。 - 需要手動綁定事件處理函數的
this
指向。
- 使用
-
適用場景:
- 需要復雜生命周期控制的場景(如精確管理組件掛載、更新、卸載時的邏輯)。
- 舊代碼庫或需要兼容 React 16.8 之前的版本。
-
示例:
class MyComponent extends React.Component {state = { count: 0 };handleClick = () => {this.setState({ count: this.state.count + 1 });};render() {return <button onClick={this.handleClick}>{this.state.count}</button>;} }
2. 函數組件(Function Components)
-
定義方式:通過普通 JavaScript 函數定義,接受
props
參數并返回 JSX。 -
核心特性:
- 使用 Hooks(如
useState
、useEffect
)管理狀態和副作用。 - 無生命周期方法,但可通過
useEffect
模擬生命周期行為。 - 代碼更簡潔,避免
this
綁定問題。
- 使用 Hooks(如
-
適用場景:
- 新項目或需要簡化代碼結構的場景。
- 需要邏輯復用(通過自定義 Hooks)。
-
示例:
function MyComponent() {const [count, setCount] = useState(0);const handleClick = () => setCount(count + 1);return <button onClick={handleClick}>{count}</button>; }
3. 高階組件(HOC, Higher-Order Components)
-
定義方式:接收一個組件并返回一個新組件的函數。
-
核心特性:
- 用于邏輯復用(如權限校驗、數據獲取)。
- 通過包裝組件注入額外 props 或行為。
-
缺點:
- 嵌套過多可能導致“包裝地獄”(類似
withA(withB(Component))
)。 - 可能引入命名沖突。
- 嵌套過多可能導致“包裝地獄”(類似
-
示例:
function withLogger(WrappedComponent) {return function(props) {useEffect(() => {console.log('Component rendered!');}, []);return <WrappedComponent {...props} />;}; } const EnhancedComponent = withLogger(MyComponent);
4. Render Props 模式
-
定義方式:通過
props
傳遞一個函數,由子組件決定如何渲染內容。 -
核心特性:
- 解決邏輯復用問題,避免 HOC 的嵌套問題。
- 更靈活地共享組件間的邏輯。
-
示例:
<DataProvider render={data => <ChildComponent data={data} />} />
5. 自定義 Hooks
-
定義方式:通過
useXxx
命名的函數封裝可復用邏輯。 -
核心特性:
- 替代 HOC 和 Render Props,更簡潔地實現邏輯復用。
- 可以在函數組件中直接調用。
-
示例:
function useCounter(initialValue) {const [count, setCount] = useState(initialValue);const increment = () => setCount(count + 1);return { count, increment }; } // 使用 function MyComponent() {const { count, increment } = useCounter(0);return <button onClick={increment}>{count}</button>; }
6. 復合組件(Compound Components)
-
定義方式:通過多個關聯組件共同工作,共享隱式狀態(如
<Select>
和<Option>
)。 -
核心特性:
- 通過
React.Context
或React.Children
實現狀態共享。 - 提供更直觀的 API 設計。
- 通過
-
示例:
const Tabs = ({ children }) => {const [activeTab, setActiveTab] = useState(0);return (<div>{React.Children.map(children, (child, index) =>React.cloneElement(child, {isActive: index === activeTab,onClick: () => setActiveTab(index),}))}</div>); };
各方式的核心區別
方式 | 狀態管理 | 邏輯復用 | 代碼簡潔性 | 適用場景 |
---|---|---|---|---|
類組件 | this.state | 繼承、HOC | 較復雜 | 舊項目、復雜生命周期控制 |
函數組件 + Hooks | useState | 自定義 Hooks | 簡潔 | 新項目、邏輯復用 |
HOC | 通過 props 注入 | 包裝組件 | 中等 | 橫切關注點(如鑒權、日志) |
Render Props | 通過函數參數傳遞 | 動態渲染 | 靈活但稍顯冗長 | 需要高度定制的邏輯復用 |
復合組件 | Context 或 Children | 隱式狀態共享 | 直觀 | 關聯組件的組合(如表單) |
總結
- 類組件:適合需要精細控制生命周期的場景,但逐漸被函數組件取代。
- 函數組件 + Hooks:現代 React 的主流方式,代碼簡潔且邏輯復用能力強。
- HOC/Render Props:在 Hooks 出現前用于邏輯復用,現可結合 Hooks 使用。
- 復合組件:適合構建復雜但 API 友好的組件庫(如 Ant Design)。
推薦選擇:
- 新項目優先使用 函數組件 + Hooks。
- 需要兼容舊代碼時,可混合使用類組件和 Hooks。
- 邏輯復用優先用 自定義 Hooks,其次考慮 HOC 或 Render Props。
11.說說react引入css的方式有哪幾種?區別
在 React 中,引入 CSS 的方式多樣,每種方法都有其適用場景和優缺點。以下是常見方案及其核心區別:
1. 內聯樣式(Inline Styles)
-
定義:直接在 JSX 元素中通過
style
屬性編寫樣式,使用 JavaScript 對象表示。 -
特點:
- 作用域:僅作用于當前元素,無全局污染。
- 動態樣式:方便根據 props/state 動態修改樣式。
- 局限性:不支持偽類(如
:hover
)、媒體查詢、動畫等。
-
示例:
const divStyle = { color: 'red', fontSize: '20px' }; function Component() {return <div style={divStyle}>Hello</div>; }
2. 普通 CSS 文件(Plain CSS)
-
定義:通過
import './styles.css'
引入全局 CSS 文件。 -
特點:
- 作用域:全局生效,易引發樣式沖突。
- 維護性:適合傳統項目,但缺乏模塊化。
- 功能支持:完整支持所有 CSS 特性。
-
示例:
/* styles.css */ .my-class { color: red; }
import './styles.css'; function Component() {return <div className="my-class">Hello</div>; }
3. CSS Modules
-
定義:通過構建工具(如 Webpack)將 CSS 文件轉換為局部作用域的模塊。
-
特點:
- 作用域:類名被哈希化,避免全局沖突(如
.my-class_1x2y3
)。 - 維護性:模塊化清晰,適合組件化開發。
- 兼容性:需配置構建工具支持(如
css-loader
)。
- 作用域:類名被哈希化,避免全局沖突(如
-
示例:
/* styles.module.css */ .myClass { color: red; }
import styles from './styles.module.css'; function Component() {return <div className={styles.myClass}>Hello</div>; }
4. CSS-in-JS
-
定義:使用 JavaScript 編寫 CSS,常見庫包括
styled-components
、Emotion
、JSS
。 -
特點:
- 作用域:樣式與組件綁定,無全局污染。
- 動態樣式:支持基于 props/state 的動態樣式。
- 功能支持:完整 CSS 功能(包括偽類、動畫)。
- 性能:運行時生成樣式,可能影響性能(但通常可優化)。
-
示例(styled-components) :
import styled from 'styled-components'; const StyledDiv = styled.div`color: ${props => props.primary ? 'red' : 'blue'};&:hover { font-size: 20px; } `; function Component() {return <StyledDiv primary>Hello</StyledDiv>; }
5. CSS 預處理器(Sass/Less/Stylus)
-
定義:通過 Sass/Less 等預處理器增強 CSS 功能(變量、嵌套、混合等)。
-
特點:
- 功能增強:支持變量、嵌套、函數等高級特性。
- 結合方式:可與 CSS Modules 或普通 CSS 結合使用。
- 構建依賴:需配置預處理器(如
sass-loader
)。
-
示例(Sass + CSS Modules) :
/* styles.module.scss */ $primary-color: red; .myClass { color: $primary-color; }
import styles from './styles.module.scss'; function Component() {return <div className={styles.myClass}>Hello</div>; }
6. Utility-First CSS(Tailwind CSS)
-
定義:通過預定義的實用類(utility classes)快速組合樣式。
-
特點:
- 開發速度:無需手寫 CSS,通過類名組合實現樣式。
- 定制性:支持通過配置文件擴展主題。
- 學習成本:需記憶大量類名,但 IDE 插件可輔助。
-
示例:
function Component() {return (<div className="text-red-500 hover:text-blue-500">Hello</div>); }
7. CSS 框架(如 Bootstrap)
-
定義:使用現成的 UI 框架(如 Bootstrap、Ant Design)提供的樣式。
-
特點:
- 快速開發:直接使用預定義的組件和樣式。
- 定制性:通常支持主題覆蓋,但可能需覆蓋框架默認樣式。
-
示例:
import 'bootstrap/dist/css/bootstrap.min.css'; function Component() {return <button className="btn btn-primary">Submit</button>; }
各方案對比
方式 | 作用域 | 動態樣式 | 功能支持 | 維護性 | 適用場景 |
---|---|---|---|---|---|
內聯樣式 | 組件內 | ? | ?(無偽類/媒體查詢) | 低 | 簡單動態樣式 |
普通 CSS | 全局 | ? | ? | 中 | 傳統項目、小型應用 |
CSS Modules | 局部 | ? | ? | 高 | 組件化開發、避免沖突 |
CSS-in-JS | 局部 | ? | ? | 高 | 復雜動態樣式、主題系統 |
預處理器 | 依賴引入方式 | ? | ?(增強功能) | 高 | 需要高級 CSS 功能 |
Utility-First | 全局/局部 | ?(通過類名) | ? | 中 | 快速開發、減少自定義 CSS |
CSS 框架 | 全局 | ? | ? | 中 | 快速搭建標準化 UI |
總結
- 簡單場景:內聯樣式或普通 CSS。
- 組件化開發:優先選擇 CSS Modules 或 CSS-in-JS(如 styled-components)。
- 動態主題/復雜樣式:CSS-in-JS 是最佳選擇。
- 快速開發:Tailwind CSS 或現成的 CSS 框架。
- 大型項目:結合 CSS Modules + 預處理器(如 Sass)提升可維護性。
根據項目規模、團隊習慣和樣式復雜度靈活選擇,也可混合使用多種方案(如用 Tailwind 處理布局,CSS-in-JS 處理動態主題)。
12.React生命周期有哪些不同的階段?每個階段對應的方法是?
初始化掛載(Mounting) 、更新(Updating) ?和?卸載(Unmounting)
1. 生命周期概述
1.1 React 16.3 之前的生命周期
- 初始化階段
- constructor
- componentWillMount
- render
- componentDidMount
- 更新階段
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- render
- componentDidUpdate
- 卸載階段
- componentWillUnmount
1.2 React 16.3 之后的生命周期
- 初始化階段
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
- 更新階段
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
- 卸載階段
- componentWillUnmount
5.2 生命周期方法與 Hooks 對照表
生命周期方法 | Hooks 實現 |
---|---|
constructor | useState |
componentDidMount | useEffect(() => {}, []) |
componentDidUpdate | useEffect(() => {}, [deps]) |
componentWillUnmount | useEffect(() => { return () => {} }, []) |
shouldComponentUpdate | useMemo, useCallback |
getDerivedStateFromProps | useState + useEffect |
詳細請看鏈接https://tutudev.blog.csdn.net/article/details/144718978 |
13.React組件之間如何進行通信?
在 React 中,組件間的通信方式根據組件關系的不同有多種解決方案。以下是常見場景及對應方法:
一、父子組件通信
1. 父 → 子:通過 props
傳遞數據
-
實現:父組件通過屬性(props)將數據傳遞給子組件。
-
示例:
// 父組件 function Parent() {const data = "Hello";return <Child message={data} />; } // 子組件 function Child({ message }) {return <div>{message}</div>; // 輸出:Hello }
2. 子 → 父:通過回調函數
-
實現:父組件傳遞一個回調函數給子組件,子組件調用該函數傳回數據。
-
示例:
// 父組件 function Parent() {const handleData = (data) => console.log(data);return <Child onSend={handleData} />; } // 子組件 function Child({ onSend }) {return <button onClick={() => onSend("Data from child")}>Send</button>; }
二、兄弟組件通信
1. 通過共同的父組件(狀態提升)
-
實現:將共享狀態提升到父組件,通過 props 和回調函數傳遞。
-
示例:
function Parent() {const [sharedData, setSharedData] = useState("");return (<><ChildA onUpdate={setSharedData} /><ChildB data={sharedData} /></>); }
三、跨層級組件通信
1. Context API
-
實現:通過
React.createContext
創建上下文,Provider
提供數據,useContext
或Consumer
消費數據。 -
示例:
// 創建 Context const MyContext = React.createContext(); // 父組件(Provider) function App() {return (<MyContext.Provider value="Hello"><Grandchild /></MyContext.Provider>); } // 子組件(Consumer) function Grandchild() {const value = useContext(MyContext);return <div>{value}</div>; // 輸出:Hello }
2. 狀態管理庫(Redux、MobX、Zustand)
-
實現:通過全局 Store 管理狀態,組件通過
useSelector
或connect
訂閱狀態。 -
Redux 示例:
// 定義 Action 和 Reducer const increment = () => ({ type: 'INCREMENT' }); const counterReducer = (state = 0, action) => {if (action.type === 'INCREMENT') return state + 1;return state; }; // 組件中派發 Action function Component() {const count = useSelector(state => state);const dispatch = useDispatch();return <button onClick={() => dispatch(increment())}>{count}</button>; }
四、任意組件通信
1. 事件總線(Event Emitter)
-
實現:使用第三方庫(如
events
)或自定義事件系統。 -
示例:
// 創建事件總線 const eventEmitter = new EventEmitter(); // 組件 A:發布事件 function ComponentA() {return <button onClick={() => eventEmitter.emit("event", "Data")}>Send</button>; } // 組件 B:訂閱事件 function ComponentB() {const [data, setData] = useState("");useEffect(() => {eventEmitter.on("event", setData);return () => eventEmitter.off("event", setData);}, []);return <div>{data}</div>; }
2. Refs 和命令式方法
-
實現:父組件通過
ref
調用子組件的方法。 -
示例:
// 子組件(類組件) class Child extends React.Component {method() { console.log("Child method called"); }render() { return <div>Child</div>; } } // 父組件 function Parent() {const childRef = useRef();return (<><Child ref={childRef} /><button onClick={() => childRef.current.method()}>Call Method</button></>); }
五、路由參數傳遞
1. React Router 的 useParams
和 state
-
實現:通過 URL 參數或路由狀態傳遞數據。
-
示例:
// 路由配置 <Route path="/user/:id" component={User} /> // 組件獲取參數 function User() {const { id } = useParams();const location = useLocation();const data = location.state?.data; // 通過 state 傳遞return <div>User ID: {id}, Data: {data}</div>; }
六、Hooks 共享邏輯
1. 自定義 Hooks
-
實現:封裝共享邏輯,多個組件復用同一狀態。
-
示例:
function useCounter(initialValue) {const [count, setCount] = useState(initialValue);const increment = () => setCount(count + 1);return { count, increment }; } // 組件 A 和 B 共享計數器邏輯 function ComponentA() {const { count, increment } = useCounter(0);return <button onClick={increment}>A: {count}</button>; }
總結
場景 | 解決方案 | 適用場景 |
---|---|---|
父子組件 | Props + 回調函數 | 簡單數據傳遞 |
兄弟組件 | 狀態提升 + 共同父組件 | 少量兄弟組件 |
跨層級組件 | Context API、Redux | 主題、用戶信息等全局數據 |
任意組件 | 事件總線、狀態管理庫、消息訂閱發布 | 復雜應用狀態共享 |
路由跳轉傳參 | React Router 參數 | 頁面間數據傳遞 |
邏輯復用 | 自定義 Hooks | 跨組件共享業務邏輯 |
選擇建議:
- 簡單場景優先使用 Props 和 Context。
- 中大型項目使用 Redux/Zustand 管理全局狀態。
- 避免過度使用事件總線,以保持數據流清晰。
詳細請看鏈接https://tutudev.blog.csdn.net/article/details/144770984
14.React中組件過渡動畫如何實現
在 React 中實現組件過渡動畫,通常需要結合 CSS 和 React 的生命周期控制。以下是 5 種主流實現方案,從基礎到進階,附帶代碼示例:
React 提供了多種方式來實現組件的過渡動畫:
- React Transition Group:提供
CSSTransition
和TransitionGroup
,用于實現組件的生命周期過渡動畫。 - CSS 動畫:對于簡單的動畫,直接使用 CSS 的
transition
或animation
即可。 - react-spring:適用于需要更加復雜、物理驅動的動畫效果。
方案對比
方案 | 復雜度 | 控制粒度 | 適用場景 | 學習成本 |
---|---|---|---|---|
CSS 類名切換 | 低 | 粗 | 簡單顯示/隱藏 | 低 |
react-transition-group | 中 | 中 | 通用組件過渡 | 中 |
framer-motion | 高 | 精細 | 復雜交互動畫 | 高 |
自定義 Hooks | 中 | 靈活 | 需要定制邏輯 | 中 |
react-spring | 高 | 精細 | 物理動畫/列表動畫 | 高 |
方案 1:純 CSS 類名切換
適用場景:簡單的顯示/隱藏過渡
原理:通過狀態切換 CSS 類名觸發動畫
// CSS
.fade-enter {opacity: 0;
}
.fade-enter-active {opacity: 1;transition: opacity 300ms;
}
.fade-exit {opacity: 1;
}
.fade-exit-active {opacity: 0;transition: opacity 300ms;
}// React 組件
function App() {const [show, setShow] = useState(false);return (<div><button onClick={() => setShow(!show)}>Toggle</button>{show && (<div className="fade-enter-active">Content with Fade Animation</div>)}</div>);
}
缺點:無法處理卸載動畫(元素會立即消失)
方案 2:react-transition-group 庫
適用場景:完整的進入/離開動畫控制
安裝:npm install react-transition-group
import { CSSTransition } from 'react-transition-group';function App() {const [show, setShow] = useState(false);return (<div><button onClick={() => setShow(!show)}>Toggle</button><CSSTransitionin={show}timeout={300}classNames="fade"unmountOnExit><div className="box">Content with Transition</div></CSSTransition></div>);
}
/* 對應 CSS */
.fade-enter {opacity: 0;
}
.fade-enter-active {opacity: 1;transition: opacity 300ms;
}
.fade-exit {opacity: 1;
}
.fade-exit-active {opacity: 0;transition: opacity 300ms;
}
優勢:自動處理動畫生命周期,支持卸載動畫
方案 3:使用動畫庫(如 framer-motion)
適用場景:復雜動畫序列,物理效果動畫
安裝:npm install framer-motion
import { motion, AnimatePresence } from 'framer-motion';function App() {const [show, setShow] = useState(false);return (<div><button onClick={() => setShow(!show)}>Toggle</button><AnimatePresence>{show && (<motion.divinitial={{ opacity: 0, y: -20 }}animate={{ opacity: 1, y: 0 }}exit={{ opacity: 0, y: 20 }}transition={{ duration: 0.3 }}>Animated Content</motion.div>)}</AnimatePresence></div>);
}
特點:聲明式 API,支持彈簧物理動畫、手勢交互
方案 4:結合 Hooks 的自定義動畫
適用場景:需要精細控制動畫過程
function useFadeAnimation(duration = 300) {const [isVisible, setIsVisible] = useState(false);const [isAnimating, setIsAnimating] = useState(false);const fadeIn = () => {setIsAnimating(true);setIsVisible(true);};const fadeOut = () => {setIsAnimating(true);setTimeout(() => {setIsVisible(false);setIsAnimating(false);}, duration);};return {isVisible,isAnimating,fadeIn,fadeOut,animationStyle: {opacity: isVisible ? 1 : 0,transition: `opacity ${duration}ms ease-out`}};
}// 使用示例
function Component() {const { isVisible, fadeIn, fadeOut, animationStyle } = useFadeAnimation();return (<div><button onClick={isVisible ? fadeOut : fadeIn}>Toggle</button><div style={animationStyle}>Animated Content</div></div>);
}
方案 5:列表動畫(react-spring)
適用場景:動態列表項的添加/刪除動畫
安裝:npm install @react-spring/web
import { useTransition, animated } from '@react-spring/web';function List() {const [items, setItems] = useState([]);const transitions = useTransition(items, {from: { opacity: 0, height: 0 },enter: { opacity: 1, height: 40 },leave: { opacity: 0, height: 0 },});return (<div><button onClick={() => setItems([...items, Date.now()])}>Add Item</button><div className="list">{transitions((style, item) => (<animated.div style={style} onClick={() => setItems(items.filter(i => i !== item))}>Item {item}</animated.div>))}</div></div>);
}
性能優化技巧
-
優先使用 transform 和 opacity
這些屬性不會觸發重排(reflow)// 好 { transform: 'translateX(100px)' } // 避免 { left: '100px' }
-
啟用 GPU 加速
.animated-element {transform: translateZ(0);will-change: transform; }
-
合理使用 requestAnimationFrame
const animate = () => {requestAnimationFrame(() => {// 更新動畫狀態}); };
15.說說你在React項目如何捕獲錯誤的?
在 React 中,我們通常使用錯誤邊界(Error Boundaries)來捕獲運行時的錯誤,并做出相應的處理。錯誤邊界是一個組件,它可以捕獲其子組件樹中的 JavaScript 錯誤、記錄錯誤信息,并展示一個備用 UI。
1. 錯誤邊界(Error Boundaries)
React 提供了一種名為 錯誤邊界 的機制,允許我們在應用中捕獲并處理渲染過程中發生的錯誤。錯誤邊界是一個類組件,必須實現 **componentDidCatch**
生命周期方法,或者在更現代的版本中實現 static **getDerivedStateFromError**
。
- getDerivedStateFromError捕獲錯誤降級渲染頁面UI
- componentDidCatch捕獲錯誤上傳服務器日志
使用錯誤邊界:
- 創建一個錯誤邊界組件:
import React, { Component } from 'react';class ErrorBoundary extends Component {constructor(props) {super(props);this.state = { hasError: false, errorInfo: null };}static getDerivedStateFromError(error) {// 更新狀態以便下次渲染可以顯示備選UIreturn { hasError: true };}componentDidCatch(error, info) {// 可以將錯誤日志上報到服務器console.error("Error caught by Error Boundary:", error, info);this.setState({ errorInfo: info });}render() {if (this.state.hasError) {// 渲染備選的 UIreturn (<div><h1>Something went wrong.</h1><details style={{ whiteSpace: 'pre-wrap' }}>{this.state.errorInfo && this.state.errorInfo.componentStack}</details></div>);}return this.props.children;}
}export default ErrorBoundary;
- 使用錯誤邊界包裹子組件:
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import MyComponent from './MyComponent';function App() {return (<ErrorBoundary><MyComponent /></ErrorBoundary>);
}export default App;
getDerivedStateFromError
:這個靜態方法會在子組件拋出錯誤時被調用,用來更新組件的狀態并決定是否渲染備用 UI。componentDidCatch
:這個生命周期方法用于捕獲錯誤和錯誤的調試信息,可以用來記錄錯誤日志或發送錯誤信息到后臺。
2. 錯誤邊界的應用場景
- 捕獲子組件的運行時錯誤:錯誤邊界主要用于捕獲子組件的渲染過程中、生命周期方法、構造函數等發生的錯誤。它能保證父組件不會受到影響,從而防止整個應用崩潰。
- 展示備用 UI:當捕獲到錯誤時,錯誤邊界會展示一個備選的 UI,可以是一個簡單的提示信息,或是一個錯誤日志供用戶或開發人員參考。
- 避免應用崩潰:如果沒有錯誤邊界,React 會默認讓整個應用崩潰。而使用錯誤邊界后,其他不受影響的部分可以繼續正常渲染,提升用戶體驗。
3. 限制與注意事項
-
只能捕獲渲染階段的錯誤:錯誤邊界僅能捕獲組件樹中渲染、生命周期方法和構造函數中的錯誤,不能捕獲事件處理、異步代碼(如
setTimeout
、fetch
)、服務端渲染等地方的錯誤。 -
事件處理:如果在事件處理程序中發生錯誤,React 并不會自動捕獲,需手動進行錯誤處理(例如使用
try/catch
)。const handleClick = () => {try {// 執行可能出錯的代碼} catch (error) {console.error('Error occurred in event handler:', error);} };
-
不適用于異步代碼:如果你在
componentDidMount
或其他生命周期方法中使用了異步代碼,需要確保對異步操作中的錯誤進行處理。可以使用try/catch
或.catch()
來捕獲異常。async componentDidMount() {try {const data = await fetchData();this.setState({ data });} catch (error) {console.error('Error fetching data:', error);} }
4. 結合 Error Boundaries
和日志記錄
通常,錯誤邊界會與日志服務(如 Sentry、LogRocket)配合使用,以便在捕獲到錯誤時,將錯誤信息發送到服務器進行日志記錄和分析。
componentDidCatch(error, info) {// 假設我們用 Sentry 來捕獲錯誤日志Sentry.captureException(error, { extra: info });this.setState({ errorInfo: info });
}
總結:
- 錯誤邊界 是 React 中專門用于捕獲和處理運行時錯誤的機制。它能夠捕獲子組件的錯誤,避免整個應用崩潰,并顯示一個備用 UI。
getDerivedStateFromError
和componentDidCatch
是錯誤邊界的核心方法,用來處理錯誤、更新組件狀態和記錄錯誤信息。- 錯誤邊界只能捕獲渲染過程中的錯誤,對于事件處理和異步代碼中的錯誤,開發者仍然需要手動處理。
詳細見鏈接
16.說說對React Refs的理解?應用場景
解釋 refs
的概念、如何使用它以及常見的應用場景。以下是我的回答:
React Refs(引用)是 React 用于直接訪問組件實例或 DOM 元素的一種方式。通過 refs,開發者可以繞過 React 的聲明式數據流,直接與 DOM 元素或 React 組件實例進行交互。
1. Refs 的基本概念
在 React 中,ref
是用來引用 DOM 元素或 React 組件實例的一個特殊對象。通常情況下,React 的數據流是單向的,通過狀態(state)來控制視圖,但有些情況下我們需要直接操作 DOM 或調用組件的方法,這時就可以使用 ref
。
使用方式:
-
訪問 DOM 元素: 使用
React.createRef()
創建一個ref
對象,并將其賦值給 React 組件中的元素或組件實例。import React, { Component } from 'react';class MyComponent extends Component {constructor(props) {super(props);// 創建一個 ref 對象this.myInput = React.createRef();}focusInput = () => {// 直接操作 DOM 元素,調用 focus 方法this.myInput.current.focus();};render() {return (<div><input ref={this.myInput} type="text" /><button onClick={this.focusInput}>Focus the input</button></div>);} }export default MyComponent;
這里的
this.myInput
是一個 ref 對象,通過this.myInput.current
來訪問對應的 DOM 元素。 -
訪問類組件實例: 如果
ref
被用來引用一個類組件實例,可以通過ref.current
來訪問該組件的實例,并調用其方法。class ChildComponent extends React.Component {sayHello() {console.log('Hello from Child');}render() {return <div>Child Component</div>;} }class ParentComponent extends React.Component {constructor(props) {super(props);this.childRef = React.createRef();}callChildMethod = () => {// 調用子組件的方法this.childRef.current.sayHello();};render() {return (<div><button onClick={this.callChildMethod}>Call Child Method</button><ChildComponent ref={this.childRef} /></div>);} }export default ParentComponent;
在這個例子中,
this.childRef.current
是ChildComponent
的實例,我們可以通過它來調用sayHello
方法。
2. ref
的應用場景
(1) 訪問和操作 DOM 元素
ref
最常見的應用場景是直接訪問 DOM 元素,尤其是在以下情況下:
- 需要聚焦輸入框(
input
)。 - 需要獲取元素的尺寸或位置(例如,測量一個元素的寬高)。
- 需要實現自定義的滾動行為。
例如,我們可以使用 ref
來聚焦一個輸入框,或是控制動畫時直接訪問 DOM 元素。
(2) 控制子組件的行為
通過 ref
,父組件可以訪問子組件的實例,并調用子組件暴露的方法。這對于處理一些業務邏輯(如觸發子組件的生命周期方法、重置子組件狀態等)非常有用。
(3) 觸發動畫
在 React 中,如果需要直接操作 DOM 元素來觸發動畫(例如使用第三方動畫庫或自定義的動畫),ref
可以幫助我們繞過 React 的渲染機制,直接訪問 DOM 元素進行操作。
(4) 表單驗證與焦點管理
在表單中,可以使用 ref
來獲取對輸入框的引用,從而控制焦點的跳轉或驗證某些輸入項的內容。例如,當用戶提交表單時,可以使用 ref
來自動聚焦到第一個錯誤的輸入框,提供更好的用戶體驗。
(5) 集成第三方庫
在一些情況下,React 組件需要與第三方庫集成,尤其是一些需要直接訪問 DOM 或依賴 DOM 操作的庫。ref
可以讓我們將 React 與這些庫進行有效的連接。例如,集成圖表庫、地圖庫、視頻播放器等。
3. useRef
在函數組件中的使用
在函數組件中,useRef
是 ref
的鉤子版本。useRef
返回一個可以在整個組件生命周期內持久化的對象,這個對象的 current
屬性指向 DOM 元素或組件實例。
示例:使用 useRef
訪問 DOM 元素
import React, { useRef } from 'react';function MyComponent() {const inputRef = useRef(null);const focusInput = () => {inputRef.current.focus();};return (<div><input ref={inputRef} type="text" /><button onClick={focusInput}>Focus the input</button></div>);
}export default MyComponent;
示例:useRef
訪問組件實例
import React, { useRef } from 'react';function ChildComponent() {const sayHello = () => {console.log('Hello from Child');};return <div>Child Component</div>;
}function ParentComponent() {const childRef = useRef();const callChildMethod = () => {childRef.current.sayHello();};return (<div><button onClick={callChildMethod}>Call Child Method</button><ChildComponent ref={childRef} /></div>);
}export default ParentComponent;
4. ref
的限制與注意事項
- 不應過度使用
ref
:ref
是 React 提供的對 DOM 直接操作的功能,但它打破了 React 的聲明式編程方式。通常應該盡量通過 React 的狀態(state)來驅動組件的行為,避免過度依賴ref
。 - 無法觸發重新渲染:改變
ref
的值不會導致組件重新渲染,因此不應該將其用來存儲和管理 React 中的狀態。
總結:
-
React Refs 提供了一種訪問 DOM 元素或組件實例的方式。
-
ref
的常見應用場景:- 操作 DOM 元素,如聚焦輸入框、滾動控制。
- 訪問子組件實例,調用其方法。
- 與第三方庫集成,特別是需要直接操作 DOM 的庫。
- 表單驗證和焦點管理。
- 執行動畫和效果。
-
在函數組件中,我們使用
useRef
鉤子來創建ref
。
17.談談React中的setState的執行機制
在 React 中,setState
是一個用于更新組件狀態的函數。主要在react中是 1.異步更新(如改變狀態后立馬打印值是不會改變的),在原生事件中是同步更新(在原生點擊事件中使用是同步更新的),2.批量更新,React 會將多個 setState
調用合并成一個更新操作,避免了每次狀態改變都導致一次重新渲染。 3.setState
接受一個回調函數作為第二個參數,這個回調函數會在狀態更新并重新渲染完成后調用
1. 異步性
setState
通常是異步的,尤其在事件處理器和生命周期方法中。React 并不會立即更新狀態,而是將其加入到一個更新隊列中,并在下一個渲染周期中批量處理這些更新。- 這種異步執行的機制可以有效減少不必要的重新渲染,提高性能。
2. 批量更新(Batching)
- React 會將多個
setState
調用合并成一個更新操作,避免了每次狀態改變都導致一次重新渲染。例如,如果在同一個事件處理函數內調用了多次setState
,React 會合并這些調用,并在渲染過程中只執行一次更新。 - 批量更新的實現方式依賴于 React 的更新隊列,React 會在事件循環的末尾處理這些更新,并觸發一次新的渲染。
3. 回調函數
setState
接受一個回調函數作為第二個參數,這個回調函數會在狀態更新并重新渲染完成后調用。該回調函數的執行是同步的。- 回調函數的使用場景通常是需要在狀態更新后執行一些額外的操作,如操作 DOM 或執行網絡請求。
this.setState({ count: this.state.count + 1 }, () => {console.log("State updated and component re-rendered");
});
4. 合并狀態(State Merging)
setState
會對更新進行合并。當調用setState
更新狀態時,它并不會完全覆蓋當前狀態,而是對指定的屬性進行合并。- 例如,如果當前狀態是
{ count: 0, name: "John" }
,調用this.setState({ count: 1 })
后,最終的狀態會變成{ count: 1, name: "John" }
,而不是{ count: 1 }
。
5. 函數式更新
- 如果你需要基于前一個狀態來更新狀態,可以傳遞一個函數給
setState
。這個函數接收當前狀態作為參數,并返回更新后的新狀態。這樣做可以確保在多個setState
調用中正確計算狀態。
this.setState((prevState) => ({count: prevState.count + 1
}));
6. 優化
- 在一些場景下,可以通過
shouldComponentUpdate
或React.memo
等機制,避免不必要的渲染。 - 通過
setState
來觸發渲染時,React 會檢查狀態是否真的發生了變化,如果沒有變化,React 會跳過渲染步驟。
總結
setState
在 React 中是一個異步的、批量更新的機制。它通過合并狀態和優化渲染,盡量減少了不必要的 DOM 更新。理解其異步和批量更新的行為,有助于提高 React 應用的性能和正確性。
18.說說React render方法的原理 ?在什么時候會觸發?
React 的 render
方法是其核心機制之一,負責將組件狀態和屬性轉換為用戶界面。它的原理和觸發時機如下:
一、render
方法的原理
-
虛擬 DOM(Virtual DOM)
render
方法生成的是虛擬 DOM(一個輕量級的 JavaScript 對象),而不是直接操作真實 DOM。- 虛擬 DOM 是真實 DOM 的抽象表示,通過
React.createElement
或 JSX 語法生成。
-
協調(Reconciliation)
- 當組件狀態或屬性變化時,React 會調用
render
生成新的虛擬 DOM。 - React 通過 Diff 算法對比新舊虛擬 DOM 的差異,找出需要更新的部分(最小化 DOM 操作)。
- 當組件狀態或屬性變化時,React 會調用
-
批量更新與異步性
- React 會將多個狀態更新合并(批處理),避免頻繁觸發渲染,提升性能。
- 虛擬 DOM 的對比和真實 DOM 的更新是異步的(React 18 默認啟用并發模式)。
二、render
方法的觸發時機
-
初始渲染(Mounting)
- 組件首次掛載到 DOM 時,會觸發
render
方法。
- 組件首次掛載到 DOM 時,會觸發
-
狀態更新(State Change)
- 通過
setState
更新組件狀態時,會觸發重新渲染(除非被shouldComponentUpdate
阻止)。
- 通過
-
屬性變化(Props Change)
- 父組件重新渲染導致子組件的
props
變化時,子組件會重新渲染。
- 父組件重新渲染導致子組件的
-
Context 更新
- 如果組件訂閱了 React Context,當 Context 的值變化時,相關組件會重新渲染。
-
強制更新(Force Update)
- 調用
forceUpdate()
方法會跳過shouldComponentUpdate
,強制觸發render
。
- 調用
三、優化渲染的關鍵點
-
避免不必要的渲染
- 類組件:通過
shouldComponentUpdate
或繼承PureComponent
實現淺比較。 - 函數組件:使用
React.memo
包裹組件,或通過useMemo
/useCallback
緩存值和函數。
- 類組件:通過
-
不可變數據
- 直接修改狀態(如
this.state.obj.key = 1
)不會觸發渲染,必須通過setState
或更新函數(如useState
的 setter)。
- 直接修改狀態(如
-
Key 的合理使用
- 列表渲染時,為元素分配唯一且穩定的
key
,幫助 React 高效識別變化。
- 列表渲染時,為元素分配唯一且穩定的
四、常見問題
- 為什么修改了 state,但頁面沒更新?
可能直接修改了狀態對象(未通過setState
),或未正確觸發渲染(如異步操作未正確處理)。 - 函數組件如何觸發渲染?
函數組件通過useState
的 setter 或useReducer
的 dispatch 觸發更新。 - React 18 并發模式下的渲染
React 會優先處理高優先級更新,可能中斷并重新開始渲染,提升用戶體驗。
總結
即回答以下即可
render
方法是 React 類組件中負責渲染 UI 的核心方法,它在組件的 state
或 props
發生變化,通過虛擬 DOM 和 Diff 算法實現高效更新。觸發條件包括狀態/屬性變化、Context 更新等,合理優化可避免性能瓶頸。
- React 會調用?
render
?生成新的虛擬 DOM。 - React 通過?Diff 算法對比新舊虛擬 DOM 的差異,找出需要更新的部分(最小化 DOM 操作)。
19.說說React 中Real DOM 和 Virtual DOM 的區別?優缺點?
React 中的 Real DOM(真實 DOM)和 Virtual DOM(虛擬 DOM)是兩種不同的 DOM 管理機制,它們的核心區別在于操作方式和性能優化策略。以下是它們的區別、優缺點及適用場景:
一、核心區別
特性 | Real DOM | Virtual DOM |
---|---|---|
本質 | 瀏覽器提供的原生 DOM 對象 | 輕量級的 JavaScript 對象(虛擬表示) |
更新方式 | 直接操作 DOM 節點 | 通過 Diff 算法對比差異后批量更新真實 DOM |
性能開銷 | 直接操作成本高(重排、重繪) | 計算差異的 JS 開銷,但減少真實 DOM 操作 |
更新粒度 | 每次修改觸發完整更新 | 批量合并更新,最小化 DOM 操作 |
跨平臺能力 | 依賴瀏覽器環境 | 可脫離瀏覽器(如 React Native、SSR) |
開發體驗 | 手動管理 DOM,易出錯 | 聲明式編程,自動管理 DOM 更新 |
二、Real DOM 的優缺點
優點
- 直接控制:可直接操作 DOM,適合需要精細控制 DOM 的場景(如復雜動畫)。
- 無中間層:無需維護虛擬 DOM 結構,減少內存占用(適用于極簡單頁面)。
缺點
- 性能瓶頸:頻繁操作 DOM 會導致重排(Reflow)和重繪(Repaint),性能開銷大。
- 開發復雜度:手動管理 DOM 狀態容易出錯(如內存泄漏、事件綁定殘留)。
- 跨平臺限制:依賴瀏覽器環境,難以復用邏輯到其他平臺(如移動端)。
三、Virtual DOM 的優缺點
優點
-
性能優化:
- 通過 Diff 算法對比差異,僅更新必要的 DOM 節點。
- 批量合并更新,減少真實 DOM 操作次數(如 React 的自動批處理)。
-
聲明式編程:
- 開發者只需關注數據狀態(State/Props),無需手動操作 DOM。
- 代碼更易維護,邏輯更清晰。
-
跨平臺能力:
- 虛擬 DOM 是純 JS 對象,可適配不同渲染目標(瀏覽器、移動端、服務端等)。
缺點
-
額外開銷:
- 維護虛擬 DOM 需要內存和計算資源(生成虛擬 DOM、Diff 對比)。
- 在極簡單場景下,可能不如直接操作 DOM 高效。
-
無法完全避免 DOM 操作:
- 最終仍需操作真實 DOM,只是通過中間層優化了流程。
四、為什么 React 選擇 Virtual DOM?
-
平衡性能與開發體驗:
- 在大多數應用場景中,虛擬 DOM 的 Diff 算法能顯著減少 DOM 操作,提升性能。
- 開發者無需手動優化,專注于業務邏輯。
-
跨平臺統一:
- 虛擬 DOM 抽象了渲染層,使 React 可同時支持 Web、Native、SSR 等場景。
-
聲明式 UI 的優勢:
- 通過狀態驅動 UI,簡化復雜交互的實現(如條件渲染、動態列表)。
五、適用場景
-
Virtual DOM 適用場景:
- 中大型應用,頻繁狀態更新(如社交網絡、儀表盤)。
- 需要跨平臺復用邏輯(如 React Native)。
- 團隊協作項目,需統一開發范式。
-
Real DOM 適用場景:
- 極簡單靜態頁面,無需復雜交互。
- 對性能要求極高的局部操作(如 Canvas 動畫、游戲渲染)。
六、性能對比示例
假設更新 10 個 DOM 節點:
-
Real DOM:直接修改 10 次,觸發 10 次重排/重繪。
-
Virtual DOM:
- 生成新虛擬 DOM,對比差異(Diff 算法)。
- 計算最小修改路徑(如僅更新 2 個節點)。
- 批量操作真實 DOM,觸發 1 次重排/重繪。
七、總結
- Virtual DOM 是 React 的核心優化策略,通過犧牲少量 JS 計算時間,換取真實 DOM 操作的大幅減少,從而提升整體性能。
- Real DOM 直接操作更底層,但在復雜場景下難以維護,適合特殊需求。
- 現代前端框架(如 React、Vue)均采用虛擬 DOM 或類似機制,平衡性能與開發效率。
20.React JSX轉換成真實DOM的過程
React 將 JSX 轉換為真實 DOM 的過程是一個分層的、高效的工作流程,涉及多個階段的處理。以下是詳細的轉換過程:
一、JSX 的本質
JSX 是 JavaScript 的語法擴展,本質上是 React.createElement()
的語法糖。它允許開發者以類似 HTML 的語法描述 UI,但最終會被編譯為 JavaScript 對象(即 虛擬 DOM 節點)。
示例:JSX → React.createElement
// JSX 代碼
const element = <div className="title">Hello React</div>;// 編譯后的 JavaScript 代碼
const element = React.createElement("div",{ className: "title" },"Hello React"
);
二、轉換過程的核心步驟
1. JSX 編譯階段(Babel 或 TypeScript)
-
工具:通過 Babel(
@babel/preset-react
)或 TypeScript 將 JSX 轉換為React.createElement()
調用。 -
輸出:生成 React 元素對象(即虛擬 DOM 節點),結構如下:
{type: "div",props: {className: "title",children: "Hello React"},// ...其他內部屬性(如 key、ref) }
2. 構建虛擬 DOM 樹
-
組件渲染:當組件調用
render()
(類組件)或執行函數組件時,遞歸生成嵌套的 React 元素對象樹。 -
示例:
function App() {return (<div><Header /><Content /></div>); }
轉換為:
React.createElement("div", null, React.createElement(Header, null),React.createElement(Content, null) );
3. 協調(Reconciliation)與 Diff 算法
-
觸發時機:當狀態(State)或屬性(Props)變化時,重新生成新的虛擬 DOM 樹。
-
Diff 過程:
- React 對比新舊虛擬 DOM 樹,找出需要更新的部分。
- 使用 高效 Diff 策略(如按層級比較、Key 優化列表更新)。
4. 生成真實 DOM(提交階段)
-
首次渲染(Mounting) :
- React 根據虛擬 DOM 樹創建真實 DOM 節點。
- 通過
ReactDOM.render()
或根組件的createRoot
(React 18+)將 DOM 插入頁面。
-
更新階段(Updating) :
- 根據 Diff 結果,通過 最小化 DOM 操作(如
appendChild
、removeChild
、updateAttribute
)更新真實 DOM。
- 根據 Diff 結果,通過 最小化 DOM 操作(如
三、詳細流程示意圖
JSX 代碼 → Babel 編譯為 React.createElement() → 生成虛擬 DOM 樹(React 元素對象) → 協調(Diff 算法對比新舊樹) → 生成 DOM 更新指令 → 批量操作真實 DOM
四、關鍵角色與 API
-
React.createElement(type, props, children)
- 創建 React 元素對象,描述 UI 結構。
-
ReactDOM.render(element, container)
- 將虛擬 DOM 轉換為真實 DOM 并掛載到容器(如
document.getElementById('root')
)。
- 將虛擬 DOM 轉換為真實 DOM 并掛載到容器(如
-
協調器(Reconciler)
- React 16+ 引入 Fiber 架構,支持可中斷的異步渲染,優化性能。
五、性能優化機制
-
批量更新(Batching)
- 將多次狀態更新合并為一次渲染,減少 DOM 操作次數。
-
Diff 算法優化
- 同級節點比較、唯一 Key 標識列表項,避免不必要的節點重建。
-
惰性加載與按需渲染
- 通過
React.lazy
或Suspense
延遲加載組件,減少初始渲染壓力。
- 通過
六、示例:從 JSX 到真實 DOM 的完整過程
// 1. JSX 代碼
function App() {return <button onClick={() => alert("Clicked!")}>Click Me</button>;
}// 2. 編譯為 React.createElement
React.createElement("button", { onClick: () => alert("Clicked!") }, "Click Me");// 3. 生成 React 元素對象
const buttonElement = {type: "button",props: {onClick: () => alert("Clicked!"),children: "Click Me"}
};// 4. 轉換為真實 DOM(簡化版邏輯)
const domNode = document.createElement(buttonElement.type);
domNode.textContent = buttonElement.props.children;
domNode.addEventListener("click", buttonElement.props.onClick);// 5. 插入頁面
document.getElementById("root").appendChild(domNode);
七、總結
- JSX 是聲明式 UI 的抽象層,通過編譯轉換為虛擬 DOM 對象。
- 虛擬 DOM 是中間表示,幫助 React 高效計算最小化 DOM 操作。
- 協調與 Diff 算法 是 React 性能的核心保障,避免直接操作真實 DOM 的開銷。
- 開發者只需關注數據(State/Props)的變化,React 自動處理 UI 更新,實現高效開發。
21.React中用戶如何根據不同的權限查看不同的頁面
在 React 中實現基于權限的頁面訪問控制,通常需要結合 路由管理、全局狀態 和 權限驗證邏輯。以下是完整的實現方案,包含代碼示例和最佳實踐:
一、核心思路
- 權限數據管理:從后端獲取用戶權限信息,存儲到全局狀態(如 Redux、Context API)。
- 路由動態配置:根據權限生成可訪問的路由列表。
- 路由守衛:在導航前校驗權限,攔截未授權訪問。
- 組件級控制:頁面內根據權限隱藏/顯示元素。
二、關鍵優化點
-
路由懶加載:使用
React.lazy
提升性能const AdminPage = React.lazy(() => import("./pages/AdminPage"));
-
權限持久化:通過
localStorage
或 Cookie 保存登錄狀態 -
接口級鑒權:在 API 請求頭中攜帶 Token,后端驗證權限
-
動態菜單渲染:根據權限生成側邊欄菜單項
const filteredRoutes = routes.filter(route => route.isPublic || (user && route.requiredRoles?.includes(user.role)) );
四、安全注意事項
- 前端權限非絕對安全:必須與后端接口權限校驗結合
- 敏感數據保護:即使隱藏 UI,也要確保 API 返回數據經過過濾
- Token 有效期管理:使用 refreshToken 機制更新訪問令牌
五、完整工作流程
用戶登錄 → 獲取角色權限 → 存儲到全局狀態 → 動態生成路由 → 導航時校驗權限 → 有權限則渲染頁面,否則跳轉登錄/無權限頁
22.React.createClass 與 extends component 的區別是什么
在 React 的發展過程中,React.createClass
和 extends React.Component
是兩種不同的組件定義方式,它們的區別主要體現在語法、功能特性和歷史背景上。以下是詳細對比:
一、核心區別對比表
特性 | React.createClass (已廢棄) | extends React.Component (ES6 類組件) |
---|---|---|
語法 | React 專屬 API | ES6 類語法 |
this 綁定 | 自動綁定方法中的 this | 需手動綁定(或使用箭頭函數/類屬性) |
狀態初始化 | getInitialState() 方法 | 在 constructor 中通過 this.state 初始化 |
默認 Props | getDefaultProps() 方法 | 通過靜態屬性 static defaultProps 定義 |
PropTypes | 內部屬性 propTypes | 靜態屬性 static propTypes |
Mixins 支持 | 支持 | 不支持(改用高階組件/Hooks) |
生命周期方法 | 早期方法(如 componentWillMount ) | 相同方法,但需結合 ES6 類語法 |
React 版本支持 | React <15.5,已廢棄 | React 15.5+ 推薦寫法 |
二、詳細區別解析
1. 語法與定義方式
-
React.createClass
通過工廠函數創建組件,是 React 早期 API:const MyComponent = React.createClass({render() {return <div>{this.props.text}</div>;} });
-
extends React.Component
使用 ES6 類繼承語法:class MyComponent extends React.Component {render() {return <div>{this.props.text}</div>;} }
2. this
綁定問題
-
React.createClass
自動綁定方法中的this
,無需手動處理:const Component = React.createClass({handleClick() {console.log(this); // 正確指向組件實例},render() {return <button onClick={this.handleClick}>Click</button>;} });
-
extends React.Component
方法中的this
默認不綁定,需手動處理(常見方案):-
構造函數中綁定:
class Component extends React.Component {constructor(props) {super(props);this.handleClick = this.handleClick.bind(this);}handleClick() { /* ... */ } }
-
箭頭函數(類屬性):
class Component extends React.Component {handleClick = () => { /* ... */ }; }
-
3. 狀態與 Props 初始化
-
React.createClass
使用特定方法定義初始狀態和默認 Props:const Component = React.createClass({getInitialState() {return { count: 0 };},getDefaultProps() {return { text: "Hello" };} });
-
extends React.Component
在構造函數中初始化狀態,通過靜態屬性定義默認 Props:class Component extends React.Component {constructor(props) {super(props);this.state = { count: 0 };}static defaultProps = { text: "Hello" }; }
4. Mixins 支持
-
React.createClass
支持mixins
實現代碼復用:const LoggerMixin = {componentDidMount() {console.log("Component mounted");} };const Component = React.createClass({mixins: [LoggerMixin],// ... });
-
extends React.Component
不支持 Mixins(ES6 類不支持多重繼承),改用高階組件(HOC)或 Hooks:// 高階組件替代方案 function withLogger(Component) {return class extends React.Component {componentDidMount() {console.log("Component mounted");}render() {return <Component {...this.props} />;}}; }
三、為什么 React.createClass
被廢棄?
- ES6 類語法的普及:ES6 類更符合 JavaScript 標準,減少 React 專屬 API 的學習成本。
this
綁定靈活性:手動綁定讓開發者更清楚代碼行為,避免隱式綁定帶來的困惑。- 性能優化:類組件與現代 React 特性(如 Fiber 架構)更兼容。
- Mixins 的替代方案:Mixins 易導致命名沖突和代碼耦合,HOC 和 Hooks 提供了更清晰的復用模式。
四、遷移建議
-
舊項目遷移:使用
create-react-class
包過渡,或手動改為類組件/函數組件。 -
新項目:直接使用 函數組件 + Hooks(React 16.8+ 推薦):
function MyComponent({ text }) {const [count, setCount] = useState(0);return <div>{text}</div>; }
五、總結
React.createClass
是 React 早期的組件定義方式,已逐漸被淘汰。extends React.Component
是 ES6 類組件的標準寫法,更符合現代 JavaScript 規范。- 函數組件 + Hooks 已成為 React 的主流開發模式,兼具簡潔性和功能性。
23.React事件與普通的html事件有什么區別
React 事件與普通 HTML 事件在實現機制和使用方式上有顯著區別,以下是主要差異及詳細說明:
一、核心區別對比表
特性 | React 事件 | 普通 HTML 事件 |
---|---|---|
事件命名 | 駝峰命名(如 onClick ) | 全小寫(如 onclick ) |
事件綁定 | JSX 中直接綁定函數(如 onClick={fn} ) | HTML 屬性或 addEventListener |
事件對象 | 合成事件(SyntheticEvent ) | 原生 DOM 事件對象 |
事件委托 | 自動委托到根容器(React 17+) | 需手動委托(如 addEventListener ) |
默認行為阻止 | 必須顯式調用 e.preventDefault() | 可通過 return false 或 e.preventDefault() |
this 綁定 | 需手動綁定(類組件)或使用箭頭函數 | 默認指向觸發事件的元素(非嚴格模式) |
性能優化 | 事件池復用,異步需 e.persist() | 無復用機制 |
跨瀏覽器兼容性 | 統一封裝,無需處理瀏覽器差異 | 需手動處理(如 IE 兼容性) |
二、詳細區別解析
1. 事件命名與綁定方式
-
React 事件
-
使用駝峰命名(如
onClick
、onChange
)。 -
在 JSX 中直接綁定函數,無需字符串:
<button onClick={handleClick}>Click</button>
-
-
HTML 事件
-
使用全小寫屬性名(如
onclick
)。 -
通過字符串或
addEventListener
綁定:<button onclick="handleClick()">Click</button> <!-- 或 --> <script>document.querySelector("button").addEventListener("click", handleClick); </script>
-
2. 事件對象(Event Object)
-
React 事件
- 使用 合成事件(SyntheticEvent) ,是對原生事件的跨瀏覽器包裝。
- 通過事件池復用,提升性能(異步訪問需調用
e.persist()
)。 - 統一接口,無需處理瀏覽器差異(如
e.stopPropagation()
在所有瀏覽器中一致)。
const handleClick = (e) => {e.preventDefault(); // 阻止默認行為e.persist(); // 保留事件對象供異步使用 };
-
HTML 事件
- 直接使用原生 DOM 事件對象。
- 需處理瀏覽器兼容性(如 IE 的
window.event
)。
element.onclick = function(e) {e = e || window.event; // 處理 IE 兼容e.stopPropagation(); };
3. 事件委托機制
-
React 事件
- React 17+ 將事件委托到根容器(如
ReactDOM.createRoot()
掛載的節點),而非document
。 - 減少內存占用,支持多 React 版本共存。
- React 17+ 將事件委托到根容器(如
-
HTML 事件
-
需手動實現事件委托:
document.getElementById("parent").addEventListener("click", (e) => {if (e.target.matches("button")) {// 處理子元素點擊} });
-
4. this
綁定問題
-
React 事件(類組件)
-
類組件中需手動綁定
this
,或使用箭頭函數:class Button extends React.Component {handleClick() { console.log(this); } // this 默認未綁定render() {return (<button onClick={this.handleClick.bind(this)}>Bind</button>// 或<button onClick={() => this.handleClick()}>Arrow</button>);} }
-
-
HTML 事件
-
內聯事件處理函數中
this
默認指向元素:<button onclick="console.log(this)">Click</button> <!-- 輸出按鈕元素 -->
-
通過
addEventListener
綁定時,this
默認指向元素:button.addEventListener("click", function() {console.log(this); // 輸出按鈕元素 });
-
5. 默認行為阻止
-
React 事件
- 必須顯式調用
e.preventDefault()
。
const handleSubmit = (e) => {e.preventDefault(); // 阻止表單提交 };
- 必須顯式調用
-
HTML 事件
-
可通過
return false
(僅限屬性綁定)或e.preventDefault()
:<form onsubmit="return false"> <!-- 阻止提交 --> <!-- 或 --> <form onsubmit="handleSubmit(event)"> <script>function handleSubmit(e) {e.preventDefault();} </script>
-
三、React 事件系統的優勢
- 跨瀏覽器一致性
合成事件屏蔽了瀏覽器差異(如event.target
的兼容性處理)。 - 性能優化
事件池機制減少對象創建開銷,提升性能。 - 聲明式綁定
JSX 中直接綁定函數,代碼更簡潔。 - 自動清理
組件卸載時自動解綁事件,避免內存泄漏。
四、示例對比
React 事件處理
function App() {const handleClick = (e) => {e.preventDefault();console.log("React event:", e.nativeEvent);};return <a href="#" onClick={handleClick}>React Link</a>;
}
HTML 事件處理
<a href="#" onclick="handleClick(event)">HTML Link</a>
<script>function handleClick(e) {e.preventDefault();console.log("HTML event:", e);}
</script>
五、總結
- React 事件 是跨瀏覽器封裝的高層抽象,提供一致的開發體驗。
- HTML 事件 直接操作原生 DOM,需處理瀏覽器差異和手動管理。
- 掌握兩者區別有助于避免常見陷阱(如
this
綁定、默認行為處理),并寫出更健壯的 React 代碼。
24.為什么useState要使用數組而非對象?
在 React 的 useState
中,返回數組 [state, setState]
而非對象 { state, setState }
,是出于設計上的靈活性和開發便利性考慮。以下是具體原因和解析:
一、核心原因:解構賦值的靈活性
1. 自由命名變量
-
使用數組解構時,開發者可以自定義變量名,避免對象屬性名的強制約束:
// 數組解構:自由命名 const [count, setCount] = useState(0); const [user, setUser] = useState({ name: "John" });// 對象解構(假設):必須固定屬性名 const { state: count, setState: setCount } = useState(0); // 冗長且不直觀
2. 多狀態場景更簡潔
-
當組件需要多個
useState
時,數組解構更清晰:// 數組解構:直接且無命名沖突 const [count, setCount] = useState(0); const [text, setText] = useState("");// 對象解構(假設):需要重復屬性名 const { state: count, setState: setCount } = useState(0); const { state: text, setState: setText } = useState(""); // 命名沖突!
二、對比:數組 vs 對象返回值的差異
特性 | 數組返回值 | 對象返回值(假設) |
---|---|---|
變量命名 | 自由命名(如 [count, setCount] ) | 固定屬性名(如 { state, setState } ) |
多狀態聲明 | 無命名沖突 | 需重復解構或別名,易沖突 |
代碼簡潔性 | 更簡潔 | 更冗長 |
一致性 | 所有 Hook 統一返回數組(如 useReducer ) | 無統一標準 |
四、示例:數組解構的實際優勢
1. 自定義命名
// 狀態1:計數器
const [count, setCount] = useState(0); // 狀態2:輸入框文本
const [text, setText] = useState("");// 狀態3:用戶數據
const [user, setUser] = useState({ name: "John" });
2. 忽略不需要的值
-
若 Hook 返回對象,無法跳過中間值;數組解構允許占位符:
// 假設 useSomeHook 返回 [a, b, c] const [a, , c] = useSomeHook(); // 忽略第二個值// 若返回對象,需解構所有屬性 const { a, b, c } = useSomeHook(); // 必須處理 b
總結
- 返回數組:為了提供命名自由和多狀態聲明的簡潔性。
- 參數類型自由:
useState
的初始值可以是任意類型(數字、對象、數組等),與返回值形式無關。 - 設計一致性:所有 React Hook 遵循相似的返回值約定,降低學習成本。
因此,const [xxx, setXxx] = useState(0)
中的數組解構是 React 的刻意設計,旨在提升開發體驗和代碼靈活性。
25.React為什么要使用hooks
React 引入 Hooks 的核心目標是解決類組件的設計缺陷,提升代碼的可維護性、邏輯復用性和開發體驗。以下是具體原因和 Hooks 的核心優勢:
總結:為什么使用 Hooks?
- 代碼更簡潔:消除類組件的冗余代碼。
- 邏輯更復用:自定義 Hook 實現高效邏輯共享。
- 開發更高效:函數式編程減少心智負擔。
- 未來更友好:適配 React 新特性,代表未來方向。
一、解決類組件的痛點
1. 邏輯復用困難
-
類組件時代:通過高階組件(HOC)、Render Props 等模式復用邏輯,導致嵌套地獄(Wrapper Hell)和代碼冗余。
-
Hooks 方案:通過自定義 Hook(如
useFetch
、useAuth
)直接復用狀態邏輯,無嵌套、更簡潔。// 自定義 Hook:復用數據請求邏輯 function useFetch(url) {const [data, setData] = useState(null);useEffect(() => {fetch(url).then(res => res.json()).then(setData);}, [url]);return data; }// 在組件中使用 function UserProfile({ userId }) {const user = useFetch(`/api/users/${userId}`);return <div>{user?.name}</div>; }
2. 生命周期方法分散邏輯
-
類組件問題:相關邏輯被拆分到不同生命周期(如
componentDidMount
、componentDidUpdate
),代碼難以維護。class Timer extends React.Component {componentDidMount() {this.timer = setInterval(() => {/* 更新狀態 */}, 1000);}componentWillUnmount() {clearInterval(this.timer);}// 相關邏輯被拆分到多個方法 }
-
Hooks 方案:用
useEffect
合并生命周期邏輯,按功能組織代碼。function Timer() {useEffect(() => {const timer = setInterval(() => {/* 更新狀態 */}, 1000);return () => clearInterval(timer); // 清理邏輯}, []); }
3. this
綁定問題
-
類組件問題:需要手動綁定
this
,或使用箭頭函數,易出錯且代碼冗余。class Button extends React.Component {handleClick() { /* 需要綁定 this */ }render() {return <button onClick={this.handleClick.bind(this)}>Click</button>;} }
-
Hooks 方案:函數組件無
this
,直接使用閉包變量,避免綁定問題。function Button() {const handleClick = () => {/* 直接訪問 props/state */};return <button onClick={handleClick}>Click</button>; }
二、Hooks 的核心優勢
1. 函數式編程范式
- 更簡潔的代碼:函數組件比類組件代碼量更少,結構更清晰。
- 更少的樣板代碼:無需定義
class
、constructor
或render
方法。
2. 邏輯與 UI 解耦
-
關注點分離:通過自定義 Hook 將業務邏輯抽離,UI 組件只負責渲染。
// 邏輯復用層:useCounter function useCounter(initialValue) {const [count, setCount] = useState(initialValue);const increment = () => setCount(c => c + 1);return { count, increment }; }// UI 組件:只負責渲染 function Counter() {const { count, increment } = useCounter(0);return <button onClick={increment}>{count}</button>; }
3. 更好的 TypeScript 支持
- 函數組件 + Hooks 的類型推斷更直觀,避免類組件中
this
類型問題。
4. 性能優化更精細
- 通過
useMemo
、useCallback
精確控制重渲染,避免不必要的計算。
三、Hooks 如何推動 React 生態
- 漸進式遷移:支持類組件與函數組件共存,項目可逐步遷移。
- 社區創新:自定義 Hooks 催生了大量開源庫(如
react-query
、ahooks
)。 - 未來兼容性:React 新特性(如并發模式)優先支持 Hooks。
四、Hooks 的局限性
- 學習曲線:需理解閉包、依賴數組等概念。
- 規則限制:必須遵守 Hooks 的調用順序(不能在條件或循環中使用)。
Hooks 是 React 對“關注點分離”和“代碼復用”的終極答案,讓開發者以更優雅的方式構建可維護的現代應用。
26.在React中如何實現代碼分割
在 React 中實現代碼分割(Code Splitting)是優化應用性能的關鍵手段,能夠減少初始加載時間,提升用戶體驗。以下是 5 種核心方法及詳細實現步驟:
一、使用 React.lazy
+ Suspense
(組件級分割)
適用場景:按需加載非首屏組件(如彈窗、復雜模塊)。
實現步驟:
- 用
React.lazy
動態導入組件。 - 用
Suspense
包裹組件,提供加載狀態。
import React, { Suspense } from 'react';// 動態導入組件(Webpack 自動分割代碼)
const LazyComponent = React.lazy(() => import('./LazyComponent'));function App() {return (<div><Suspense fallback={<div>Loading...</div>}><LazyComponent /> {/* 使用時才加載 */}</Suspense></div>);
}
二、基于路由的代碼分割(路由級分割)
適用場景:SPA 中按路由拆分代碼(如不同頁面)。
實現步驟(以 React Router v6 為例):
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import React, { Suspense } from 'react';const Home = React.lazy(() => import('./routes/Home'));
const About = React.lazy(() => import('./routes/About'));function App() {return (<BrowserRouter><Suspense fallback={<div>Loading Page...</div>}><Routes><Route path="/" element={<Home />} /><Route path="/about" element={<About />} /></Routes></Suspense></BrowserRouter>);
}
三、動態 import()
語法(手動控制加載時機)
適用場景:在事件觸發時加載代碼(如點擊按鈕后加載模塊)。
實現步驟:
function App() {const [module, setModule] = useState(null);const loadModule = async () => {const { default: DynamicModule } = await import('./DynamicModule');setModule(<DynamicModule />);};return (<div><button onClick={loadModule}>Load Module</button>{module}</div>);
}
四、使用第三方庫 @loadable/component
適用場景:更靈活的代碼分割(支持服務端渲染、預加載等)。
實現步驟:
-
安裝庫:
npm install @loadable/component
-
使用
loadable
包裝組件:import loadable from '@loadable/component';const LoadableComponent = loadable(() => import('./Component'), {fallback: <div>Loading...</div>, });function App() {return <LoadableComponent />; }
五、Webpack 配置優化(文件名哈希、分組)
適用場景:精細化控制代碼塊(chunk)生成策略。
配置示例(webpack.config.js
):
module.exports = {output: {filename: '[name].[contenthash].js',chunkFilename: '[name].[contenthash].chunk.js',},optimization: {splitChunks: {chunks: 'all',cacheGroups: {vendors: {test: /[\/]node_modules[\/]/,name: 'vendors',},},},},
};
六、最佳實踐與注意事項
-
優先分割大組件/路由:對性能提升最明顯的部分優先處理。
-
預加載關鍵資源:使用
webpackPrefetch
提前加載未來可能需要的模塊。const Component = React.lazy(() => import(/* webpackPrefetch: true */ './Component' ));
-
錯誤邊界處理:用
ErrorBoundary
捕獲加載失敗。class ErrorBoundary extends React.Component {state = { hasError: false };static getDerivedStateFromError() { return { hasError: true }; }render() {return this.state.hasError ? <div>Error!</div> : this.props.children;} }// 使用 <ErrorBoundary><Suspense fallback="Loading..."><LazyComponent /></Suspense> </ErrorBoundary>
-
避免過度分割:每個分割塊會增加 HTTP 請求,平衡數量與體積。
總結
- 輕量級組件 →
React.lazy
+Suspense
- 路由級分割 → 結合 React Router
- 精細控制 →
@loadable/component
或動態import()
- 打包優化 → Webpack 配置
通過合理應用代碼分割,可顯著提升 React 應用的加載速度與運行時性能。