React高頻面試題梳理,看看怎麼答?(上)

前段時間準備面試,總結了不少,下面是我在準備React面試時,結合本身的實際面試經歷,以及我之前源碼分析的文章,總結出來的一些 React高頻面試題目。html

之前我寫的源碼分析的文章,並無不少人看,由於大部分狀況下你不須要深刻源碼也能懂得其中原理,並解決實際問題,這也是我總結這些面試題的緣由,讓你在更短的時間內得到更大的收益。react

因爲是以面試題的角度來討論,因此某些點可能不能很是深刻,我在問題下面都貼了相關連接,若是想深刻理解,請點擊這些文章。面試

因爲題目較多,分爲上、下兩篇,本篇文章咱們先來討論以下19個題目:算法

  • React生命週期有哪些,16版本生命週期發生了哪些變化?瀏覽器

  • setState是同步的仍是異步的?性能優化

  • 爲何有時連續屢次 setState只有一次生效?bash

  • React如何實現本身的事件機制?服務器

  • 爲什麼 React事件要本身綁定 thisbabel

  • 原生事件和 React事件的區別?網絡

  • React的合成事件是什麼?

  • React和原生事件的執行順序是什麼?能夠混用嗎?

  • 虛擬Dom是什麼?

  • 虛擬Dom普通Dom更快嗎?

  • 虛擬Dom中的 $$typeof屬性的做用是什麼?

  • React組件的渲染流程是什麼?

  • 爲何代碼中必定要引入 React

  • 爲何 React組件首字母必須大寫?

  • React在渲染 真實Dom時作了哪些性能優化?

  • 什麼是高階組件?如何實現?

  • HOC在業務場景中有哪些實際應用場景?

  • 高階組件( HOC)和 Mixin的異同點是什麼?

  • Hook有哪些優點?

React生命週期有哪些,16版本生命週期發生了哪些變化?

15生命週期

  • 初始化階段

    • constructor 構造函數

    • getDefaultProps props默認值

    • getInitialState state默認值

  • 掛載階段

    • componentWillMount 組件初始化渲染前調用

    • render 組件渲染

    • componentDidMount組件掛載到 DOM後調用

  • 更新階段

    • componentWillReceiveProps 組件將要接收新 props前調用

    • shouldComponentUpdate 組件是否須要更新

    • componentWillUpdate 組件更新前調用

    • render 組件渲染

    • componentDidUpdate 組件更新後調用

  • 卸載階段

    • componentWillUnmount 組件卸載前調用

16生命週期

  • 初始化階段

    • constructor 構造函數

    • getDefaultProps props默認值

    • getInitialState state默認值

  • 掛載階段

    • staticgetDerivedStateFromProps(props,state)

    • render

    • componentDidMount

getDerivedStateFromProps:組件每次被 rerender的時候,包括在組件構建以後(虛擬 dom以後,實際 dom掛載以前),每次獲取新的 propsstate以後;每次接收新的props以後都會返回一個對象做爲新的 state,返回null則說明不須要更新 state;配合 componentDidUpdate,能夠覆蓋 componentWillReceiveProps的全部用法

  • 更新階段

    • staticgetDerivedStateFromProps(props,state)

    • shouldComponentUpdate

    • render

    • getSnapshotBeforeUpdate(prevProps,prevState)

    • componentDidUpdate

getSnapshotBeforeUpdate:觸發時間: update發生的時候,在 render以後,在組件 dom渲染以前;返回一個值,做爲 componentDidUpdate的第三個參數;配合 componentDidUpdate, 能夠覆蓋 componentWillUpdate的全部用法

  • 卸載階段

    • componentWillUnmount

  • 錯誤處理

    • componentDidCatch

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

React16並無刪除這三個鉤子函數,可是不能和新增的鉤子函數混用, React17將會刪除這三個鉤子函數,新增了對錯誤的處理( componentDidCatch

setState是同步的仍是異步的?

  • 生命週期和合成事件中

React的生命週期和合成事件中, React仍然處於他的更新機制中,這時不管調用多少次 setState,都會不會當即執行更新,而是將要更新的·存入 _pendingStateQueue,將要更新的組件存入 dirtyComponent

當上一次更新機制執行完畢,以生命週期爲例,全部組件,即最頂層組件 didmount後會將批處理標誌設置爲 false。這時將取出 dirtyComponent中的組件以及 _pendingStateQueue中的 state進行更新。這樣就能夠確保組件不會被從新渲染屢次。

componentDidMount() {    this.setState({      index: this.state.index + 1    })    console.log('state', this.state.index);  }複製代碼

因此,如上面的代碼,當咱們在執行 setState後當即去獲取 state,這時是獲取不到更新後的 state的,由於處於 React的批處理機制中, state被暫存起來,待批處理機制完成以後,統一進行更新。

因此。setState自己並非異步的,而是 React的批處理機制給人一種異步的假象。

  • 異步代碼和原生事件中

componentDidMount() {    setTimeout(() => {      console.log('調用setState');      this.setState({        index: this.state.index + 1      })      console.log('state', this.state.index);    }, 0);  }複製代碼

如上面的代碼,當咱們在異步代碼中調用 setState時,根據 JavaScript的異步機制,會將異步代碼先暫存,等全部同步代碼執行完畢後在執行,這時 React的批處理機制已經走完,處理標誌設被設置爲 false,這時再調用 setState便可當即執行更新,拿到更新後的結果。

在原生事件中調用 setState並不會出發 React的批處理機制,因此當即能拿到最新結果。

  • 最佳實踐

setState的第二個參數接收一個函數,該函數會在 React的批處理機制完成以後調用,因此你想在調用 setState後當即獲取更新後的值,請在該回調函數中獲取。

this.setState({ index: this.state.index + 1 }, () => {      console.log(this.state.index);    })複製代碼

推薦閱讀:由實際問題探究setState的執行機制

爲何有時連續屢次setState只有一次生效?

例以下面的代碼,兩次打印出的結果是相同的:

componentDidMount() {    this.setState({ index: this.state.index + 1 }, () => {      console.log(this.state.index);    })    this.setState({ index: this.state.index + 1 }, () => {      console.log(this.state.index);    })  }複製代碼

緣由就是 React會批處理機制中存儲的多個 setState進行合併,來看下 React源碼中的 _assign函數,相似於 Objectassign

_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);複製代碼

若是傳入的是對象,很明顯會被合併成一次,因此上面的代碼兩次打印的結果是相同的:

Object.assign(  nextState,  {index: state.index+ 1},  {index: state.index+ 1})複製代碼

注意, assign函數中對函數作了特殊處理,處理第一個參數傳入的是函數,函數的參數 preState是前一次合併後的結果,因此計算結果是準確的:

componentDidMount() {    this.setState((state, props) => ({        index: state.index + 1    }), () => {      console.log(this.state.index);    })    this.setState((state, props) => ({        index: state.index + 1    }), () => {      console.log(this.state.index);    })  }複製代碼

因此上面的代碼兩次打印的結果是不一樣的。

  • 最佳實踐

React會對屢次連續的 setState進行合併,若是你想當即使用上次 setState後的結果進行下一次 setState,可讓 setState 接收一個函數而不是一個對象。這個函數用上一個 state 做爲第一個參數,將這次更新被應用時的 props 作爲第二個參數。

React如何實現本身的事件機制?

React事件並無綁定在真實的 Dom節點上,而是經過事件代理,在最外層的 document上對事件進行統一分發。

組件掛載、更新時:

  • 經過 lastPropsnextProps判斷是否新增、刪除事件分別調用事件註冊、卸載方法。

  • 調用 EventPluginHubenqueuePutListener進行事件存儲

  • 獲取 document對象。

  • 根據事件名稱(如 onClickonCaptureClick)判斷是進行冒泡仍是捕獲。

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

  • document註冊原生事件回調爲 dispatchEvent(統一的事件分發機制)。

事件初始化:

  • EventPluginHub負責管理 React合成事件的 callback,它將 callback存儲在 listenerBank中,另外還存儲了負責合成事件的 Plugin

  • 獲取綁定事件的元素的惟一標識 key

  • callback根據事件類型,元素的惟一標識 key存儲在 listenerBank中。

  • listenerBank的結構是: listenerBank[registrationName][key]

觸發事件時:

  • 觸發 document註冊原生事件的回調 dispatchEvent

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

  • 遍歷這個元素的全部父元素,依次對每一級元素進行處理。

  • 構造合成事件。

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

  • 遍歷 eventQueue

  • 經過 isPropagationStopped判斷當前事件是否執行了阻止冒泡方法。

  • 若是阻止了冒泡,中止遍歷,不然經過 executeDispatch執行合成事件。

  • 釋放處理完成的事件。

React在本身的合成事件中重寫了 stopPropagation方法,將 isPropagationStopped設置爲 true,而後在遍歷每一級事件的過程當中根據此遍歷判斷是否繼續執行。這就是 React本身實現的冒泡機制。

推薦閱讀:【React深刻】React事件機制

爲什麼React事件要本身綁定this?

在上面提到的事件處理流程中, Reactdocument上進行統一的事件分發, dispatchEvent經過循環調用全部層級的事件來模擬事件冒泡和捕獲。

React源碼中,當具體到某一事件處理函數將要調用時,將調用 invokeGuardedCallback方法。

function invokeGuardedCallback(name, func, a) {  try {    func(a);  } catch (x) {    if (caughtError === null) {      caughtError = x;    }  }}複製代碼

可見,事件處理函數是直接調用的,並無指定調用的組件,因此不進行手動綁定的狀況下直接獲取到的 this是不許確的,因此咱們須要手動將當前組件綁定到 this上。

原生事件和React事件的區別?

  • React 事件使用駝峯命名,而不是所有小寫。

  • 經過 JSX , 你傳遞一個函數做爲事件處理程序,而不是一個字符串。

  • React 中你不能經過返回 false 來阻止默認行爲。必須明確調用 preventDefault

React的合成事件是什麼?

React 根據 W3C 規範定義了每一個事件處理函數的參數,即合成事件。

事件處理程序將傳遞 SyntheticEvent 的實例,這是一個跨瀏覽器原生事件包裝器。它具備與瀏覽器原生事件相同的接口,包括 stopPropagation()preventDefault(),在全部瀏覽器中他們工做方式都相同。

React合成的 SyntheticEvent採用了事件池,這樣作能夠大大節省內存,而不會頻繁的建立和銷燬事件對象。

另外,無論在什麼瀏覽器環境下,瀏覽器會將該事件類型統一建立爲合成事件,從而達到了瀏覽器兼容的目的。

React和原生事件的執行順序是什麼?能夠混用嗎?

React的全部事件都經過 document進行統一分發。當真實 Dom觸發事件後冒泡到 document後纔會對 React事件進行處理。

因此原生的事件會先執行,而後執行 React合成事件,最後執行真正在 document上掛載的事件

React事件和原生事件最好不要混用。原生事件中若是執行了 stopPropagation方法,則會致使其餘 React事件失效。由於全部元素的事件將沒法冒泡到 document上,致使全部的 React事件都將沒法被觸發。。

虛擬Dom是什麼?

在原生的 JavaScript程序中,咱們直接對 DOM進行建立和更改,而 DOM元素經過咱們監聽的事件和咱們的應用程序進行通信。

React會先將你的代碼轉換成一個 JavaScript對象,而後這個 JavaScript對象再轉換成真實 DOM。這個 JavaScript對象就是所謂的虛擬 DOM

當咱們須要建立或更新元素時, React首先會讓這個 VitrualDom對象進行建立和更改,而後再將 VitrualDom對象渲染成真實DOM。

當咱們須要對 DOM進行事件監聽時,首先對 VitrualDom進行事件監聽, VitrualDom會代理原生的 DOM事件從而作出響應。

推薦閱讀:【React深刻】深刻分析虛擬DOM的渲染過程和特性

虛擬Dom比普通Dom更快嗎?

不少文章說 VitrualDom能夠提高性能,這一說法其實是很片面的。

直接操做 DOM是很是耗費性能的,這一點毋庸置疑。可是 React使用 VitrualDom也是沒法避免操做 DOM的。

若是是首次渲染, VitrualDom不具備任何優點,甚至它要進行更多的計算,消耗更多的內存。

VitrualDom的優點在於 ReactDiff算法和批處理策略, React在頁面更新以前,提早計算好了如何進行更新和渲染 DOM。實際上,這個計算過程咱們在直接操做 DOM時,也是能夠本身判斷和實現的,可是必定會耗費很是多的精力和時間,並且每每咱們本身作的是不如 React好的。因此,在這個過程當中 React幫助咱們"提高了性能"。

因此,我更傾向於說, VitrualDom幫助咱們提升了開發效率,在重複渲染時它幫助咱們計算如何更高效的更新,而不是它比 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

若是你的服務器有一個漏洞,容許用戶存儲任意 JSON對象, 而客戶端代碼須要一個字符串,這可能會成爲一個問題:

// JSONlet expectedTextButGotJSON = {  type: 'div',  props: {    dangerouslySetInnerHTML: {      __html: '/* put your exploit here */'    },  },};let message = { text: expectedTextButGotJSON };<p>  {message.text}</p>複製代碼

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

ReactElement.isValidElement函數用來判斷一個 React組件是不是有效的,下面是它的具體實現。

ReactElement.isValidElement = function (object) {  return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;};複製代碼

可見 React渲染時會把沒有 $$typeof標識,以及規則校驗不經過的組件過濾掉。

當你的環境不支持 Symbol時, $$typeof被賦值爲 0xeac7,至於爲何, React開發者給出了答案:

0xeac7看起來有點像 React

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


  • 使用 React.createElementJSX編寫 React組件,實際上全部的 JSX代碼最後都會轉換成 React.createElement(...)Babel幫助咱們完成了這個轉換的過程。



  • createElement函數對 keyref等特殊的 props進行處理,並獲取 defaultProps對默認 props進行賦值,而且對傳入的孩子節點進行處理,最終構形成一個 ReactElement對象(所謂的虛擬 DOM)。



  • ReactDOM.render將生成好的虛擬 DOM渲染到指定容器上,其中採用了批處理、事務等機制而且對特定瀏覽器進行了性能優化,最終轉換爲真實 DOM


爲何代碼中必定要引入React?

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

全部的 JSX代碼最後都會轉換成 React.createElement(...)Babel幫助咱們完成了這個轉換的過程。

因此使用了 JSX的代碼都必須引入 React

爲何React組件首字母必須大寫?

babel在編譯時會判斷 JSX中組件的首字母,當首字母爲小寫時,其被認定爲原生 DOM標籤, createElement的第一個變量被編譯爲字符串;當首字母爲大寫時,其被認定爲自定義組件, createElement的第一個變量被編譯爲對象;

React在渲染真實Dom時作了哪些性能優化?

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

React經過 lazyTree,在 IE(8-11)Edge中進行單個節點依次渲染節點,而在其餘瀏覽器中則首先將整個大的 DOM結構構建好,而後再總體插入容器。

而且,在單獨渲染節點時, React還考慮了 fragment等特殊節點,這些節點則不會一個一個插入渲染。

什麼是高階組件?如何實現?

高階組件能夠看做 React對裝飾模式的一種實現,高階組件就是一個函數,且該函數接受一個組件做爲參數,並返回一個新的組件。

高階組件( HOC)是 React中的高級技術,用來重用組件邏輯。但高階組件自己並非 ReactAPI。它只是一種模式,這種模式是由 React自身的組合性質必然產生的。

function visible(WrappedComponent) {  return class extends Component {    render() {      const { visible, ...props } = this.props;      if (visible === false) return null;      return <WrappedComponent {...props} />;    }  }}複製代碼

上面的代碼就是一個 HOC的簡單應用,函數接收一個組件做爲參數,並返回一個新組件,新組建能夠接收一個 visible props,根據 visible的值來判斷是否渲染Visible。

咱們能夠經過如下兩種方式實現高階組件:

屬性代理

函數返回一個咱們本身定義的組件,而後在 render中返回要包裹的組件,這樣咱們就能夠代理全部傳入的 props,而且決定如何渲染,實際上 ,這種方式生成的高階組件就是原組件的父組件,上面的函數 visible就是一個 HOC屬性代理的實現方式。

function proxyHOC(WrappedComponent) {  return class extends Component {    render() {      return <WrappedComponent {...this.props} />;    }  }}複製代碼

對比原生組件加強的項:

  • 可操做全部傳入的 props

  • 可操做組件的生命週期

  • 可操做組件的 static方法

  • 獲取 refs

反向繼承

返回一個組件,繼承原組件,在 render中調用原組件的 render。因爲繼承了原組件,能經過this訪問到原組件的 生命週期、props、state、render等,相比屬性代理它能操做更多的屬性。

function inheritHOC(WrappedComponent) {  return class extends WrappedComponent {    render() {      return super.render();    }  }}複製代碼

對比原生組件加強的項:

  • 可操做全部傳入的 props

  • 可操做組件的生命週期

  • 可操做組件的 static方法

  • 獲取 refs

  • 可操做 state

  • 能夠渲染劫持

推薦閱讀:【React深刻】從Mixin到HOC再到Hook

HOC在業務場景中有哪些實際應用場景?

HOC能夠實現的功能:

  • 組合渲染

  • 條件渲染

  • 操做 props

  • 獲取 refs

  • 狀態管理

  • 操做 state

  • 渲染劫持

HOC在業務中的實際應用場景:

  • 日誌打點

  • 權限控制

  • 雙向綁定

  • 表單校驗

具體實現請參考我這篇文章:https://juejin.im/post/5cad39b3f265da03502b1c0a

高階組件(HOC)和Mixin的異同點是什麼?

MixinHOC均可以用來解決 React的代碼複用問題。

圖片來源於網絡

  • Mixin 可能會相互依賴,相互耦合,不利於代碼維護

  • 不一樣的 Mixin中的方法可能會相互衝突

  • Mixin很是多時,組件是能夠感知到的,甚至還要爲其作相關處理,這樣會給代碼形成滾雪球式的複雜性

HOC的出現能夠解決這些問題:

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

  • 高階組件也有可能形成衝突,但咱們能夠在遵照約定的狀況下避免這些行爲

  • 高階組件並不關心數據使用的方式和緣由,而被包裹的組件也不關心數據來自何處。高階組件的增長不會爲原組件增長負擔

Hook有哪些優點?

  • 減小狀態邏輯複用的風險

HookMixin在用法上有必定的類似之處,可是 Mixin引入的邏輯和狀態是能夠相互覆蓋的,而多個 Hook之間互不影響,這讓咱們不須要在把一部分精力放在防止避免邏輯複用的衝突上。在不遵照約定的狀況下使用 HOC也有可能帶來必定衝突,好比 props覆蓋等等,使用 Hook則能夠避免這些問題。

  • 避免地獄式嵌套

大量使用 HOC的狀況下讓咱們的代碼變得嵌套層級很是深,使用 HOC,咱們能夠實現扁平式的狀態邏輯複用,而避免了大量的組件嵌套。

  • 讓組件更容易理解

在使用 class組件構建咱們的程序時,他們各自擁有本身的狀態,業務邏輯的複雜使這些組件變得愈來愈龐大,各個生命週期中會調用愈來愈多的邏輯,愈來愈難以維護。使用 Hook,可讓你更大限度的將公用邏輯抽離,將一個組件分割成更小的函數,而不是強制基於生命週期方法進行分割。

  • 使用函數代替class

相比函數,編寫一個 class可能須要掌握更多的知識,須要注意的點也越多,好比 this指向、綁定事件等等。另外,計算機理解一個 class比理解一個函數更快。Hooks讓你能夠在 classes以外使用更多 React的新特性。

下篇預告:

  • ReactDiff算法的策略是什麼?

  • Reactkey的做用是什麼?

  • ReactFiber是什麼?爲何要引入?

  • 爲何推薦在 componentDidMount中發起網絡請求?

  • React代碼優化?

  • React組件設計要掌握哪些原則?

  • Redux的核心原理是什麼?

  • 什麼是 Redux中間件?

  • Reduxconnect函數的實現策略?

  • Mox的核心原理是什麼?

  • ReduxMobx的異同點,如何選擇?


轉載 查看原文

相關文章
相關標籤/搜索