web前端教程分享:常見 React 面試題

千鋒IT技術學習發表於2018-11-20

React 中 keys 的作用是什麼?

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

render () {
return (
<ul>
{this.state.todoItems.map(({item, key}) => {
return <li key={key}>{item}</li>
})}
</ul>
)
}

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

呼叫 setState 之後發生了什麼?

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

react 生命週期函式


  • 初始化階段:

  • getDefaultProps:獲取例項的預設屬性

  • getInitialState:獲取每個例項的初始化狀態

  • componentWillMount:元件即將被裝載、渲染到頁面上

  • render:元件在這裡生成虛擬的 DOM 節點

  • componentDidMount:元件真正在被裝載之後

  • 執行中狀態:

  • componentWillReceiveProps:元件將要接收到屬性的時候呼叫

  • shouldComponentUpdate:元件接受到新屬性或者新狀態的時候(可以返回 false,接收資料後不更新,阻止 render 呼叫,後面的函式不會被繼續執行了)

  • componentWillUpdate:元件即將更新不能修改屬性和狀態

  • render:元件重新描繪

  • componentDidUpdate:元件已經更新

  • 銷燬階段:

  • componentWillUnmount:元件即將銷燬


shouldComponentUpdate 是做什麼的,(react 效能優化是哪個周期函式?)

shouldComponentUpdate 這個方法用來判斷是否需要呼叫 render 方法重新描繪 dom。因為 dom 的描繪非常消耗效能,如果我們能在 shouldComponentUpdate 方法中能夠寫出更優化的 dom diff 演算法,可以極大的提高效能。

參考react 效能優化-sf


為什麼虛擬 dom 會提高效能?(必考)

虛擬 dom 相當於在 js 和真實 dom 中間加了一個快取,利用 dom diff 演算法避免了沒有必要的 dom 操作,從而提高效能。

用 JavaScript 物件結構表示 DOM 樹的結構;然後用這個樹構建一個真正的 DOM 樹,插到文件當中當狀態變更的時候,重新構造一棵新的物件樹。然後用新的樹和舊的樹進行比較,記錄兩棵樹差異把 2 所記錄的差異應用到步驟 1 所構建的真正的 DOM 樹上,檢視就更新了。

參考 如何理解虛擬 DOM?-zhihu


react diff 原理(常考,大廠必考)


  • 把樹形結構按照層級分解,只比較同級元素。

  • 給列表結構的每個單元新增唯一的 key 屬性,方便比較。

  • React 只會匹配相同 class 的 component(這裡面的 class 指的是元件的名字)

  • 合併操作,呼叫 component 的 setState 方法的時候, React 將其標記為 dirty.到每一個事件迴圈結束, React 檢查所有標記 dirty 的 component 重新繪製.

  • 選擇性子樹渲染。開發人員可以重寫 shouldComponentUpdate 提高 diff 的效能。


參考:React 的 diff 演算法


React 中 refs 的作用是什麼?

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>
)
}


如果你建立了類似於下面的 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>


展示元件(Presentational component)和容器元件(Container component)之間有何不同


  • 展示元件關心元件看起來是什麼。展示專門通過 props 接受資料和回撥,並且幾乎不會有自身的狀態,但當展示元件擁有自身的狀態時,通常也只關心 UI 狀態而不是資料的狀態。

  • 容器元件則更關心元件是如何運作的。容器元件會為展示元件或者其它容器元件提供資料和行為(behavior),它們會呼叫 Flux actions,並將其作為回撥提供給展示元件。容器元件經常是有狀態的,因為它們是(其它元件的)資料來源。


類元件(Class component)和函式式元件(Functional component)之間有何不同


  • 類元件不僅允許你使用更多額外的功能,如元件自身的狀態和生命週期鉤子,也能使元件直接訪問 store 並維持狀態

  • 當元件僅是接收 props,並將元件自身渲染到頁面時,該元件就是一個 '無狀態元件(stateless component)',可以使用一個純函式來建立這樣的元件。這種元件也被稱為啞元件(dumb components)或展示元件


(元件的)狀態(state)和屬性(props)之間有何不同


  • State 是一種資料結構,用於元件掛載時所需資料的預設值。State 可能會隨著時間的推移而發生突變,但多數時候是作為使用者事件行為的結果。

  • Props(properties 的簡寫)則是元件的配置。props 由父元件傳遞給子元件,並且就子元件而言,props 是不可變的(immutable)。元件不能改變自身的 props,但是可以把其子元件的 props 放在一起(統一管理)。Props 也不僅僅是資料--回撥函式也可以通過 props 傳遞。


何為受控元件(controlled component)

在 HTML 中,類似 <input>, <textarea> 和 <select> 這樣的表單元素會維護自身的狀態,並基於使用者的輸入來更新。當使用者提交表單時,前面提到的元素的值將隨表單一起被髮送。但在 React 中會有些不同,包含表單元素的元件將會在 state 中追蹤輸入的值,並且每次呼叫回撥函式時,如 onChange 會更新 state,重新渲染元件。一個輸入表單元素,它的值通過 React 的這種方式來控制,這樣的元素就被稱為"受控元素"。


何為高階元件(higher order component)

高階元件是一個以元件為引數並返回一個新元件的函式。HOC 執行你重用程式碼、邏輯和引導抽象。最常見的可能是 Redux 的 connect 函式。除了簡單分享工具庫和簡單的組合,HOC 最好的方式是共享 React 元件之間的行為。如果你發現你在不同的地方寫了大量程式碼來做同一件事時,就應該考慮將程式碼重構為可重用的 HOC。


為什麼建議傳遞給 setState 的引數是一個 callback 而不是一個物件

因為 this.props 和 this.state 的更新可能是非同步的,不能依賴它們的值去計算下一個 state。


除了在建構函式中繫結 this,還有其它方式嗎

你可以使用屬性初始值設定項(property initializers)來正確繫結回撥,create-react-app 也是預設支援的。在回撥中你可以使用箭頭函式,但問題是每次元件渲染時都會建立一個新的回撥。


(在建構函式中)呼叫 super(props) 的目的是什麼

在 super() 被呼叫之前,子類是不能使用 this 的,在 ES2015 中,子類必須在 constructor 中呼叫 super()。傳遞 props 給 super() 的原因則是便於(在子類中)能在 constructor 訪問 this.props。


應該在 React 元件的何處發起 Ajax 請求

在 React 元件中,應該在 componentDidMount 中發起網路請求。這個方法會在元件第一次“掛載”(被新增到 DOM)時執行,在元件的生命週期中僅會執行一次。更重要的是,你不能保證在元件掛載之前 Ajax 請求已經完成,如果是這樣,也就意味著你將嘗試在一個未掛載的元件上呼叫 setState,這將不起作用。在 componentDidMount 中發起網路請求將保證這有一個元件可以更新了。


描述事件在 React 中的處理方式。

為了解決跨瀏覽器相容性問題,您的 React 中的事件處理程式將傳遞 SyntheticEvent 的例項,它是 React 的瀏覽器本機事件的跨瀏覽器包裝器。

這些 SyntheticEvent 與您習慣的原生事件具有相同的介面,除了它們在所有瀏覽器中都相容。有趣的是,React 實際上並沒有將事件附加到子節點本身。React 將使用單個事件監聽器監聽頂層的所有事件。這對於效能是有好處的,這也意味著在更新 DOM 時,React 不需要擔心跟蹤事件監聽器。


createElement 和 cloneElement 有什麼區別?

React.createElement():JSX 語法就是用 React.createElement()來構建 React 元素的。它接受三個引數,第一個引數可以是一個標籤名。如 div、span,或者 React 元件。第二個引數為傳入的屬性。第三個以及之後的引數,皆作為元件的子元件。

React.createElement(
type,
[props],
[...children]
)

React.cloneElement()與 React.createElement()相似,不同的是它傳入的第一個引數是一個 React 元素,而不是標籤名或元件。新新增的屬性會併入原有的屬性,傳入到返回的新元素中,而就的子元素獎盃替換。

React.cloneElement(
element,
[props],
[...children]
)


React 中有三種構建元件的方式

React.createClass()、ES6 class 和無狀態函式。


react 元件的劃分業務元件技術元件?


  • 根據元件的職責通常把元件分為 UI 元件和容器元件。

  • UI 元件負責 UI 的呈現,容器元件負責管理資料和邏輯。

  • 兩者通過 React-Redux 提供 connect 方法聯絡起來。


簡述 flux 思想

Flux 的最大特點,就是資料的"單向流動"。


  • 使用者訪問 View

  • View 發出使用者的 Action

  • Dispatcher 收到 Action,要求 Store 進行相應的更新

  • Store 更新後,發出一個"change"事件

  • View 收到"change"事件後,更新頁面


React 專案用過什麼腳手架(本題是開放性題目)

creat-react-app Yeoman 等


瞭解 redux 麼,說一下 redux 把


  • redux 是一個應用資料流框架,主要是解決了元件間狀態共享的問題,原理是集中式管理,主要有三個核心方法,action,store,reducer,工作流程是 view 呼叫 store 的 dispatch 接收 action 傳入 store,reducer 進行 state 操作,view 通過 store 提供的 getState 獲取最新的資料,flux 也是用來進行資料操作的,有四個組成部分 action,dispatch,view,store,工作流程是 view 發出一個 action,派發器接收 action,讓 store 進行資料更新,更新完成以後 store 發出 change,view 接受 change 更新檢視。Redux 和 Flux 很像。主要區別在於 Flux 有多個可以改變應用狀態的 store,在 Flux 中 dispatcher 被用來傳遞資料到註冊的回撥事件,但是在 redux 中只能定義一個可更新狀態的 store,redux 把 store 和 Dispatcher 合併,結構更加簡單清晰

  • 新增 state,對狀態的管理更加明確,通過 redux,流程更加規範了,減少手動編碼量,提高了編碼效率,同時缺點時當資料更新時有時候元件不需要,但是也要重新繪製,有些影響效率。一般情況下,我們在構建多互動,多資料流的複雜專案應用時才會使用它們


redux 有什麼缺點


  • 一個元件所需要的資料,必須由父元件傳過來,而不能像 flux 中直接從 store 取。

  • 當一個元件相關資料更新時,即使父元件不需要用到這個元件,父元件還是會重新 render,可能會有效率影響,或者需要寫複雜的 shouldComponentUpdate 進行判斷。


作者:小胡

原文:https://github.com/nanhupatar/FEGuide/blob/master/框架/react.md



相關文章