1. 程式人生 > >常見 React 面試題

常見 React 面試題

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 的最大特點,就是資料的"單向流動"。

  1. 使用者訪問 View

  2. View 發出使用者的 Action

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

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

  5. 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 進行判斷。