詳解 react 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
render props 模式是一種非常靈活複用性非常高的模式,它可以把特定行爲或功能封裝成一個組件,提供給其他組件使用讓其他組件擁有這樣的能力。他把組件可以動態渲染的地方暴露給外部,你不用再關注組件的內部實現,只要把數據通過函數傳出去就好。
使用場景:
-
通用業務邏輯的抽取
-
當兩個平級組件之間需要單向依賴的時候,比如兩個同級組件 A、B,A 組件需要跟隨 B 組件的內部狀態來改變自己的內部狀態,我們就說 A 依賴 B;或者 B 依賴 A
2.2 Hoc
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 高階組件相關的面試題
-
這怎麼在高階組件裏面訪問組件實例 **
答案見上面 -
你的項目中怎麼使用的高階組件 **
a. 項目中經常存在在配置系統中配置開關 / 全局常量,然後在頁面需要請求配置來做控制,如果在每個需要調用全局設置的地方都去請求一下接口,就會有一種不優雅的感覺,這個時候我就想到利用高階組件抽象一下。
b. 項目開發過程中,經常也會遇到需要對當前頁面的一些事件的默認執行做阻止,我們也可以寫一個高階組件等。 -
hooks 和 hoc 和 render props 有什麼不同?
它們之間最大的不同在於,後兩者僅僅是一種開發模式,而自定義的 hooks 是 react 提供的 API 模式,它既能更加自然的融入到 react 的渲染過程也更加符合 react 的函數編程理念。
- 介紹下常用的 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 佈局並同步觸發重渲染
-
描述下 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。
- hooks 中的坑,以及爲什麼?
-
不要在循環,條件或嵌套函數中調用 Hook,必須始終在 React 函數的頂層使用 Hook。這是因爲 React 需要利用調用順序來正確更新相應的狀態,以及調用相應的鉤子函數。一旦在循環或條件分支語句中調用 Hook,就容易導致調用順序的不一致性,從而產生難以預料到的後果。
-
使用 useState 時候,使用 push,pop,splice 等直接更改數組對象的坑,demo 中使用 push 直接更改數組無法獲取到新值,應該採用析構方式,但是在 class 裏面不會有這個問題。(這個的原因是 push,pop,splice 是直接修改原數組,react 會認爲 state 並沒有發生變化,無法更新) 這裏的坑很多的,經常出現的就是每次修改數組的時候:
const [firstData, setFirstData]: any = useState([]);
const handleFirstAdd = () => {
// let temp = firstData
// 不要這麼寫,直接修改原數組相當於沒有更新
let temp = [...firstData];
// 必須這麼寫,多層數組也要這麼寫
temp.push({
value: "",
});
setFirstData(temp);
};
function Indicatorfilter() {
let [num, setNums] = useState([0, 1, 2, 3]);
const test = () => {
// 這裏坑是直接採用push去更新num,setNums(num)是無法更新num的,必須使用num = [...num ,1]
num.push(1);
// num = [...num ,1]
setNums(num);
};
return (
<div class>
<div onClick={test}>測試</div>
<div>
{num.map((item, index) => (
<div key={index}>{item}</div>
))}
</div>
</div>
);
}
class Indicatorfilter extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
nums: [1, 2, 3],
};
this.test = this.test.bind(this);
}
test() {
// class採用同樣的方式是沒有問題的
this.state.nums.push(1);
this.setState({
nums: this.state.nums,
});
}
render() {
let { nums } = this.state;
return (
<div>
<div onClick={this.test}>測試</div>
<div>
{nums.map((item: any, index: number) => (
<div key={index}>{item}</div>
))}
</div>
</div>
);
}
}
useState 設置狀態的時候,只有第一次生效,後期需要更新狀態,必須通過 useEffect
TableDeail 是一個公共組件,在調用它的父組件裏面,我們通過 set 改變 columns 的值,以爲傳遞給 TableDeail 的 columns 是最新的值,所以 tabColumn 每次也是最新的值,但是實際 tabColumn 是最開始的值,不會隨着 columns 的更新而更新
const TableDeail = ({
columns,
}:TableData) => {
const [tabColumn, setTabColumn] = useState(columns)
}
正確的做法是通過 useEffect 改變這個值
const TableDeail = ({
columns,
}:TableData) => {
const [tabColumn, setTabColumn] = useState(columns)
useEffect(() =>{setTabColumn(columns)},[columns])
}
原文地址: https://blog.csdn.net/kellywong/article/details/106430977
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ebdz3NeSXFYNrDqmKDkahQ