React 常用面試題目與分析

發表於2017-01-17

1-DJmDDc_QWPzqg7osODW81g

呼叫 setState 之後發生了什麼?

在程式碼中呼叫setState函式之後,React 會將傳入的引數物件與元件當前的狀態合併,然後觸發所謂的調和過程(Reconciliation)。經過調和過程,React 會以相對高效的方式根據新的狀態構建 React 元素樹並且著手重新渲染整個UI介面。在 React 得到元素樹之後,React 會自動計算出新的樹與老樹的節點差異,然後根據差異對介面進行最小化重渲染。在差異計算演算法中,React 能夠相對精確地知道哪些位置發生了改變以及應該如何改變,這就保證了按需更新,而不是全部重新渲染。

React 中 Element 與 Component 的區別是?

簡單而言,React Element 是描述螢幕上所見內容的資料結構,是對於 UI 的物件表述。典型的 React Element 就是利用 JSX 構建的宣告式程式碼片然後被轉化為createElement的呼叫組合。而 React Component 則是可以接收引數輸入並且返回某個 React Element 的函式或者類。更多介紹可以參考React Elements vs React Components

在什麼情況下你會優先選擇使用 Class Component 而不是 Functional Component?

在元件需要包含內部狀態或者使用到生命週期函式的時候使用 Class Component ,否則使用函式式元件。

React 中 refs 的作用是什麼?

Refs 是 React 提供給我們的安全訪問 DOM 元素或者某個元件例項的控制程式碼。我們可以為元素新增ref屬性然後在回撥函式中接受該元素在 DOM 樹中的控制程式碼,該值會作為回撥函式的第一個引數返回:

上述程式碼中的input域包含了一個ref屬性,該屬性宣告的回撥函式會接收input對應的 DOM 元素,我們將其繫結到this指標以便在其他的類函式中使用。另外值得一提的是,refs 並不是類元件的專屬,函式式元件同樣能夠利用閉包暫存其值:

React 中 keys 的作用是什麼?

Keys 是 React 用於追蹤哪些列表中元素被修改、被新增或者被移除的輔助標識。

在開發過程中,我們需要保證某個元素的 key 在其同級元素中具有唯一性。在 React Diff 演算法中 React 會藉助元素的 Key 值來判斷該元素是新近建立的還是被移動而來的元素,從而減少不必要的元素重渲染。此外,React 還需要藉助 Key 值來判斷元素與本地狀態的關聯關係,因此我們絕不可忽視轉換函式中 Key 的重要性。

如果你建立了類似於下面的Twitter元素,那麼它相關的類定義是啥樣子的?

如果你還不熟悉回撥渲染模式(Render Callback Pattern),這個程式碼可能看起來有點怪。這種模式中,元件會接收某個函式作為其子元件,然後在渲染函式中以props.children進行呼叫:

這種模式的優勢在於將父元件與子元件解耦和,父元件可以直接訪問子元件的內部狀態而不需要再通過Props傳遞,這樣父元件能夠更為方便地控制子元件展示的UI介面。譬如產品經理讓我們將原本展示的Badge替換為Profile,我們可以輕易地修改下回撥函式即可:

Controlled Component 與 Uncontrolled Component 之間的區別是什麼?

React 的核心組成之一就是能夠維持內部狀態的自治元件,不過當我們引入原生的HTML表單元素時(input,select,textarea 等),我們是否應該將所有的資料託管到 React 元件中還是將其仍然保留在 DOM 元素中呢?這個問題的答案就是受控元件與非受控元件的定義分割。受控元件(Controlled Component)代指那些交由 React 控制並且所有的表單資料統一存放的元件。譬如下面這段程式碼中username變數值並沒有存放到DOM元素中,而是存放在元件狀態資料中。任何時候我們需要改變username變數值時,我們應當呼叫setState函式進行修改。

而非受控元件(Uncontrolled Component)則是由DOM存放表單資料,並非存放在 React 元件中。我們可以使用 refs 來操控DOM元素:

竟然非受控元件看上去更好實現,我們可以直接從 DOM 中抓取資料,而不需要新增額外的程式碼。不過實際開發中我們並不提倡使用非受控元件,因為實際情況下我們需要更多的考慮表單驗證、選擇性的開啟或者關閉按鈕點選、強制輸入格式等功能支援,而此時我們將資料託管到 React 中有助於我們更好地以宣告式的方式完成這些功能。引入 React 或者其他 MVVM 框架最初的原因就是為了將我們從繁重的直接操作 DOM 中解放出來。

在生命週期中的哪一步你應該發起 AJAX 請求?

我們應當將AJAX 請求放到 componentDidMount 函式中執行,主要原因有下:

  • React 下一代調和演算法 Fiber 會通過開始或停止渲染的方式優化應用效能,其會影響到 componentWillMount 的觸發次數。對於 componentWillMount 這個生命週期函式的呼叫次數會變得不確定,React 可能會多次頻繁呼叫 componentWillMount。如果我們將 AJAX 請求放到 componentWillMount 函式中,那麼顯而易見其會被觸發多次,自然也就不是好的選擇。
  • 如果我們將 AJAX 請求放置在生命週期的其他函式中,我們並不能保證請求僅在元件掛載完畢後才會要求響應。如果我們的資料請求在元件掛載之前就完成,並且呼叫了setState函式將資料新增到元件狀態中,對於未掛載的元件則會報錯。而在 componentDidMount 函式中進行 AJAX 請求則能有效避免這個問題。

shouldComponentUpdate 的作用是啥以及為何它這麼重要?

shouldComponentUpdate 允許我們手動地判斷是否要進行元件更新,根據元件的應用場景設定函式的合理返回值能夠幫我們避免不必要的更新。

如何告訴 React 它應該編譯生產環境版本?

通常情況下我們會使用 Webpack 的 DefinePlugin 方法來將 NODE_ENV 變數值設定為 production。編譯版本中 React 會忽略 propType 驗證以及其他的告警資訊,同時還會降低程式碼庫的大小,React 使用了 Uglify 外掛來移除生產環境下不必要的註釋等資訊。

為什麼我們需要使用 React 提供的 Children API 而不是 JavaScript 的 map?

props.children並不一定是陣列型別,譬如下面這個元素:

如果我們使用props.children.map函式來遍歷時會受到異常提示,因為在這種情況下props.children是物件(object)而不是陣列(array)。React 當且僅當超過一個子元素的情況下會將props.children設定為陣列,就像下面這個程式碼片:

這也就是我們優先選擇使用React.Children.map函式的原因,其已經將props.children不同型別的情況考慮在內了。

概述下 React 中的事件處理邏輯

為了解決跨瀏覽器相容性問題,React 會將瀏覽器原生事件(Browser Native Event)封裝為合成事件(SyntheticEvent)傳入設定的事件處理器中。這裡的合成事件提供了與原生事件相同的介面,不過它們遮蔽了底層瀏覽器的細節差異,保證了行為的一致性。另外有意思的是,React 並沒有直接將事件附著到子元素上,而是以單一事件監聽器的方式將所有的事件傳送到頂層進行處理。這樣 React 在更新 DOM 的時候就不需要考慮如何去處理附著在 DOM 上的事件監聽器,最終達到優化效能的目的。

createElement 與 cloneElement 的區別是什麼?

createElement 函式是 JSX 編譯之後使用的建立 React Element 的函式,而 cloneElement 則是用於複製某個元素並傳入新的 Props。

傳入 setState 函式的第二個引數的作用是什麼?

該函式會在setState函式呼叫完成並且元件開始重渲染的時候被呼叫,我們可以用該函式來監聽渲染是否完成:

下述程式碼有錯嗎?

這段程式碼沒啥問題,不過只是不太常用罷了,詳細可以參考React中setState同步更新策略

相關文章