1. 程式人生 > 其它 >react面試題總結一波,以備不時之需

react面試題總結一波,以備不時之需

React元件的建構函式有什麼作用?它是必須的嗎?

建構函式主要用於兩個目的:

  • 通過將物件分配給this.state來初始化本地狀態
  • 將事件處理程式方法繫結到例項上

所以,當在React class中需要設定state的初始值或者繫結事件時,需要加上建構函式,官方Demo:

class LikeButton extends React.Component {
  constructor() {
    super();
    this.state = {
      liked: false
    };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState({liked: !this.state.liked});
  }
  render() {
    const text = this.state.liked ? 'liked' : 'haven\'t liked';
    return (
      <div onClick={this.handleClick}>
        You {text} this. Click to toggle.      </div>
    );
  }
}
ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);

建構函式用來新建父類的this物件;子類必須在constructor方法中呼叫super方法;否則新建例項時會報錯;因為子類沒有自己的this物件,而是繼承父類的this物件,然後對其進行加工。如果不呼叫super方法;子類就得不到this物件。

注意:

  • constructor () 必須配上 super(), 如果要在constructor 內部使用 this.props 就要 傳入props , 否則不用
  • JavaScript中的 bind 每次都會返回一個新的函式, 為了效能等考慮, 儘量在constructor中繫結事件

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

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

什麼原因會促使你脫離 create-react-app 的依賴

當你想去配置 webpack 或 babel presets。

何為 action

Actions 是一個純 javascript 物件,它們必須有一個 type 屬性表明正在執行的 action 的型別。實質上,action 是將資料從應用程式傳送到 store 的有效載荷。

diff演算法如何比較?

  • 只對同級比較,跨層級的dom不會進行復用
  • 不同型別節點生成的dom樹不同,此時會直接銷燬老節點及子孫節點,並新建節點
  • 可以通過key來對元素diff的過程提供複用的線索
  • 單節點diff
  • 單點diff有如下幾種情況:
  • key和type相同表示可以複用節點
  • key不同直接標記刪除節點,然後新建節點
  • key相同type不同,標記刪除該節點和兄弟節點,然後新建立節點

元件通訊的方式有哪些

  • ⽗元件向⼦元件通訊: ⽗元件可以向⼦元件通過傳 props 的⽅式,向⼦元件進⾏通訊
  • ⼦元件向⽗元件通訊: props+回撥的⽅式,⽗元件向⼦元件傳遞props進⾏通訊,此props為作⽤域為⽗元件⾃身的函 數,⼦元件調⽤該函式,將⼦元件想要傳遞的資訊,作為引數,傳遞到⽗元件的作⽤域中
  • 兄弟元件通訊: 找到這兩個兄弟節點共同的⽗節點,結合上⾯兩種⽅式由⽗節點轉發資訊進⾏通訊
  • 跨層級通訊: Context 設計⽬的是為了共享那些對於⼀個元件樹⽽⾔是“全域性”的資料,例如當前認證的⽤戶、主題或⾸選語⾔,對於跨越多層的全域性資料通過 Context 通訊再適合不過
  • 釋出訂閱模式: 釋出者釋出事件,訂閱者監聽事件並做出反應,我們可以通過引⼊event模組進⾏通訊
  • 全域性狀態管理⼯具: 藉助Redux或者Mobx等全域性狀態管理⼯具進⾏通訊,這種⼯具會維護⼀個全域性狀態中⼼Store,並根據不同的事件產⽣新的狀態

參考 前端進階面試題詳細解答

什麼是受控元件和非受控元件

  • 受控元件:

    沒有維持自己的狀態

    資料由付元件控制

    通過props獲取當前值,然後通過回撥函式通知更改

  • 非受控元件

    保持這個自己的狀態

    資料有DOM控制

    refs用於獲取其當前值

React的虛擬DOM和Diff演算法的內部實現

傳統 diff 演算法的時間複雜度是 O(n^3),這在前端 render 中是不可接受的。為了降低時間複雜度,react 的 diff 演算法做了一些妥協,放棄了最優解,最終將時間複雜度降低到了 O(n)。

那麼 react diff 演算法做了哪些妥協呢?,參考如下:

  1. tree diff:只對比同一層的 dom 節點,忽略 dom 節點的跨層級移動

如下圖,react 只會對相同顏色方框內的 DOM 節點進行比較,即同一個父節點下的所有子節點。當發現節點不存在時,則該節點及其子節點會被完全刪除掉,不會用於進一步的比較。

這樣只需要對樹進行一次遍歷,便能完成整個 DOM 樹的比較。

這就意味著,如果 dom 節點發生了跨層級移動,react 會刪除舊的節點,生成新的節點,而不會複用。

  1. component diff:如果不是同一型別的元件,會刪除舊的元件,建立新的元件
  1. element diff:對於同一層級的一組子節點,需要通過唯一 id 進行來區分
  • 如果沒有 id 來進行區分,一旦有插入動作,會導致插入位置之後的列表全部重新渲染
  • 這也是為什麼渲染列表時為什麼要使用唯一的 key。

React如何獲取元件對應的DOM元素?

可以用ref來獲取某個子節點的例項,然後通過當前class元件例項的一些特定屬性來直接獲取子節點例項。ref有三種實現方法:

  • 字串格式:字串格式,這是React16版本之前用得最多的,例如:<p ref="info">span</p>

  • 函式格式:ref對應一個方法,該方法有一個引數,也就是對應的節點例項,例如:<p ref={ele => this.info = ele}></p>

  • createRef方法:React 16提供的一個API,使用React.createRef()來實現       

如何配置 React-Router 實現路由切換

(1)使用<Route> 元件

路由匹配是通過比較 <Route> 的 path 屬性和當前地址的 pathname 來實現的。當一個 <Route> 匹配成功時,它將渲染其內容,當它不匹配時就會渲染 null。沒有路徑的 <Route> 將始終被匹配。

// when location = { pathname: '/about' }
<Route path='/about' component={About}/> // renders <About/>
<Route path='/contact' component={Contact}/> // renders null
<Route component={Always}/> // renders <Always/>

(2)結合使用 <Switch> 元件和 <Route> 元件

<Switch> 用於將 <Route> 分組。

<Switch>
    <Route exact path="/" component={Home} />
    <Route path="/about" component={About} />
    <Route path="/contact" component={Contact} />
</Switch>

<Switch> 不是分組 <Route> 所必須的,但他通常很有用。 一個 <Switch> 會遍歷其所有的子 <Route>元素,並僅渲染與當前地址匹配的第一個元素。

(3)使用 <Link>、 <NavLink>、<Redirect> 元件

<Link> 元件來在你的應用程式中建立連結。無論你在何處渲染一個<Link> ,都會在應用程式的 HTML 中渲染錨(<a>)。

<Link to="/">Home</Link>   
// <a href='/'>Home</a>

是一種特殊型別的 當它的 to屬性與當前地址匹配時,可以將其定義為"活躍的"。

// location = { pathname: '/react' }
<NavLink to="/react" activeClassName="hurray">
    React
</NavLink>
// <a href='/react' className='hurray'>React</a>

當我們想強制導航時,可以渲染一個<Redirect>,當一個<Redirect>渲染時,它將使用它的to屬性進行定向。

Redux Thunk 的作用是什麼

Redux thunk 是一個允許你編寫返回一個函式而不是一個 action 的 actions creators 的中介軟體。如果滿足某個條件,thunk 則可以用來延遲 action 的派發(dispatch),這可以處理非同步 action 的派發(dispatch)。

React實現的移動應用中,如果出現卡頓,有哪些可以考慮的優化方案

  • 增加shouldComponentUpdate鉤子對新舊props進行比較,如果值相同則阻止更新,避免不必要的渲染,或者使用PureReactComponent替代Component,其內部已經封裝了shouldComponentUpdate的淺比較邏輯
  • 對於列表或其他結構相同的節點,為其中的每一項增加唯一key屬性,以方便Reactdiff演算法中對該節點的複用,減少節點的建立和刪除操作
  • render函式中減少類似onClick={() => {doSomething()}}的寫法,每次呼叫render函式時均會建立一個新的函式,即使內容沒有發生任何變化,也會導致節點沒必要的重渲染,建議將函式儲存在元件的成員物件中,這樣只會建立一次
  • 元件的props如果需要經過一系列運算後才能拿到最終結果,則可以考慮使用reselect庫對結果進行快取,如果props值未發生變化,則結果直接從快取中拿,避免高昂的運算代價
  • webpack-bundle-analyzer分析當前頁面的依賴包,是否存在不合理性,如果存在,找到優化點並進行優化

Diff 的瓶頸以及 React 的應對

由於 diff 操作本身會帶來效能上的損耗,在 React 文件中提到過,即使最先進的演算法中,將前後兩棵樹完全比對的演算法複雜度為O(n3),其中 n 為樹中元素的數量。

如果 React 使用了該演算法,那麼僅僅一千個元素的頁面所需要執行的計算量就是十億的量級,這無疑是無法接受的。

為了降低演算法的複雜度,React 的 diff 會預設三個限制:

  1. 只對同級元素進行 diff 比對。如果一個元素節點在前後兩次更新中跨越了層級,那麼 React 不會嘗試複用它
  2. 兩個不同型別的元素會產生出不同的樹。如果元素由 div 變成 p,React 會銷燬 div 及其子孫節點,並新建 p 及其子孫節點
  3. 開發者可以通過 key 來暗示哪些子元素在不同的渲染下能保持穩定

fetch封裝

npm install whatwg-fetch --save  // 適配其他瀏覽器
npm install es6-promise

export const handleResponse = (response) => {
  if (response.status === 403 || response.status === 401) {
    const oauthurl = response.headers.get('locationUrl');
    if (!_.isEmpty(oauthUrl)) {
      window.location.href = oauthurl;
      return;
    }
  }
  if (!response.ok) {
    return getErrorMessage(response).then(errorMessage => apiError(response.status, errorMessage));
  }
  if (isJson(response)) {
    return response.json();
  }
  if (isText(response)) {
    return response.text();
  }

  return response.blob();
};

const httpRequest = {
  request: ({
    method, headers, body, path, query,
  }) => {
    const options = {};
    let url = path;
    if (method) {
      options.method = method;
    }
    if (headers) {
      options.headers = {...options.headers,...headers};
    }
    if (body) {
      options.body = body;
    }
    if (query) {
      const params = Object.keys(query)
        .map(k => `${k}=${query[k]}`)
        .join('&');
      url = url.concat(`?${params}`);
    }
    return fetch(url, Object.assign({}, options, { credentials: 'same-origin' })).then(handleResponse);
  },
};

export default httpRequest;

什麼是上下文Context

Context 通過元件樹提供了一個傳遞資料的方法,從而避免了在每一個層級手動的傳遞 props 屬性。

  • 用法:在父元件上定義getChildContext方法,返回一個物件,然後它的子元件就可以通過this.context屬性來獲取
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class Header extends Component{
    render() {
        return (
            <div>
                <Title/>
            </div>
        )
    }
}
class Title extends Component{
    static contextTypes={
        color:PropTypes.string
    }
    render() {
        return (
            <div style={{color:this.context.color}}>
                Title
            </div>
        )
    }
}
class Main extends Component{
    render() {
        return (
            <div>
                <Content>
                </Content>
            </div>
        )
    }
}
class Content extends Component{
    static contextTypes={
        color: PropTypes.string,
        changeColor:PropTypes.func
    }
    render() {
        return (
            <div style={{color:this.context.color}}>
                Content
                <button onClick={()=>this.context.changeColor('green')}>綠色</button>
                <button onClick={()=>this.context.changeColor('orange')}>橙色</button>
            </div>
        )
    }
}
class Page extends Component{
    constructor() {
        super();
        this.state={color:'red'};
    }
    static childContextTypes={
        color: PropTypes.string,
        changeColor:PropTypes.func
    }
    getChildContext() {
        return {
            color: this.state.color,
            changeColor:(color)=>{
                this.setState({color})
            }
        }
    }
    render() {
        return (
            <div>
                <Header/>
                <Main/>
            </div>
        )
    }
}
ReactDOM.render(<Page/>,document.querySelector('#root'));

何為 Children

在JSX表示式中,一個開始標籤(比如<a>)和一個關閉標籤(比如</a>)之間的內容會作為一個特殊的屬性props.children被自動傳遞給包含著它的元件。

這個屬性有許多可用的方法,包括 React.Children.mapReact.Children.forEachReact.Children.countReact.Children.onlyReact.Children.toArray

componentWillReceiveProps呼叫時機

  • 已經被廢棄掉
  • 當props改變的時候才呼叫,子元件第二次接收到props的時候

React 效能優化

  • shouldCompoentUpdate
  • pureComponent 自帶shouldCompoentUpdate的淺比較優化
  • 結合Immutable.js達到最優

說說你用react有什麼坑點?

1. JSX做表示式判斷時候,需要強轉為boolean型別

如果不使用 !!b 進行強轉資料型別,會在頁面裡面輸出 0

render() {
  const b = 0;
  return <div>
    {
      !!b && <div>這是一段文字</div>
    }
  </div>
}

2. 儘量不要在 componentWillReviceProps 裡使用 setState,如果一定要使用,那麼需要判斷結束條件,不然會出現無限重渲染,導致頁面崩潰

3. 給元件新增ref時候,儘量不要使用匿名函式,因為當元件更新的時候,匿名函式會被當做新的prop處理,讓ref屬性接受到新函式的時候,react內部會先清空ref,也就是會以null為回撥引數先執行一次ref這個props,然後在以該元件的例項執行一次ref,所以用匿名函式做ref的時候,有的時候去ref賦值後的屬性會取到null

4. 遍歷子節點的時候,不要用 index 作為元件的 key 進行傳入

React Hooks 解決了哪些問題?

React Hooks 主要解決了以下問題:

(1)在元件之間複用狀態邏輯很難

React 沒有提供將可複用性行為“附加”到元件的途徑(例如,把元件連線到 store)解決此類問題可以使用 render props 和 高階元件。但是這類方案需要重新組織元件結構,這可能會很麻煩,並且會使程式碼難以理解。由 providers,consumers,高階元件,render props 等其他抽象層組成的元件會形成“巢狀地獄”。儘管可以在 DevTools 過濾掉它們,但這說明了一個更深層次的問題:React 需要為共享狀態邏輯提供更好的原生途徑。

可以使用 Hook 從元件中提取狀態邏輯,使得這些邏輯可以單獨測試並複用。Hook 使我們在無需修改元件結構的情況下複用狀態邏輯。 這使得在元件間或社群內共享 Hook 變得更便捷。

(2)複雜元件變得難以理解

在元件中,每個生命週期常常包含一些不相關的邏輯。例如,元件常常在 componentDidMount 和 componentDidUpdate 中獲取資料。但是,同一個 componentDidMount 中可能也包含很多其它的邏輯,如設定事件監聽,而之後需在 componentWillUnmount 中清除。相互關聯且需要對照修改的程式碼被進行了拆分,而完全不相關的程式碼卻在同一個方法中組合在一起。如此很容易產生 bug,並且導致邏輯不一致。

在多數情況下,不可能將元件拆分為更小的粒度,因為狀態邏輯無處不在。這也給測試帶來了一定挑戰。同時,這也是很多人將 React 與狀態管理庫結合使用的原因之一。但是,這往往會引入了很多抽象概念,需要你在不同的檔案之間來回切換,使得複用變得更加困難。

為了解決這個問題,Hook 將元件中相互關聯的部分拆分成更小的函式(比如設定訂閱或請求資料),而並非強制按照生命週期劃分。你還可以使用 reducer 來管理元件的內部狀態,使其更加可預測。

(3)難以理解的 class

除了程式碼複用和程式碼管理會遇到困難外,class 是學習 React 的一大屏障。我們必須去理解 JavaScript 中 this 的工作方式,這與其他語言存在巨大差異。還不能忘記繫結事件處理器。沒有穩定的語法提案,這些程式碼非常冗餘。大家可以很好地理解 props,state 和自頂向下的資料流,但對 class 卻一籌莫展。即便在有經驗的 React 開發者之間,對於函式元件與 class 元件的差異也存在分歧,甚至還要區分兩種元件的使用場景。

為了解決這些問題,Hook 使你在非 class 的情況下可以使用更多的 React 特性。 從概念上講,React 元件一直更像是函式。而 Hook 則擁抱了函式,同時也沒有犧牲 React 的精神原則。Hook 提供了問題的解決方案,無需學習複雜的函式式或響應式程式設計技術