【前端面試題】—30 道常見 React 基礎面試題(附答案)

當今最流行的框架非 React 莫屬。 React 以其岀色的性能,顛覆了互聯網的理念,簡單的開發方式受到許多開發者的青睞。

因此,在 React 中,虛擬 DOM、組件的生命週期、組件的通信、組件的約束性,配合 Reflux、 Redux 等框架的使用,基於 EMAScript6 語法開發,以及 Webpack 編譯等都是讀者要掌握的內容。

當然, React 的三大特色(虛擬 DOM、組件開發、多端適配)的具體實現,開發者也要有所瞭解。

1、當調用 setState 的時候,發生了什麼操作?

當調用 setState 時, React 做的第一件事是將傳遞給 setState 的對象合併到組件的當前狀態,這將啓動一個稱爲和解( reconciliation)的過程。

和解的最終目標是,根據這個新的狀態以最有效的方式更新 DOM。

爲此, React 將構建一個新的 React 虛擬 DOM 樹(可以將其視爲頁面 DOM 元素的對象表示方式)。

一旦有了這個 DOM 樹,爲了弄清 DOM 是如何響應新的狀態而改變的, React 會將這個新樹與上一個虛擬 DOM 樹比較。

這樣做, React 會知道發生的確切變化,並且通過了解發生的變化後,在絕對必要的情況下進行更新 DOM,即可將因操作 DOM 而佔用的空間最小化。

2、在 React 中元素( element)和組件( component)有什麼區別?

簡單地說,在 React 中元素(虛擬 DOM)描述了你在屏幕上看到的 DOM 元素。

換個說法就是,在 React 中元素是頁面中 DOM 元素的對象表示方式。在 React 中組件是一個函數或一個類,它可以接受輸入並返回一個元素。

注意:工作中,爲了提高開發效率,通常使用 JSX 語法表示 React 元素(虛擬 DOM)。在編譯的時候,把它轉化成一個 React. createElement 調用方法。

3、什麼時候使用類組件( Class Component)?什麼時候使用功能組件 (Functional Component)?

如果組件具有狀態( state)或生命週期方法,請使用類組件;否則,使用功能組件。

4、什麼是 React 的 refs?爲什麼它們很重要?

refs 允許你直接訪問 DOM 元素或組件實例。爲了使用它們,可以向組件添加個 ref 屬性。

如果該屬性的值是一個回調函數,它將接受底層的 DOM 元素或組件的已掛載實例作爲其第一個參數。可以在組件中存儲它。

export class App extends Component  {
showResult ( ) {
console. log(this. input. value)
}
render ( ) {
return (
<div>
<input  type="text" ref={input => this .input =input } />
< button onClick={this. showResult.bind(this)}>展示結果</ button>
</div>
);
}
}

如果該屬性值是一個字符串, React 將會在組件實例化對象的 refs 屬性中,存儲一個同名屬性,該屬性是對這個 DOM 元素的引用。可以通過原生的 DOM API 操作它。

export class App extends Component {
 showResult( )
console .log ( this.refs.username.value)
render ( ){
  return (
<div>
<input type="text" ref="username"/>
< button onClick={this. showResu1t.bind (this)}>展示結果</ button>
</div>
);
}
}

5、React 中的 key 是什麼?爲什麼它們很重要?

key 可以幫助 React 跟蹤循環創建列表中的虛擬 DOM 元素,瞭解哪些元素已更改、添加或刪除。

每個綁定 key 的虛擬 DOM 元素,在兄弟元素之間都是獨一無二的。在 React 的和解過程中,比較新的虛擬 DOM 樹與上一個虛擬 DOM 樹之間的差異,並映射到頁面中。key 使 React 處理列表中虛擬 DOM 時更加高效,因爲 React 可以使用虛擬 DOM 上的 key 屬性,快速瞭解元素是新的、需要刪除的,還是修改過的。如果沒有 key,Rεat 就不知道列表中虛擬 DOM 元素與頁面中的哪個元素相對應。所以在創建列表的時候,不要忽略 key。

6、如果創建了類似於下面的 Icketang 元素,那麼該如何實現 Icketang 類?

< Icketang user>
{user = > user ?<Info user={user} />:<Loading />}
</Icketang>
import React, { Component } fromr "react"export class Icketang extends Component {
//請實現你的代碼
}

在上面的案例中,一個組件接受一個函數作爲它的子組件。Icketang 組件的子組件是一個函數,而不是一個常用的組件。這意味着在實現 Icketang 組件時,需要將 props. children 作爲一個函數來處理。

具體實現如下。

import React,  { Component } from "react";
class Icketang extends Component {
 constructor ( props ){
  super ( props ) 
this .state = {
 user : props.user 
}
}
componentDidMount( ) {
//模擬異步獲取數據操作,更新狀態
setTimeout ( ( ) => this .setstate ({
user:'有課前端網'
}),2000)
}
render ( ) {
return this.props.children ( this .state.user )
}
}
class Loading extends Component {
 render  ( ) {
 return <p>Loading.</p>
}
}
class Info extends Component  { 
render ( ) {
 return <hl> { this .props.user }</h1>
}
}

調用 Icketang 組件,並傳遞給 user 屬性數據,把 props.children 作爲一個函數來處理。這種模式的好處是,我們已經將父組件與子組件分離了,父組件管理狀態。父組件的使用者可以決定父組件以何種形式渲染子組件。

爲了演示這一點,在渲染 Icketang 組件時,分別傳遞和不傳遞 user 屬性數據來觀察渲染結果。

import {render} from  "react-dom";
 render (<Icketang>
{ user = > user ?<Info user = {user} /> :<Loading /> }
</Icketang> , ickt)

上述代碼沒有爲 Icketang 組件傳遞 user 屬性數據,因此將首先渲染 Loading 組件,當父組件的 user 狀態數據發生改變時,我們發現 Info 組件可以成功地渲染出來。

render(< Icketang user="雨夜清荷">
{ user => user ?<Info user = {user} /> :<Loading />}
</Icketang>, ickt)

上述代碼爲 Icketang 組件傳遞了 user 屬性數據,因此將直接渲染 Info 組件,當父組件的 user 狀態數據發生改變時,我們發現 Info 組件產生了更新,在整個過程中, Loading 組件都未渲染。

7、約束性組件( controlled component)與非約束性組件( uncontrolled  component)有什麼區別?

在 React 中,組件負責控制和管理自己的狀態。

如果將 HTML 中的表單元素( input、 select、 textarea 等)添加到組件中,當用戶與表單發生交互時,就涉及表單數據存儲問題。根據表單數據的存儲位置,將組件分成約東性組件和非約東性組件。

約束性組件( controlled component)就是由 React 控制的組件,也就是說,表單元素的數據存儲在組件內部的狀態中,表單到底呈現什麼由組件決定。

如下所示, username 沒有存儲在 DOM 元素內,而是存儲在組件的狀態中。每次要更新 username 時,就要調用 setState 更新狀態;每次要獲取 username 的值,就要獲取組件狀態值。

class App extends Component {
//初始化狀態
constructor ( props ) {
 super ( props )
this .state = {
 username:'有課前端網'
}
}
//查看結果
showResult ( ) {
//獲取數據就是獲取狀態值
console. log ( this .state. username )
}
changeUsername (e) {
//原生方法獲取
var value =e .target .value
//更新前,可以進行髒值檢測
//更新狀態
this .setState ( {t
username:value
} )
}
//渲染組件
render( ) {
//返回虛擬DOM 
return (
<div>
<p>
{/*輸入框綁定va1ue*/}
<input type="text" onChange={ this.changeUsername .bind (this ) }
value= { this .state.username }/>
</p>
<p>
< button onClick={this.showResult.bind (this)}>查看結果</ button>
</p>
</div>
)
}
}

非約束性組件( uncontrolled component)就是指表單元素的數據交由元素自身存儲並處理,而不是通過 React 組件。表單如何呈現由表單元素自身決定。

如下所示,表單的值並沒有存儲在組件的狀態中,而是存儲在表單元素中,當要修改表單數據時,直接輸入表單即可。有時也可以獲取元素,再手動修改它的值。當要獲取表單數據時,要首先獲取表單元素,然後通過表單元素獲取元素的值。

注意:爲了方便在組件中獲取表單元素,通常爲元素設置 ref 屬性,在組件內部通過 refs 屬性獲取對應的 DOM 元素。

class App extends Component {
//查看結果
showResult ( ) {
//獲取值
console. log(this. refs. username .value)
//修改值,就是修改元素自身的值
this.refs.username.value="專業前端學習平臺"
//渲染組件
render ( ) {
//返回虛擬DOM 
return (
<div>
<p>
{/*非約束性組件中,表單元素通過 defaultvalue定義*/}
< input type="text"  ref=" username"  defaultvalue="有課前端網"/>
</p>
<p>
< button onClick={this. showResult.bind ( this ) }>查看結果</button>
</p>
</div>
 )
  }
   }

雖然非約東性組件通常更容易實現,可以通過 refs 直接獲取 DOM 元素,並獲取其值,但是 React 建議使用約束性組件。主要原因是,約東性組件支持即時字段驗證,允許有條件地禁用 / 啓用按鈕,強制輸入格式等。

8、在哪個生命週期中你會發出 Ajax 請求?爲什麼?

Ajax 請求應該寫在組件創建期的第五個階段,即 componentDidMount 生命週期方法中。原因如下。

在創建期的其他階段,組件尚未渲染完成。而在存在期的 5 個階段,又不能確保生命週期方法一定會執行(如通過 shouldComponentUpdate 方法優化更新等)。在銷毀期,組件即將被銷燬,請求數據變得無意義。因此在這些階段發岀 Ajax 請求顯然不是最好的選擇。

在組件尚未掛載之前,Ajax 請求將無法執行完畢,如果此時發出請求,將意味着在組件掛載之前更新狀態(如執行 setState),這通常是不起作用的。

在 componentDidMount 方法中,執行 Ajax 即可保證組件已經掛載,並且能夠正常更新組件。

9、shouldComponentUpdate 有什麼用?爲什麼它很重要?

組件狀態數據或者屬性數據發生更新的時候,組件會進入存在期,視圖會渲染更新。在生命週期方法 should ComponentUpdate 中,允許選擇退出某些組件(和它們的子組件)的和解過程。

和解的最終目標是根據新的狀態,以最有效的方式更新用戶界面。如果我們知道用戶界面的某一部分不會改變,那麼沒有理由讓 React 弄清楚它是否應該更新渲染。通過在 shouldComponentUpdate 方法中返回 false, React 將讓當前組件及其所有子組件保持與當前組件狀態相同。

10、如何用 React 構建( build)生產模式?

通常,使用 Webpack 的 DefinePlugin 方法將 NODE ENV 設置爲 production。這將剝離 propType 驗證和額外的警告。除此之外,還可以減少代碼,因爲 React 使用 Uglify 的 dead-code 來消除開發代碼和註釋,這將大大減少包占用的空間。

11、爲什麼要使用 React. Children. map( props. children,()=>) 而不是 props. children. map ( (  ) => )?

因爲不能保證 props. children 將是一個數組。

以下面的代碼爲例。

<Parent>
<h1>有課前端網</h1>
</Parent>

在父組件內部,如果嘗試使用 props.children. map 映射子對象,則會拋出錯誤,因爲 props. children 是一個對象,而不是一個數組。

如果有多個子元素, React 會使 props.children 成爲一個數組,如下所示。

<Parent>
<h1>有課前端網</h1>
<h2>前端技術學習平臺</h2>
</Parent>
不建議使用如下方式,在這個案例中會拋出錯誤。
class Parent extends Component {
 render ( ) {
 return (
<div> { this .props.children.map (obj = > obj ) }</div>
)
} 
}

建議使用如下方式,避免在上一個案例中拋出錯誤。

class Parent extends Component  {
render ( ) {
  return (
<div> { React.Children.map ( this .props.children, obj => obj) }</div>
)
}
}

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

爲了解決跨瀏覽器兼容性問題, React 中的事件處理程序將傳遞 SyntheticEvent 的實例,它是跨瀏覽器事件的包裝器。這些 SyntheticEvent 與你習慣的原生事件具有相同的接口,它們在所有瀏覽器中都兼容。

React 實際上並沒有將事件附加到子節點本身。而是通過事件委託模式,使用單個事件監聽器監聽頂層的所有事件。這對於性能是有好處的。這也意味着在更新 DOM 時, React 不需要擔心跟蹤事件監聽器。

13、createElement 和 cloneElement 有什麼區別?

createElement 是 JSX 被轉載得到的,在 React 中用來創建 React 元素(即虛擬 DOM)的內容。cloneElement 用於複製元素並傳遞新的 props。

14、setState 方法的第二個參數有什麼用?使用它的目的是什麼?

它是一個回調函數,當 setState 方法執行結束並重新渲染該組件時調用它。在工作中,更好的方式是使用 React 組件生命週期之——“存在期” 的生命週期方法,而不是依賴這個回調函數。

export class App extends Component {
constructor (props) {
 super ( props )
this.state = {
username:"雨夜清荷"
}
}
render ( ) {
return (
<div> { this .state. username) </div>
);
}
componentDidMount ( ) {
this .setstate ( { 
 username :'有課前端網'
}( ) => console. log ( 're-rendered success. ' ) )

15、這段代碼有什麼問題?

class App extends Component {
 constructor ( props )  {
super ( props )
this .state = {
username:"有課前端網", 
msg:' '
}
}
render ( ) {
return (
<div> { this .state. msg }</div>
);
}
componentDidMount ( )  {
 this .setState ( ( oldState, props ) => {
return {
msg:oldState .username + ' - ' + props.intro 
}
} )
}

render (< App intro="前端技術專業學習平臺">,ickt )

在頁面中正常輸出 “有課前端網 - 前端技術專業學習平臺”。但是這種寫法很少使用,並不是常用的寫法。React 允許對 setState 方法傳遞一個函數,它接收到先前的狀態和屬性數據並返回一個需要修改的狀態對象,正如我們在上面所做的那樣。它不但沒有問題,而且如果根據以前的狀態( state)以及屬性來修改當前狀態,推薦使用這種寫法。

16、請說岀 React 從 EMAScript5 編程規範到 EMAScript6 編程規範過程中的幾點改變。

主要改變如下。

(1)創建組件的方法不同。

EMAScript5 版本中,定義組件用 React.createClass。EMAScript6 版本中,定義組件要定義組件類,並繼承 Component 類。

(2)定義默認屬性的方法不同。

EMAScript5 版本中,用 getDefaultProps 定義默認屬性。EMAScript6 版本中,爲組件定義 defaultProps 靜態屬性,來定義默認屬性。

(3)定義初始化狀態的方法不同。EMAScript5 版本中,用 getInitialState 定義初始化狀態。EMAScript6 版本中,在構造函數中,通過 this. state 定義初始化狀態。

注意:構造函數的第一個參數是屬性數據,一定要用 super 繼承。

(4)定義屬性約束的方法不同。

EMAScript5 版本中,用 propTypes 定義屬性的約束。

EMAScript6 版本中,爲組件定義 propsTypes 靜態屬性,來對屬性進行約束。

(5)使用混合對象、混合類的方法不同。

EMAScript5 版本中,通過 mixins 繼承混合對象的方法。

EMAScript6 版本中,定義混合類,讓混合類繼承 Component 類,然後讓組件類繼承混合類,實現對混合類方法的繼承。

(6)綁定事件的方法不同。

EMAScript5 版本中,綁定的事件回調函數作用域是組件實例化對象。

EMAScript6 版本中,綁定的事件回調函數作用域是 null。

(7)父組件傳遞方法的作用域不同。

EMAScript5 版本中,作用域是父組件。 EMAScript6 版本中,變成了 null。

(8)組件方法作用域的修改方法不同。

EMAScript5 版本中,無法改變作用域。

EMAScript6 版本中,作用域是可以改變的。

17、React 中 D 算法的原理是什麼?

原理如下。

(1)節點之間的比較。

節點包括兩種類型:一種是 React 組件,另一種是 HTML 的 DOM。

如果節點類型不同,按以下方式比較。

如果 HTML DOM 不同,直接使用新的替換舊的。如果組件類型不同,也直接使用新的替換舊的。

如果 HTML DOM 類型相同,按以下方式比較。

在 React 裏樣式並不是一個純粹的字符串,而是一個對象,這樣在樣式發生改變時,只需要改變替換變化以後的樣式。修改完當前節點之後,遞歸處理該節點的子節點。

如果組件類型相同,按以下方式比較。

如果組件類型相同,使用 React 機制處理。一般使用新的 props 替換舊的 props,並在之後調用組件的 componentWillReceiveProps 方法,之前組件的 render 方法會被調用。

節點的比較機制開始遞歸作用於它的子節點。

(2)兩個列表之間的比較。

一個節點列表中的一個節點發生改變, React 無法很妤地處理這個問題。循環新舊兩個列表,並找出不同,這是 React 唯一的處理方法。

但是,有一個辦法可以把這個算法的複雜度降低。那就是在生成一個節點列表時給每個節點上添加一個 key。這個 key 只需要在這一個節點列表中唯一,不需要全局唯一。

(3)取捨

需要注意的是,上面的啓發式算法基於兩點假設。

類型相近的節點總是生成同樣的樹,而類型不同的節點也總是生成不同的樹

可以爲多次 render 都表現穩定的節點設置 key。

上面的節點之間的比較算法基本上就是基於這兩個假設而實現的。要提高 React 應用的效率,需要按照這兩點假設來開發。

18、概述一下 React 中的事件處理邏輯。

爲了解決跨瀏覽器兼容性問題, React 會將瀏覽器原生事件( Browser Native Event)封裝爲合成事件( Synthetic Event)並傳入設置的事件處理程序中。

這裏的合成事件提供了與原生事件相同的接口,不過它們屏蔽了底層瀏覽器的細節差異,保證了行爲的一致性。另外, React 並沒有直接將事件附着到子元素上,而是以單一事件監聽器的方式將所有的事件發送到頂層進行處理(基於事件委託原理)。

這樣 React 在更新 DOM 時就不需要考慮如何處理附着在 DOM 上的事件監聽器,最終達到優化性能的目的。

19、傳入 setstate 函數的第二個參數的作用是什麼?

第二個參數是一個函數,該函數會在 setState 函數調用完成並且組件開始重渲染時調用,可以用該函數來監聽渲染是否完成。

this .setstate ({ 
username:'有課前端網'
}, ( ) => console.log ( 're-rendered success. ' ) )

20、React 和 vue.js 的相似性和差異性是什麼?

相似性如下。

(1)都是用於創建 UI 的 JavaScript 庫。

(2)都是快速和輕量級的代碼庫(這裏指 React 核心庫)。

(3)都有基於組件的架構。

(4)都使用虛擬 DOM。

(5)都可以放在單獨的 HTML 文件中,或者放在 Webpack 設置的一個更復雜的模塊中。

(6)都有獨立但常用的路由器和狀態管理庫。

它們最大的區別在於 Vue. js 通常使用 HTML 模板文件,而 React 完全使用 JavaScript 創建虛擬 DOM。 Vue. js 還具有對於 “可變狀態” 的“ reactivity”的重新渲染的自動化檢測系統。

21、生命週期調用方法的順序是什麼?

React 生命週期分爲三大週期,11 個階段,生命週期方法調用順序分別如下。

(1)在創建期的五大階段,調用方法的順序如下。

(2)在存在期的五大階段,調用方法的順序如下。

(3)在銷燬期的一個階段,調用方法 componentWillUnmount,表示組件即將被銷毀。

22、使用狀態要注意哪些事情?

要注意以下幾點。

23、說說 React 組件開發中關於作用域的常見問題。

在 EMAScript5 語法規範中,關於作用域的常見問題如下。

(1)在 map 等方法的回調函數中,要綁定作用域 this(通過 bind 方法)。

(2)父組件傳遞給子組件方法的作用域是父組件實例化對象,無法改變。

(3)組件事件回調函數方法的作用域是組件實例化對象(綁定父組件提供的方法就是父組件實例化對象),無法改變。

在 EMAScript6 語法規範中,關於作用域的常見問題如下。

(1)當使用箭頭函數作爲 map 等方法的回調函數時,箭頭函數的作用域是當前組件的實例化對象(即箭頭函數的作用域是定義時的作用域),無須綁定作用域。

(2)事件回調函數要綁定組件作用域。

(3)父組件傳遞方法要綁定父組件作用域。

總之,在 EMAScript6 語法規範中,組件方法的作用域是可以改變的。

24、在 Redux 中使用 Action 要注意哪些問題?

在 Redux 中使用 Action 的時候, Action 文件裏儘量保持 Action 文件的純淨,傳入什麼數據就返回什麼數據,最妤把請求的數據和 Action 方法分離開,以保持 Action 的純淨。

25、在 Reducer 文件裏,對於返回的結果,要注意哪些問題?

在 Reducer 文件裏,對於返回的結果,必須要使用 Object.assign ( ) 來複制一份新的 state,否則頁面不會跟着數據刷新。

return Object. assign ( { }, state, {
type:action .type,
shouldNotPaint : true
})

26、如何使用 4.0 版本的 React Router?

React Router 4.0 版本中對 hashHistory 做了遷移,執行包安裝命令 npm install react-router-dom 後,按照如下代碼進行使用即可。

import  { HashRouter, Route, Redirect, Switch  } from " react-router-dom"; 
class App extends Component {
render ( ) {
return (
<div>
<Switch>
<Route path="/list"  componen t= { List }></Route>
<Route path="/detail/:id" component= { Detail } > </Route>
<Redirect from="/ "  to="/list"> </Redirect>
</Switch>
</div>
)
}
}
const routes = (
<HashRouter>
<App> </App>
</HashRouter>
)
render(routes, ickt);

27、在 ReactNative 中,如何解決 8081 端口號被佔用而提示無法訪問的問題?

在運行 react-native start 時添加參數 port 8082;在 package.json 中修改 “scripts” 中的參數,添加端口號;修改項目下的 node_modules \react-native\local- cli\server\server.js 文件配置中的 default 端口值。

28、在 ReactNative 中,如何解決 adb devices 找不到連接設備的問題?

在使用 Genymotion 時,首先需要在 SDK 的 platform-tools 中加入環境變量,然後在 Genymotion 中單擊 Setting,選擇 ADB 選項卡,單擊 Use custom Android SDK tools,瀏覽本地 SDK 的位置,單擊 OK 按鈕就可以了。啓動虛擬機後,在 cmd 中輸入 adb devices 可以查看設備。

29、React- Router 有幾種形式?

有以下幾種形式。

HashRouter,通過散列實現,路由要帶 #。

BrowerRouter,利用 HTML5 中 history API 實現,需要服務器端支持,兼容性不是很好。

30、在使用 React Router 時,如何獲取當前頁面的路由或瀏覽器中地址欄中的地址?

在當前組件的 props 中,包含 location 屬性對象,包含當前頁面路由地址信息,在 match 中存儲當前路由的參數等數據信息。可以直接通過 this .props 使用它們。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/lAQP3wNVuHpp-mt_RAqK1A