React 經常使用面試題目與分析翻譯自React Interview Questions,從屬於筆者的Web 前端入門與工程實踐,更多前端思考借鑑2016-個人前端之路:工具化與工程化前端
在代碼中調用setState
函數以後,React 會將傳入的參數對象與組件當前的狀態合併,而後觸發所謂的調和過程(Reconciliation)。通過調和過程,React 會以相對高效的方式根據新的狀態構建 React 元素樹而且着手從新渲染整個UI界面。在 React 獲得元素樹以後,React 會自動計算出新的樹與老樹的節點差別,而後根據差別對界面進行最小化重渲染。在差別計算算法中,React 可以相對精確地知道哪些位置發生了改變以及應該如何改變,這就保證了按需更新,而不是所有從新渲染。react
簡單而言,React Element 是描述屏幕上所見內容的數據結構,是對於 UI 的對象表述。典型的 React Element 就是利用 JSX 構建的聲明式代碼片而後被轉化爲createElement
的調用組合。而 React Component 則是能夠接收參數輸入而且返回某個 React Element 的函數或者類。更多介紹能夠參考React Elements vs React Components。git
在組件須要包含內部狀態或者使用到生命週期函數的時候使用 Class Component ,不然使用函數式組件。github
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> ) }
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 的重要性。promise
Twitter
元素,那麼它相關的類定義是啥樣子的?<Twitter username='tylermcginnis33'> {(user) => user === null ? <Loading /> : <Badge info={user} />} </Twitter>
import React, { Component, PropTypes } from 'react' import fetchUser from 'twitter' // fetchUser take in a username returns a promise // which will resolve with that username's data. class Twitter extends Component { // finish this }
若是你還不熟悉回調渲染模式(Render Callback Pattern),這個代碼可能看起來有點怪。這種模式中,組件會接收某個函數做爲其子組件,而後在渲染函數中以props.children
進行調用:瀏覽器
import React, { Component, PropTypes } from 'react' import fetchUser from 'twitter' class 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>
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 請求放到 componentDidMount 函數中執行,主要緣由有下:
React 下一代調和算法 Fiber 會經過開始或中止渲染的方式優化應用性能,其會影響到 componentWillMount 的觸發次數。對於 componentWillMount 這個生命週期函數的調用次數會變得不肯定,React 可能會屢次頻繁調用 componentWillMount。若是咱們將 AJAX 請求放到 componentWillMount 函數中,那麼顯而易見其會被觸發屢次,天然也就不是好的選擇。
若是咱們將 AJAX 請求放置在生命週期的其餘函數中,咱們並不能保證請求僅在組件掛載完畢後纔會要求響應。若是咱們的數據請求在組件掛載以前就完成,而且調用了setState
函數將數據添加到組件狀態中,對於未掛載的組件則會報錯。而在 componentDidMount 函數中進行 AJAX 請求則能有效避免這個問題。
shouldComponentUpdate 容許咱們手動地判斷是否要進行組件更新,根據組件的應用場景設置函數的合理返回值可以幫咱們避免沒必要要的更新。
一般狀況下咱們會使用 Webpack 的 DefinePlugin 方法來將 NODE_ENV 變量值設置爲 production。編譯版本中 React 會忽略 propType 驗證以及其餘的告警信息,同時還會下降代碼庫的大小,React 使用了 Uglify 插件來移除生產環境下沒必要要的註釋等信息。
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 會將瀏覽器原生事件(Browser Native Event)封裝爲合成事件(SyntheticEvent)傳入設置的事件處理器中。這裏的合成事件提供了與原生事件相同的接口,不過它們屏蔽了底層瀏覽器的細節差別,保證了行爲的一致性。另外有意思的是,React 並無直接將事件附着到子元素上,而是以單一事件監聽器的方式將全部的事件發送到頂層進行處理。這樣 React 在更新 DOM 的時候就不須要考慮如何去處理附着在 DOM 上的事件監聽器,最終達到優化性能的目的。
createElement 函數是 JSX 編譯以後使用的建立 React Element 的函數,而 cloneElement 則是用於複製某個元素並傳入新的 Props。
該函數會在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同步更新策略