react面試要點(diǎn)

生命周期

  • 初始化階段

  • constructor 構(gòu)造函數(shù)

  • getDefaultProps props默認(rèn)值

  • getInitialState state默認(rèn)值

  • 掛載階段

  • componentWillMount 組件初始化渲染前調(diào)用

  • render 組件渲染

  • componentDidMount組件掛載到 DOM后調(diào)用

  • 更新階段

  • componentWillReceiveProps 組件將要接收新 props前調(diào)用

  • shouldComponentUpdate 組件是否需要更新

  • componentWillUpdate 組件更新前調(diào)用

  • render 組件渲染

  • componentDidUpdate 組件更新后調(diào)用

  • 卸載階段

  • componentWillUnmount 組件卸載前調(diào)用

生命周期

  • 初始化階段

  • constructor 構(gòu)造函數(shù)

  • getDefaultProps props默認(rèn)值

  • getInitialState state默認(rèn)值

  • 掛載階段

  • staticgetDerivedStateFromProps(props,state)

  • render

  • componentDidMount

getDerivedStateFromProps:組件每次被 rerender的時候,包括在組件構(gòu)建之后(虛擬 dom之后,實(shí)際 dom掛載之前),每次獲取新的 propsstate之后;每次接收新的props之后都會返回一個對象作為新的 state,返回null則說明不需要更新 state;配合 componentDidUpdate,可以覆蓋 componentWillReceiveProps的所有用法

  • 更新階段

  • staticgetDerivedStateFromProps(props,state)

  • shouldComponentUpdate

  • render

  • getSnapshotBeforeUpdate(prevProps,prevState)

  • componentDidUpdate

getSnapshotBeforeUpdate:觸發(fā)時間:update發(fā)生的時候,在render之后,在組件dom渲染之前;返回一個值,作為componentDidUpdate的第三個參數(shù);配合componentDidUpdate, 可以覆蓋componentWillUpdate`的所有用法

  • 卸載階段

  • componentWillUnmount

  • 錯誤處理

  • componentDidCatch

React16新的生命周期棄用了 componentWillMount、componentWillReceivePorps,componentWillUpdate新增了 getDerivedStateFromProps、getSnapshotBeforeUpdate來代替棄用的三個鉤子函數(shù)。

React16并沒有刪除這三個鉤子函數(shù),但是不能和新增的鉤子函數(shù)混用,React17將會刪除這三個鉤子函數(shù),新增了對錯誤的處理(componentDidCatch`)

setState是同步的還是異步的?

  • 生命周期和合成事件中

React的生命周期和合成事件中, React仍然處于他的更新機(jī)制中,這時無論調(diào)用多少次 setState,都會不會立即執(zhí)行更新,而是將要更新的·存入 _pendingStateQueue,將要更新的組件存入 dirtyComponent。

當(dāng)上一次更新機(jī)制執(zhí)行完畢,以生命周期為例,所有組件,即最頂層組件 didmount后會將批處理標(biāo)志設(shè)置為 false。這時將取出 dirtyComponent中的組件以及 _pendingStateQueue中的 state進(jìn)行更新。這樣就可以確保組件不會被重新渲染多次。

當(dāng)我們在執(zhí)行 setState后立即去獲取 state,這時是獲取不到更新后的 state的,因?yàn)樘幱?React的批處理機(jī)制中, state被暫存起來,待批處理機(jī)制完成之后,統(tǒng)一進(jìn)行更新。

所以。setState本身并不是異步的,而是 React的批處理機(jī)制給人一種異步的假象。

  • 異步代碼和原生事件中
    當(dāng)我們在異步代碼中調(diào)用 setState時,根據(jù) JavaScript的異步機(jī)制,會將異步代碼先暫存,等所有同步代碼執(zhí)行完畢后在執(zhí)行,這時 React的批處理機(jī)制已經(jīng)走完,處理標(biāo)志設(shè)被設(shè)置為 false,這時再調(diào)用 setState即可立即執(zhí)行更新,拿到更新后的結(jié)果。

在原生事件中調(diào)用 setState并不會出發(fā) React的批處理機(jī)制,所以立即能拿到最新結(jié)果。

  • 最佳實(shí)踐

setState的第二個參數(shù)接收一個函數(shù),該函數(shù)會在 React的批處理機(jī)制完成之后調(diào)用,所以你想在調(diào)用 setState后立即獲取更新后的值,請?jiān)谠摶卣{(diào)函數(shù)中獲取。

為什么有時連續(xù)多次setState只有一次生效?

原因就是 React會批處理機(jī)制中存儲的多個 setState進(jìn)行合并,來看下 React源碼中的 _assign函數(shù),類似于 Objectassign

如果傳入的是對象,很明顯會被合并成一次,所以上面的代碼兩次打印的結(jié)果是相同的

  • 最佳實(shí)踐

React會對多次連續(xù)的 setState進(jìn)行合并,如果你想立即使用上次 setState后的結(jié)果進(jìn)行下一次 setState,可以讓 setState 接收一個函數(shù)而不是一個對象。這個函數(shù)用上一個 state 作為第一個參數(shù),將此次更新被應(yīng)用時的 props 做為第二個參數(shù)。

React如何實(shí)現(xiàn)自己的事件機(jī)制?

React事件并沒有綁定在真實(shí)的 Dom節(jié)點(diǎn)上,而是通過事件代理,在最外層的 document上對事件進(jìn)行統(tǒng)一分發(fā)。

組件掛載、更新時:

  • 通過 lastPropsnextProps判斷是否新增、刪除事件分別調(diào)用事件注冊、卸載方法。

  • 調(diào)用 EventPluginHubenqueuePutListener進(jìn)行事件存儲

  • 獲取 document對象。

  • 根據(jù)事件名稱(如 onClickonCaptureClick)判斷是進(jìn)行冒泡還是捕獲。

  • 判斷是否存在 addEventListener方法,否則使用 attachEvent(兼容IE)。

  • document注冊原生事件回調(diào)為 dispatchEvent(統(tǒng)一的事件分發(fā)機(jī)制)。

事件初始化:

  • EventPluginHub負(fù)責(zé)管理 React合成事件的 callback,它將 callback存儲在 listenerBank中,另外還存儲了負(fù)責(zé)合成事件的 Plugin。

  • 獲取綁定事件的元素的唯一標(biāo)識 key。

  • callback根據(jù)事件類型,元素的唯一標(biāo)識 key存儲在 listenerBank中。

  • listenerBank的結(jié)構(gòu)是: listenerBank[registrationName][key]

觸發(fā)事件時:

  • 觸發(fā) document注冊原生事件的回調(diào) dispatchEvent

  • 獲取到觸發(fā)這個事件最深一級的元素

  • 遍歷這個元素的所有父元素,依次對每一級元素進(jìn)行處理。

  • 構(gòu)造合成事件。

  • 將每一級的合成事件存儲在 eventQueue事件隊(duì)列中。

  • 遍歷 eventQueue。

  • 通過 isPropagationStopped判斷當(dāng)前事件是否執(zhí)行了阻止冒泡方法。

  • 如果阻止了冒泡,停止遍歷,否則通過 executeDispatch執(zhí)行合成事件。

  • 釋放處理完成的事件。

React在自己的合成事件中重寫了 stopPropagation方法,將 isPropagationStopped設(shè)置為 true,然后在遍歷每一級事件的過程中根據(jù)此遍歷判斷是否繼續(xù)執(zhí)行。這就是 React自己實(shí)現(xiàn)的冒泡機(jī)制。

為何React事件要自己綁定this?

在上面提到的事件處理流程中, Reactdocument上進(jìn)行統(tǒng)一的事件分發(fā), dispatchEvent通過循環(huán)調(diào)用所有層級的事件來模擬事件冒泡和捕獲。

React源碼中,當(dāng)具體到某一事件處理函數(shù)將要調(diào)用時,將調(diào)用 invokeGuardedCallback方法。

 function invokeGuardedCallback(name, func, a)  {

   try  {

  func(a);

    }  catch  (x)  {

    if  (caughtError ===  null)  {

  caughtError = x;

  }

  }}

可見,事件處理函數(shù)是直接調(diào)用的,并沒有指定調(diào)用的組件,所以不進(jìn)行手動綁定的情況下直接獲取到的 this是不準(zhǔn)確的,所以我們需要手動將當(dāng)前組件綁定到 this上。

原生事件和React事件的區(qū)別?

  • React 事件使用駝峰命名,而不是全部小寫。

  • 通過 JSX , 你傳遞一個函數(shù)作為事件處理程序,而不是一個字符串。

  • React 中你不能通過返回 false 來阻止默認(rèn)行為。必須明確調(diào)用 preventDefault。

React的合成事件是什么?

React 根據(jù) W3C 規(guī)范定義了每個事件處理函數(shù)的參數(shù),即合成事件。

事件處理程序?qū)鬟f SyntheticEvent 的實(shí)例,這是一個跨瀏覽器原生事件包裝器。它具有與瀏覽器原生事件相同的接口,包括 stopPropagation()preventDefault(),在所有瀏覽器中他們工作方式都相同。

React合成的 SyntheticEvent采用了事件池,這樣做可以大大節(jié)省內(nèi)存,而不會頻繁的創(chuàng)建和銷毀事件對象。

另外,不管在什么瀏覽器環(huán)境下,瀏覽器會將該事件類型統(tǒng)一創(chuàng)建為合成事件,從而達(dá)到了瀏覽器兼容的目的。

React和原生事件的執(zhí)行順序是什么?可以混用嗎?

React的所有事件都通過 document進(jìn)行統(tǒng)一分發(fā)。當(dāng)真實(shí) Dom觸發(fā)事件后冒泡到 document后才會對 React事件進(jìn)行處理。

所以原生的事件會先執(zhí)行,然后執(zhí)行 React合成事件,最后執(zhí)行真正在 document上掛載的事件

React事件和原生事件最好不要混用。原生事件中如果執(zhí)行了 stopPropagation方法,則會導(dǎo)致其他 React事件失效。因?yàn)樗性氐氖录o法冒泡到 document上,導(dǎo)致所有的 React事件都將無法被觸發(fā)。。

虛擬Dom是什么?

在原生的 JavaScript程序中,我們直接對 DOM進(jìn)行創(chuàng)建和更改,而 DOM元素通過我們監(jiān)聽的事件和我們的應(yīng)用程序進(jìn)行通訊。

React會先將你的代碼轉(zhuǎn)換成一個 JavaScript對象,然后這個 JavaScript對象再轉(zhuǎn)換成真實(shí) DOM。這個 JavaScript對象就是所謂的虛擬 DOM。

當(dāng)我們需要創(chuàng)建或更新元素時, React首先會讓這個 VitrualDom對象進(jìn)行創(chuàng)建和更改,然后再將 VitrualDom對象渲染成真實(shí)DOM。

當(dāng)我們需要對 DOM進(jìn)行事件監(jiān)聽時,首先對 VitrualDom進(jìn)行事件監(jiān)聽, VitrualDom會代理原生的 DOM事件從而做出響應(yīng)。

虛擬Dom比普通Dom更快嗎?

很多文章說 VitrualDom可以提升性能,這一說法實(shí)際上是很片面的。

直接操作 DOM是非常耗費(fèi)性能的,這一點(diǎn)毋庸置疑。但是 React使用 VitrualDom也是無法避免操作 DOM的。

如果是首次渲染, VitrualDom不具有任何優(yōu)勢,甚至它要進(jìn)行更多的計(jì)算,消耗更多的內(nèi)存。

VitrualDom的優(yōu)勢在于 ReactDiff算法和批處理策略, React在頁面更新之前,提前計(jì)算好了如何進(jìn)行更新和渲染 DOM。實(shí)際上,這個計(jì)算過程我們在直接操作 DOM時,也是可以自己判斷和實(shí)現(xiàn)的,但是一定會耗費(fèi)非常多的精力和時間,而且往往我們自己做的是不如 React好的。所以,在這個過程中 React幫助我們"提升了性能"。

所以,我更傾向于說, VitrualDom幫助我們提高了開發(fā)效率,在重復(fù)渲染時它幫助我們計(jì)算如何更高效的更新,而不是它比 DOM操作更快。

虛擬Dom中的$$typeof屬性的作用是什么?

ReactElement中有一個 $$typeof屬性,它被賦值為 REACT_ELEMENT_TYPE

  var REACT_ELEMENT_TYPE =`

  (typeof  Symbol  ===  'function'  &&  Symbol.for  &&  Symbol.for('react.element'))  ||

0xeac7;

可見, $$typeof是一個 Symbol類型的變量,這個變量可以防止 XSS

如果你的服務(wù)器有一個漏洞,允許用戶存儲任意 JSON對象, 而客戶端代碼需要一個字符串,這可能會成為一個問題:

// JSON  

let expectedTextButGotJSON =  {  

type:  'div',  

props:  {

dangerouslySetInnerHTML:  {  

__html:  '/* put your exploit here */' 

},

},

};

let message =  { text: expectedTextButGotJSON };

<p>

{message.text}

</p>

JSON中不能存儲 Symbol類型的變量。

ReactElement.isValidElement函數(shù)用來判斷一個 React組件是否是有效的??梢?React渲染時會把沒有 $$typeof標(biāo)識,以及規(guī)則校驗(yàn)不通過的組件過濾掉。

當(dāng)你的環(huán)境不支持 Symbol時, $$typeof被賦值為 0xeac7,至于為什么, React開發(fā)者給出了答案:

0xeac7看起來有點(diǎn)像 React。

React組件的渲染流程是什么?

  • 使用 React.createElementJSX編寫 React組件,實(shí)際上所有的 JSX代碼最后都會轉(zhuǎn)換成 React.createElement(...), Babel幫助我們完成了這個轉(zhuǎn)換的過程。

  • createElement函數(shù)對 keyref等特殊的 props進(jìn)行處理,并獲取 defaultProps對默認(rèn) props進(jìn)行賦值,并且對傳入的孩子節(jié)點(diǎn)進(jìn)行處理,最終構(gòu)造成一個 ReactElement對象(所謂的虛擬 DOM)。

  • ReactDOM.render將生成好的虛擬 DOM渲染到指定容器上,其中采用了批處理、事務(wù)等機(jī)制并且對特定瀏覽器進(jìn)行了性能優(yōu)化,最終轉(zhuǎn)換為真實(shí) DOM。

為什么代碼中一定要引入React?

JSX只是為 React.createElement(component,props,...children)方法提供的語法糖。

所有的 JSX代碼最后都會轉(zhuǎn)換成 React.createElement(...), Babel幫助我們完成了這個轉(zhuǎn)換的過程。

所以使用了 JSX的代碼都必須引入 React。

為什么React組件首字母必須大寫?

babel在編譯時會判斷 JSX中組件的首字母,當(dāng)首字母為小寫時,其被認(rèn)定為原生 DOM標(biāo)簽, createElement的第一個變量被編譯為字符串;當(dāng)首字母為大寫時,其被認(rèn)定為自定義組件, createElement的第一個變量被編譯為對象;

React在渲染真實(shí)Dom時做了哪些性能優(yōu)化?

IE(8-11)Edge瀏覽器中,一個一個插入無子孫的節(jié)點(diǎn),效率要遠(yuǎn)高于插入一整個序列化完整的節(jié)點(diǎn)樹。

React通過 lazyTree,在 IE(8-11)Edge中進(jìn)行單個節(jié)點(diǎn)依次渲染節(jié)點(diǎn),而在其他瀏覽器中則首先將整個大的 DOM結(jié)構(gòu)構(gòu)建好,然后再整體插入容器。

并且,在單獨(dú)渲染節(jié)點(diǎn)時, React還考慮了 fragment等特殊節(jié)點(diǎn),這些節(jié)點(diǎn)則不會一個一個插入渲染。

什么是高階組件?如何實(shí)現(xiàn)?

高階組件可以看作 React對裝飾模式的一種實(shí)現(xiàn),高階組件就是一個函數(shù),且該函數(shù)接受一個組件作為參數(shù),并返回一個新的組件。

高階組件( HOC)是 React中的高級技術(shù),用來重用組件邏輯。但高階組件本身并不是 ReactAPI。它只是一種模式,這種模式是由 React自身的組合性質(zhì)必然產(chǎn)生的。

function visible(WrappedComponent)  {

return  class extends Component  {

render()  {

const  { visible,  ...props }  =  this.props;

if  (visible ===  false)  return  null;

return  <WrappedComponent  {...props}  />;

}

}

}

</pre>

上面的代碼就是一個 HOC的簡單應(yīng)用,函數(shù)接收一個組件作為參數(shù),并返回一個新組件,新組建可以接收一個 visible props,根據(jù) visible的值來判斷是否渲染Visible。

我們可以通過以下兩種方式實(shí)現(xiàn)高階組件:

屬性代理

函數(shù)返回一個我們自己定義的組件,然后在 render中返回要包裹的組件,這樣我們就可以代理所有傳入的 props,并且決定如何渲染,實(shí)際上 ,這種方式生成的高階組件就是原組件的父組件,上面的函數(shù) visible就是一個 HOC屬性代理的實(shí)現(xiàn)方式。

function proxyHOC(WrappedComponent)  {

    return  class extends Component  {

    render()  {

    return  <WrappedComponent  {...this.props}  />;

    }

    }

  }

對比原生組件增強(qiáng)的項(xiàng):

  • 可操作所有傳入的 props

  • 可操作組件的生命周期

  • 可操作組件的 static方法

  • 獲取 refs

反向繼承

返回一個組件,繼承原組件,在 render中調(diào)用原組件的 render。由于繼承了原組件,能通過this訪問到原組件的 生命周期、props、state、render等,相比屬性代理它能操作更多的屬性。

function inheritHOC(WrappedComponent)  {

  return  class extends WrappedComponent  {

render()  {

return super.render();

}

}

}

對比原生組件增強(qiáng)的項(xiàng):

  • 可操作所有傳入的 props

  • 可操作組件的生命周期

  • 可操作組件的 static方法

  • 獲取 refs

  • 可操作 state

  • 可以渲染劫持

HOC在業(yè)務(wù)場景中有哪些實(shí)際應(yīng)用場景?

HOC可以實(shí)現(xiàn)的功能:

  • 組合渲染

  • 條件渲染

  • 操作 props

  • 獲取 refs

  • 狀態(tài)管理

  • 操作 state

  • 渲染劫持

HOC在業(yè)務(wù)中的實(shí)際應(yīng)用場景:

  • 日志打點(diǎn)

  • 權(quán)限控制

  • 雙向綁定

  • 表單校驗(yàn)

高階組件(HOC)和Mixin的異同點(diǎn)是什么?

MixinHOC都可以用來解決 React的代碼復(fù)用問題。

  • Mixin 可能會相互依賴,相互耦合,不利于代碼維護(hù)

  • 不同的 Mixin中的方法可能會相互沖突

  • Mixin非常多時,組件是可以感知到的,甚至還要為其做相關(guān)處理,這樣會給代碼造成滾雪球式的復(fù)雜性

HOC的出現(xiàn)可以解決這些問題:

  • 高階組件就是一個沒有副作用的純函數(shù),各個高階組件不會互相依賴耦合

  • 高階組件也有可能造成沖突,但我們可以在遵守約定的情況下避免這些行為

  • 高階組件并不關(guān)心數(shù)據(jù)使用的方式和原因,而被包裹的組件也不關(guān)心數(shù)據(jù)來自何處。高階組件的增加不會為原組件增加負(fù)擔(dān)

Hook有哪些優(yōu)勢?

  • 減少狀態(tài)邏輯復(fù)用的風(fēng)險

HookMixin在用法上有一定的相似之處,但是 Mixin引入的邏輯和狀態(tài)是可以相互覆蓋的,而多個 Hook之間互不影響,這讓我們不需要在把一部分精力放在防止避免邏輯復(fù)用的沖突上。在不遵守約定的情況下使用 HOC也有可能帶來一定沖突,比如 props覆蓋等等,使用 Hook則可以避免這些問題。

  • 避免地獄式嵌套

大量使用 HOC的情況下讓我們的代碼變得嵌套層級非常深,使用 HOC,我們可以實(shí)現(xiàn)扁平式的狀態(tài)邏輯復(fù)用,而避免了大量的組件嵌套。

  • 讓組件更容易理解

在使用 class組件構(gòu)建我們的程序時,他們各自擁有自己的狀態(tài),業(yè)務(wù)邏輯的復(fù)雜使這些組件變得越來越龐大,各個生命周期中會調(diào)用越來越多的邏輯,越來越難以維護(hù)。使用 Hook,可以讓你更大限度的將公用邏輯抽離,將一個組件分割成更小的函數(shù),而不是強(qiáng)制基于生命周期方法進(jìn)行分割。

  • 使用函數(shù)代替class

相比函數(shù),編寫一個 class可能需要掌握更多的知識,需要注意的點(diǎn)也越多,比如 this指向、綁定事件等等。另外,計(jì)算機(jī)理解一個 class比理解一個函數(shù)更快。Hooks讓你可以在 classes之外使用更多 React的新特性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容