大廠面經---詳解react hooks面試題(含高階組件)

一. 面試中出現的關於hooks的題目

1. 簡單介紹下什麼是hooks,hooks產生的背景?hooks的優點?

hooks是針對在使用react時存在以下問題而產生的:

  • 組件之間複用狀態邏輯很難,在hooks之前,實現組件複用,一般採用高階組件和 Render Props,它們本質是將複用邏輯提升到父組件中,很容易產生很多包裝組件,帶來嵌套地域。
  • 組件邏輯變得越來越複雜,尤其是生命週期函數中常常包含一些不相關的邏輯,完全不相關的代碼卻在同一個方法中組合在一起。如此很容易產生 bug,並且導致邏輯不一致。
  • 複雜的class組件,使用class組件,需要理解 JavaScript 中 this 的工作方式,不能忘記綁定事件處理器等操作,代碼複雜且冗餘。除此之外,class組件也會讓一些react優化措施失效。

針對上面提到的問題,react團隊研發了hooks,它主要有兩方面作用:

  • 用於在函數組件中引入狀態管理和生命週期方法
  • 取代高階組件和render props來實現抽象和可重用性

優點也很明顯:

  • 避免在被廣泛使用的函數組件在後期迭代過程中,需要承擔一些副作用,而必須重構成類組件,它幫助函數組件引入狀態管理和生命週期方法。
  • Hooks 出現之後,我們將複用邏輯提取到組件頂層,而不是強行提升到父組件中。這樣就能夠避免 HOC 和 Render Props 帶來的「嵌套地域」
  • 避免上面陳述的class組件帶來的那些問題

2. 知道hoc和render props嗎,它們有什麼作用?有什麼弊端?

Render Props 組件和高階組件主要用來實現抽象和可重用性。
弊端就是高階組件和 Render Props 本質上都是將複用邏輯提升到父組件中,很容易產生很多包裝組件,帶來的「嵌套地域」。由於所有抽象邏輯都被其他 React 組件所隱藏,應用變成了一棵沒有可讀性的組件樹。而那些可見的組件也很難在瀏覽器的 DOM 中進行跟蹤。

2.1 Render Props

render props demo參考
什麼是Render Props
render props模式是一種非常靈活複用性非常高的模式,它可以把特定行爲或功能封裝成一個組件,提供給其他組件使用讓其他組件擁有這樣的能力。他把組件可以動態渲染的地方暴露給外部,你不用再關注組件的內部實現,只要把數據通過函數傳出去就好。
使用場景:

  • 通用業務邏輯的抽取
  • 當兩個平級組件之間需要單向依賴的時候,比如兩個同級組件A、B,A組件需要跟隨B組件的內部狀態來改變自己的內部狀態,我們就說A依賴B;或者B依賴A

demo參考
後邊補充

2.2 Hoc

hoc的應用demo
hoc是 React 中用於重用組件邏輯的高級技術,它是一個函數,能夠接受一個組件並返回一個新的組件。
實現高階組件的兩種方式:

  • 屬性代理。高階組件通過包裹的React組件來操作props
  • 反向繼承。高階組件繼承於被包裹的React組件
2.2.1屬性代理
a. 什麼是屬性代理

屬性代理組件繼承自React.Component,通過傳遞給被包裝的組件props得名

// 屬性代理,把高階組件接收到的屬性傳遞給傳進來的組件
function HOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}
b. 屬性代理的用途
  • 更改 props,可以對傳遞的包裹組件的WrappedComponent的props進行控制
  • 通過 refs 獲取組件實例
/*
可以通過 ref 獲取關鍵詞 this(WrappedComponent 的實例)
當 WrappedComponent 被渲染後,ref 上的回調函數 proc 將會執行,此時就有了這個 WrappedComponent 的實例的引用
*/
function refsHOC(WrappedComponent) {
  return class RefsHOC extends React.Component {
    proc(wrappedComponentInstance) {
      wrappedComponentInstance.method()
    }
    render() {
      const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
      return <WrappedComponent {...props}/>
    }
  }
}
  • 把 WrappedComponent 與其它 elements 包裝在一起
2.1.2 反向繼承

反向繼承是繼承自傳遞過來的組件

function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      return super.render()
    }
  }
}

反向繼承允許高階組件通過 this 關鍵詞獲取 WrappedComponent,意味着它可以獲取到 state,props,組件生命週期(component lifecycle)鉤子,以及渲染方法(render),所以我們主要用它來做渲染劫持,比如在渲染方法中讀取或更改 React Elements tree,或者有條件的渲染等。

2.1.3 高階組件相關的面試題

1. 這怎麼在高階組件裏面訪問組件實例
答案見上面

2. 你的項目中怎麼使用的高階組件
a. 項目中經常存在在配置系統中配置開關/全局常量,然後在頁面需要請求配置來做控制,如果在每個需要調用全局設置的地方都去請求一下接口,就會有一種不優雅的感覺,這個時候我就想到利用高階組件抽象一下。
b. 項目開發過程中,經常也會遇到需要對當前頁面的一些事件的默認執行做阻止,我們也可以寫一個高階組件等。

3. hooks和hoc和render props有什麼不同?

它們之間最大的不同在於,後兩者僅僅是一種開發模式,而自定義的hooks是react提供的API模式,它既能更加自然的融入到react的渲染過程也更加符合react的函數編程理念。

4. 介紹下常用的hooks?

  • useState(),狀態鉤子。爲函數組建提供內部狀態
// 我們實現一個簡易版的useState
let memoizedStates = [ ]  // 多個useState 時需要使用數組來存
let index = 0
function useState (initialState) {
   memoizedStates[index] = memoizedStates[index] || initialState
   let currentIndex = index;
   function setState (newState) {
      memoizedStates[currentIndex] = newState
      render()
   }
   return [memoizedStates[index++], setState]
}
  • useContext(),共享鉤子。該鉤子的作用是,在組件之間共享狀態。 可以解決react逐層通過Porps傳遞數據,它接受React.createContext()的返回結果作爲參數,使用useContext將不再需要Provider 和 Consumer。
  • useReducer(),Action 鉤子。useReducer() 提供了狀態管理,其基本原理是通過用戶在頁面中發起action, 從而通過reducer方法來改變state, 從而實現頁面和狀態的通信。使用很像redux
  • useEffect(),副作用鉤子。它接收兩個參數, 第一個是進行的異步操作, 第二個是數組,用來給出Effect的依賴項
  • useRef(),獲取組件的實例;渲染週期之間共享數據的存儲(state不能存儲跨渲染週期的數據,因爲state的保存會觸發組件重渲染)
    useRef傳入一個參數initValue,並創建一個對象{ current: initValue }給函數組件使用,在整個生命週期中該對象保持不變。
  • useMemo和useCallback:可緩存函數的引用或值,useMemo用在計算值的緩存,注意不用濫用。經常用在下面兩種場景(要保持引用相等;對於組件內部用到的 object、array、函數等,如果用在了其他 Hook 的依賴數組中,或者作爲 props 傳遞給了下游組件,應該使用 useMemo/useCallback)
  • useLayoutEffect:會在所有的 DOM 變更之後同步調用 effect,可以使用它來讀取 DOM 佈局並同步觸發重渲染

5. 描述下hooks下怎麼模擬生命週期函數,模擬的生命週期和class中的生命週期有什麼區別嗎?

// componentDidMount,必須加[],不然會默認每次渲染都執行
useEffect(()=>{
}, [])

// componentDidUpdate
useEffect(()=>{
document.title = `You clicked ${count} times`;
return()=>{
// 以及 componentWillUnmount 執行的內容       
}
}, [count])

//  shouldComponentUpdate, 只有 Parent 組件中的 count state 更新了,Child 纔會重新渲染,否則不會。
function Parent() {
  	const [count,setCount] = useState(0);
  	const child = useMemo(()=> <Child count={count} />, [count]);
  	return <>{count}</>
}

function Child(props) {
    return <div>Count:{props.count}</div>
}

這裏有一個點需要注意,就是默認的useEffect(不帶[])中return的清理函數,它和componentWillUnmount有本質區別的,默認情況下return,在每次useEffect執行前都會執行,並不是只有組件卸載的時候執行。
useEffect在副作用結束之後,會延遲一段時間執行,並非同步執行,和compontDidMount有本質區別。遇到dom操作,最好使用useLayoutEffect。

6. hooks中的坑,以及爲什麼?

  • 不要在循環,條件或嵌套函數中調用Hook,必須始終在React函數的頂層使用Hook。這是因爲React需要利用調用順序來正確更新相應的狀態,以及調用相應的鉤子函數。一旦在循環或條件分支語句中調用Hook,就容易導致調用順序的不一致性,從而產生難以預料到的後果。
  • 使用useState時候,使用push,pop,splice等直接更改數組對象的坑,demo中使用push直接更改數組會報錯,應該採用析構方式,參考demo裏面counts變量。
  • 必包帶來的坑,參考demo裏面的state變量
    因爲每次 render 都有一份新的狀態,因此上述代碼中的 setTimeout 使用產生了一個閉包,捕獲了每次 render 後的 state,也就導致了輸出了 0、1、2。如果你希望輸出的內容是最新的 state 的話,可以通過 useRef 來保存 state。前文講過 ref 在組件中只存在一份,無論何時使用它的引用都不會產生變化,因此可以來解決閉包引發的問題。
  • 濫用useContent

7. useState中的第二個參數更新狀態和class中的this.setState區別?

8. useEffect和useLayoutEffect區別?

useEffect是render結束後,callback函數執行,但是不會阻斷瀏覽器的渲染,算是某種異步的方式吧。但是class的componentDidMount 和componentDidUpdate是同步的,在render結束後就運行,useEffect在大部分場景下都比class的方式性能更好.

useLayoutEffect裏面的callback函數會在DOM更新完成後立即執行,但是會在瀏覽器進行任何繪製之前運行完成,阻塞了瀏覽器的繪製.

9. 使用hooks實現一個計時器?(demo)

計時器demo
注意第一個計時器錯誤的寫法,在useEffect裏面重複定義setInterval,正確寫法是setInterval只定義一次,它的回調函數保存狀態的更新,重點是把count更新和setInterval定義分開。

10. 使用hooks實現自定義hooks, 一個計算組件從掛載到卸載的時間?(demo,hooks抽取公共邏輯的應用)

參考:
深入理解 React 高階組件
常用的hooks

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章