React 常用面試題目與分析
本文有一定概率為水文,怕濕身者勿看。
React 常用面試題目與分析翻譯自React Interview Questions,從屬於筆者的Web 前端入門與工程實踐,更多前端思考借鑒2016-我的前端之路:工具化與工程化
調用 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 的作用是什麼?
注意,根據React最新文檔,下面這種用法已經被棄用了,統一改為回調函數模式
this.refs.textInput
Refs 是 React 提供給我們的安全訪問 DOM 元素或者某個組件實例的句柄。我們可以為元素添加ref屬性然後在回調函數中接受該元素在 DOM 樹中的句柄,該值會作為回調函數的第一個參數返回:
class CustomForm extends Component { handleSubmit = () => { console.log("Input Value: ", this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type=text ref={(input) => this.input = input} /> <button type=submit>Submit</button> </form> ) }}
上述代碼中的input域包含了一個ref屬性,該屬性聲明的回調函數會接收input對應的 DOM 元素,我們將其綁定到this指針以便在其他的類函數中使用。另外值得一提的是,refs 並不是類組件的專屬,函數式組件同樣能夠利用閉包暫存其值:
function CustomForm ({handleSubmit}) { let inputElement return ( <form onSubmit={() => handleSubmit(inputElement.value)}> <input type=text ref={(input) => inputElement = input} /> <button type=submit>Submit</button> </form> )}
React 中 keys 的作用是什麼?
Keys 是 React 用於追蹤哪些列表中元素被修改、被添加或者被移除的輔助標識。
render () { return ( <ul> {this.state.todoItems.map(({task, uid}) => { return <li key={uid}>{task}</li> })} </ul> )}
在開發過程中,我們需要保證某個元素的 key 在其同級元素中具有唯一性。在 React Diff 演算法中 React 會藉助元素的 Key 值來判斷該元素是新近創建的還是被移動而來的元素,從而減少不必要的元素重渲染。此外,React 還需要藉助 Key 值來判斷元素與本地狀態的關聯關係,因此我們絕不可忽視轉換函數中 Key 的重要性。
如果你創建了類似於下面的Twitter元素,那麼它相關的類定義是啥樣子的?
<Twitter username=tylermcginnis33> {(user) => user === null ? <Loading /> : <Badge info={user} />}</Twitter>
import React, { Component, PropTypes } from reactimport fetchUser from twitter// fetchUser take in a username returns a promise// which will resolve with that usernames data.class Twitter extends Component { // finish this}
如果你還不熟悉回調渲染模式(Render Callback Pattern),這個代碼可能看起來有點怪。這種模式中,組件會接收某個函數作為其子組件,然後在渲染函數中以props.children進行調用:
import React, { Component, PropTypes } from reactimport fetchUser from twitterclass Twitter extends Component { state = { user: null, } static propTypes = { username: PropTypes.string.isRequired, } componentDidMount () { fetchUser(this.props.username) .then((user) => this.setState({user})) } render () { return this.props.children(this.state.user) }}
這種模式的優勢在於將父組件與子組件解耦和,父組件可以直接訪問子組件的內部狀態而不需要再通過Props傳遞,這樣父組件能夠更為方便地控制子組件展示的UI界面。譬如產品經理讓我們將原本展示的Badge替換為Profile,我們可以輕易地修改下回調函數即可:
<Twitter username=tylermcginnis33> {(user) => user === null ? <Loading /> : <Profile info={user} />}</Twitter>
Controlled Component 與 Uncontrolled Component 之間的區別是什麼?
React 的核心組成之一就是能夠維持內部狀態的自治組件,不過當我們引入原生的HTML表單元素時(input,select,textarea 等),我們是否應該將所有的數據託管到 React 組件中還是將其仍然保留在 DOM 元素中呢?這個問題的答案就是受控組件與非受控組件的定義分割。受控組件(Controlled Component)代指那些交由 React 控制並且所有的表單數據統一存放的組件。譬如下面這段代碼中username變數值並沒有存放到DOM元素中,而是存放在組件狀態數據中。任何時候我們需要改變username變數值時,我們應當調用setState函數進行修改。
class ControlledForm extends Component { state = { username: } updateUsername = (e) => { this.setState({ username: e.target.value, }) } handleSubmit = () => {} render () { return ( <form onSubmit={this.handleSubmit}> <input type=text value={this.state.username} onChange={this.updateUsername} /> <button type=submit>Submit</button> </form> ) }}
而非受控組件(Uncontrolled Component)則是由DOM存放表單數據,並非存放在 React 組件中。我們可以使用 refs 來操控DOM元素:
class UnControlledForm extends Component { handleSubmit = () => { console.log("Input Value: ", this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type=text ref={(input) => this.input = input} /> <button type=submit>Submit</button> </form> ) }}
竟然非受控組件看上去更好實現,我們可以直接從 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並不一定是數組類型,譬如下面這個元素:
<Parent> <h1>Welcome.</h1></Parent>
如果我們使用props.children.map函數來遍歷時會受到異常提示,因為在這種情況下props.children是對象(object)而不是數組(array)。React 當且僅當超過一個子元素的情況下會將props.children設置為數組,就像下面這個代碼片:
<Parent> <h1>Welcome.</h1> <h2>props.children will now be an array</h2></Parent>
這也就是我們優先選擇使用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函數調用完成並且組件開始重渲染的時候被調用,我們可以用該函數來監聽渲染是否完成:
this.setState( { username: tylermcginnis33 }, () => console.log(setState has finished and the component has re-rendered.))
下述代碼有錯嗎?
this.setState((prevState, props) => { return { streak: prevState.streak + props.count }})
這段代碼沒啥問題,不過只是不太常用罷了,詳細可以參考React中setState同步更新策略
推薦閱讀:
※如何成為一個卓越的前端開發工程師
※CSS Modules入門Ⅰ:它是什麼?為什麼要使用它?
※通信技術:SSE設計方案(一)--- 前端Server-Sent Events概念講解和基礎類庫完善發布
※項目代碼調試:提問前要做的六個步驟