位置: IT常識 - 正文
推薦整理分享2023版最新最全React面試題,希望有所幫助,僅作參考,歡迎閱讀內(nèi)容。
文章相關(guān)熱門搜索詞:,內(nèi)容如對您有幫助,希望把文章鏈接給更多的朋友!
React 作為前端使用最多的框架,必然是面試的重點。我們接下來主要從 React 的使用方式、源碼層面和周邊生態(tài)(如 redux, react-router 等)等幾個方便來進行總結(jié)。
1. 使用方式上這里主要考察的是,在開發(fā)使用過程中,對 React 框架的了解,如 hook 的不同調(diào)用方式得到的結(jié)果、函數(shù)組件中的 useState 和類組件的 state 的區(qū)別等等。
props 的變動,是否會引起 state hook 中數(shù)據(jù)的變動?React 組件的 props 變動,會讓組件重新執(zhí)行,但并不會引起 state 的值的變動。state 值的變動,只能由 setState() 來觸發(fā)。因此若想在 props 變動時,重置 state 的數(shù)據(jù),需要監(jiān)聽 props 的變動,如:
const App = props => { const [count, setCount] = useState(0); // 監(jiān)聽 props 的變化,重置 count 的值 useEffect(() => { setCount(0); }, [props]); return <div onClick={() => setCount(count + 1)}>{count}</div>;};React18 有哪些新變化?React 的更新都是漸進式的更新,在 React18 中啟用的新特性,其實在 v17 中(甚至更早)就埋下了。
并發(fā)渲染機制:根據(jù)用戶的設(shè)備性能和網(wǎng)速對渲染過程進行適當?shù)恼{(diào)整, 保證 React 應(yīng)用在長時間的渲染過程中依舊保持可交互性,避免頁面出現(xiàn)卡頓或無響應(yīng)的情況,從而提升用戶體驗。新的創(chuàng)建方式:現(xiàn)在是要先通過createRoot()創(chuàng)建一個 root 節(jié)點,然后該 root 節(jié)點來調(diào)用render()方法;自動批處理優(yōu)化:批處理: React 將多個狀態(tài)更新分組到一個重新渲染中以獲得更好的性能。(將多次 setstate 事件合并);在 v18 之前只在事件處理函數(shù)中實現(xiàn)了批處理,在 v18 中所有更新都將自動批處理,包括 promise 鏈、setTimeout 等異步代碼以及原生事件處理函數(shù);startTransition:主動降低優(yōu)先級。比如「搜索引擎的關(guān)鍵詞聯(lián)想」,用戶在輸入框中的輸入希望是實時的,而聯(lián)想詞匯可以稍稍延遲一會兒。我們可以用 startTransition 來降低聯(lián)想詞匯更新的優(yōu)先級;useId:主要用于 SSR 服務(wù)端渲染的場景,方便在服務(wù)端渲染和客戶端渲染時,產(chǎn)生唯一的 id;并發(fā)模式是如何執(zhí)行的?React 中的并發(fā),并不是指同一時刻同時在做多件事情。因為 js 本身就是單線程的(同一時間只能執(zhí)行一件事情),而且還要跟 UI 渲染競爭主線程。若一個很耗時的任務(wù)占據(jù)了線程,那么后續(xù)的執(zhí)行內(nèi)容都會被阻塞。為了避免這種情況,React 就利用 fiber 結(jié)構(gòu)和時間切片的機制,將一個大任務(wù)分解成多個小任務(wù),然后按照任務(wù)的優(yōu)先級和線程的占用情況,對任務(wù)進行調(diào)度。
對于每個更新,為其分配一個優(yōu)先級 lane,用于區(qū)分其緊急程度。通過 Fiber 結(jié)構(gòu)將不緊急的更新拆分成多段更新,并通過宏任務(wù)的方式將其合理分配到瀏覽器的幀當中。這樣就能使得緊急任務(wù)能夠插入進來。高優(yōu)先級的更新會打斷低優(yōu)先級的更新,等高優(yōu)先級更新完成后,再開始低優(yōu)先級更新。什么是受控組件和非受控組件?我們稍微了解下什么是受控組件和非受控組件:
受控組件:只能通過 React 修改數(shù)據(jù)或狀態(tài)的組件,就是受控組件;非受控組件:與受控組件相反,如 input, textarea, select, checkbox 等組件,本身控件自己就能控制數(shù)據(jù)和狀態(tài)的變更,而且 React 是不知道這些變更的;那么如何將非受控組件改為受控組件呢?那就是把上面的這些純 html 組件數(shù)據(jù)或狀態(tài)的變更,交給 React 來操作:
const App = () => { const [value, setValue] = useState(''); const [checked, setChecked] = useState(false); return ( <> <input value={value} onInput={event => setValue(event.target.value)} /> <input type="checkbox" checked={checked} onChange={event => setChecked(event.target.checked)} /> </> );};上面代碼中,輸入框和 checkbox 的變化,均是經(jīng)過了 React 來操作的,在數(shù)據(jù)變更時,React 是能夠知道的。
高階組件(HOC)?高階組件?高階組件通過包裹(wrapped)被傳入的 React 組件,經(jīng)過一系列處理,最終返回一個相對增強(enhanced)的 React 組件,供其他組件調(diào)用。
作用:
復(fù)用邏輯:高階組件更像是一個加工 react 組件的工廠,批量對原有組件進行加工,包裝處理。我們可以根據(jù)業(yè)務(wù)需求定制化專屬的 HOC,這樣可以解決復(fù)用邏輯。強化 props:這個是 HOC 最常用的用法之一,高階組件返回的組件,可以劫持上一層傳過來的 props,然后混入新的 props,來增強組件的功能。代表作 react-router 中的 withRouter。賦能組件:HOC 有一項獨特的特性,就是可以給被 HOC 包裹的業(yè)務(wù)組件,提供一些拓展功能,比如說額外的生命周期,額外的事件,但是這種 HOC,可能需要和業(yè)務(wù)組件緊密結(jié)合。典型案例 react-keepalive-router 中的 keepaliveLifeCycle 就是通過 HOC 方式,給業(yè)務(wù)組件增加了額外的生命周期??刂其秩?#xff1a;劫持渲染是 hoc 一個特性,在 wrapComponent 包裝組件中,可以對原來的組件,進行條件渲染,節(jié)流渲染,懶加載等功能,后面會詳細講解,典型代表做 react-redux 中 connect 和 dva 中 dynamic 組件懶加載。參考:react 進階」一文吃透 React 高階組件(HOC)
React 中為什么要使用 Hook?官方網(wǎng)站有介紹該原因:使用 Hook 的動機。
這里我們簡要的提煉下:
在組件之間復(fù)用狀態(tài)邏輯很難:在類組件中,可能需要 render props 和 高階組件等方式,但會形成“嵌套地域”;而使用 Hook,則可以從組件中提取狀態(tài)邏輯,是的這些邏輯可以單獨測試并復(fù)用;復(fù)雜組件變得難以理解:在類組件中,每個生命周期常常包含一些不相關(guān)的邏輯。如不同的執(zhí)行邏輯,都要放在componentDidMount中執(zhí)行和獲取數(shù)據(jù),而之后需在?componentWillUnmount?中清除;但在函數(shù)組件中,不同的邏輯可以放在不同的 Hook 中執(zhí)行,互不干擾;難以理解的 class:類組件中,充斥著各種對?this?的使用,如?this.onClick.bind(this),this.state,this.setState()?等,同時,class 不能很好的壓縮,并且會使熱重載出現(xiàn)不穩(wěn)定的情況;Hook 使你在非 class 的情況下可以使用更多的 React 特性;useCallback 和 useMemo 的使用場景useCallback 和 useMemo 可以用來緩存函數(shù)和變量,提高性能,減少資源浪費。但并不是所有的函數(shù)和變量都需要用這兩者來實現(xiàn),他也有對應(yīng)的使用場景。
我們知道 useCallback 可以緩存函數(shù)體,在依賴項沒有變化時,前后兩次渲染時,使用的函數(shù)體是一樣的。它的使用場景是:
函數(shù)作為其他 hook 的依賴項時(如在 useEffect()中);函數(shù)作為 React.memo()(或 shouldComponentUpdate )中的組件的 props;主要是為了避免重新生成的函數(shù),會導(dǎo)致其他 hook 或組件的不必要刷新。
useMemo 用來緩存函數(shù)執(zhí)行的結(jié)果。如每次渲染時都要執(zhí)行一段很復(fù)雜的運算,或者一個變量需要依賴另一個變量的運算結(jié)果,就都可以使用 useMemo()。
參考文章:React18 源碼解析之 useCallback 和 useMemo。
useState 的傳參方式,有什么區(qū)別?useState()的傳參有兩種方式:純數(shù)據(jù)和回調(diào)函數(shù)。這兩者在初始化時,除了傳入方式不同,沒啥區(qū)別。但在調(diào)用時,不同的調(diào)用方式和所在環(huán)境,輸出的結(jié)果也是不一樣的。
如:
const App = () => { const [count, setCount] = useState(0); const handleParamClick = () => { setCount(count + 1); setCount(count + 1); setCount(count + 1); }; const handleCbClick = () => { setCount(count => count + 1); setCount(count => count + 1); setCount(count => count + 1); };};上面的兩種傳入方式,最后得到的 count 結(jié)果是不一樣的。為什么呢?因為在以數(shù)據(jù)的格式傳參時,這 3 個使用的是同一個 count 變量,數(shù)值是一樣的。相當于setCount(0 + 1),調(diào)用了 3 次;但以回調(diào)函數(shù)的傳參方式,React 則一般地會直接該回調(diào)函數(shù),然后得到最新結(jié)果并存儲到 React 內(nèi)部,下次使用時就是最新的了。注意:這個最新值是保存在 React 內(nèi)部的,外部的 count 并不會馬上更新,只有在下次渲染后才會更新。
還有,在定時器中,兩者得到的結(jié)果也是不一樣的:
const App = () => { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { setCount(count + 1); }, 500); return () => clearInterval(timer); }, []); useEffect(() => { const timer = setInterval(() => { setCount(count => count + 1); }, 500); return () => clearInterval(timer); }, []);};為什么在本地開發(fā)時,組件會渲染兩次?issues#2
在 React.StrictMode 模式下,如果用了 useState,usesMemo,useReducer 之類的 Hook,React 會故意渲染兩次,為的就是將一些不容易發(fā)現(xiàn)的錯誤容易暴露出來,同時 React.StrictMode 在正式環(huán)境中不會重復(fù)渲染。
也就是在測試環(huán)境的嚴格模式下,才會渲染兩次。
如何實現(xiàn)組件的懶加載從 16.6.0 開始,React 提供了 lazy 和 Suspense 來實現(xiàn)懶加載。
import React, { lazy, Suspense } from 'react';const OtherComponent = lazy(() => import('./OtherComponent'));function MyComponent() { return ( <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> );}屬性fallback表示在加載組件前,渲染的內(nèi)容。
如何實現(xiàn)一個定時器的 hook若在定時器內(nèi)直接使用 React 的代碼,可能會收到意想不到的結(jié)果。如我們想實現(xiàn)一個每 1 秒加 1 的定時器:
const App = () => { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(timer); }, []); return <div className="App">{count}</div>;};可以看到,coun 從 0 變成 1 以后,就再也不變了。為什么會這樣?
盡管由于定時器的存在,組件始終會一直重新渲染,但定時器的回調(diào)函數(shù)是掛載期間定義的,所以它的閉包永遠是對掛載時 Counter 作用域的引用,故 count 永遠不會超過 1。
針對這個單一的 hook 調(diào)用,還比較好解決,例如可以監(jiān)聽 count 的變化,或者通過 useState 的 callback 傳參方式。
const App = () => { const [count, setCount] = useState(0); // 監(jiān)聽 count 的變化,不過這里將定時器改成了 setTimeout // 即使不修改,setInterval()的timer也會在每次渲染時被清除掉, // 然后重新啟動一個新的定時器 useEffect(() => { const timer = setTimeout(() => { setCount(count + 1); }, 1000); return () => clearInterval(timer); }, [count]); // 以回調(diào)的方式 // 回調(diào)的方式,會計算回調(diào)的結(jié)果,然后作為下次更新的初始值 // 詳情可見: https://www.xiabingbao.com/post/react/react-usestate-rn5bc0.html#5.+updateReducer useEffect(() => { const timer = setInterval(() => { setCount(count => count + 1); }, 1000); return () => clearInterval(timer); }, []); return <div className="App">{count}</div>;};當然還有別的方式也可以實現(xiàn) count 的更新。那要是調(diào)用更多的 hook,或者更復(fù)雜的代碼,該怎么辦呢?這里我們可以封裝一個新的 hook 來使用:
// https://overreacted.io/zh-hans/making-setinterval-declarative-with-react-hooks/const useInterval = (callback: () => void, delay: number | null): void => { const savedCallback = useRef(callback); useEffect(() => { savedCallback.current = callback; }); useEffect(() => { function tick() { savedCallback.current(); } if (delay !== null) { const id = setInterval(tick, delay); return () => clearInterval(id); } }, [delay]);};useEffect()的清除機制是什么?在什么時候執(zhí)行?useEffect(callback)的回調(diào)函數(shù)里,若有返回的函數(shù),這是 effect 可選的清除機制。每個 effect 都可以返回一個清除函數(shù)。
React 何時清除 effect? React 會在組件卸載的時候執(zhí)行清除操作。同時,若組件產(chǎn)生了更新,會先執(zhí)行上一個的清除函數(shù),然后再運行下一個 effect。如
// 運行第一個 effect// 產(chǎn)生更新時// 清除上一個 effect// 運行下一個 effect// 產(chǎn)生更新時// 清除上一個 effect// 運行下一個 effect// 組件卸載時// 清除最后一個 effect參考:為什么每次更新的時候都要運行 Effect
2. 源碼層面上這部分考察的就更有深度一些了,多多少少得了解一些源碼,才能明白其中的緣由,比如 React 的 diff 對比,循環(huán)中 key 的作用等。
虛擬 dom 有什么優(yōu)點?真實 dom 和虛擬 dom,誰快?Virtual DOM 是以對象的方式來描述真實 dom 對象的,那么在做一些 update 的時候,可以在內(nèi)存中進行數(shù)據(jù)比對,減少對真實 dom 的操作減少瀏覽器重排重繪的次數(shù),減少瀏覽器的壓力,提高程序的性能,并且因為 diff 算法的差異比較,記錄了差異部分,那么在開發(fā)中就會幫助程序員減少對差異部分心智負擔,提高了開發(fā)效率。
虛擬 dom 好多這么多,渲染速度上是不是比直接操作真實 dom 快呢?并不是。虛擬 dom 增加了一層內(nèi)存運算,然后才操作真實 dom,將數(shù)據(jù)渲染到頁面上。渲染上肯定會慢上一些。雖然虛擬 dom 的缺點在初始化時增加了內(nèi)存運算,增加了首頁的渲染時間,但是運算時間是以毫秒級別或微秒級別算出的,對用戶體驗影響并不是很大。
什么是合成事件,與原生事件有什么區(qū)別?React 中所有觸發(fā)的事件,都是自己在其內(nèi)部封裝了一套事件機制。目的是為了實現(xiàn)全瀏覽器的一致性,抹平不同瀏覽器之間的差異性。
在 React17 之前,React 是把事件委托在 document 上的,React17 及以后版本不再把事件委托在 document 上,而是委托在掛載的容器上。React 合成事件采用的是事件冒泡機制,當在某具體元素上觸發(fā)事件時,等冒泡到頂部被掛載事件的那個元素時,才會真正地執(zhí)行事件。
而原生事件,當某具體元素觸發(fā)事件時,會立刻執(zhí)行該事件。因此若要比較事件觸發(fā)的先后時機時,原生事件會先執(zhí)行,React 合成事件會后執(zhí)行。
key 的作用是什么?key 幫助 React 識別哪些元素改變了,比如被添加或刪除。因此你應(yīng)當給數(shù)組中的每一個元素賦予一個確定的標識。
當組件刷新時,React 內(nèi)部會根據(jù) key 和元素的 type,來對比元素是否發(fā)生了變化。若選做 key 的數(shù)據(jù)有問題,可能會在更新的過程中產(chǎn)生異常。
參考:React18 源碼解析之 key 的作用。
多次執(zhí)行 useState(),會觸發(fā)多次更新嗎?在 React18 中,無論是多個 useState()的 hook,還是操作(dispatch)多次的數(shù)據(jù)。只要他們在同一優(yōu)先級,React 就會將他們合并到一起操作,最后再更新數(shù)據(jù)。
這是基于 React18 的批處理機制。React 將多個狀態(tài)更新分組到一個重新渲染中以獲得更好的性能。(將多次 setstate 事件合并);在 v18 之前只在事件處理函數(shù)中實現(xiàn)了批處理,在 v18 中所有更新都將自動批處理,包括 promise 鏈、setTimeout 等異步代碼以及原生事件處理函數(shù);
參考:多次調(diào)用 useState() 中的 dispatch 方法,會產(chǎn)生多次渲染嗎?
useState()的 state 是否可以直接修改?是否可以引起組件渲染?首先
上一篇:【GitHub Copilot X】基于GPT-4的全新智能編程助手
網(wǎng)站地圖: 企業(yè)信息 工商信息 財稅知識 網(wǎng)絡(luò)常識 編程技術(shù)
友情鏈接: 武漢網(wǎng)站建設(shè)