大廠面經(jīng)---詳解react hooks面試題【React系列02】
關(guān)注公眾號(hào) 前端人,回復(fù)“加群”
添加無(wú)廣告優(yōu)質(zhì)學(xué)習(xí)群
1. 簡(jiǎn)單介紹下什么是hooks,hooks產(chǎn)生的背景?hooks的優(yōu)點(diǎn)?
hooks是針對(duì)在使用react時(shí)存在以下問(wèn)題而產(chǎn)生的:
組件之間復(fù)用狀態(tài)邏輯很難,在hooks之前,實(shí)現(xiàn)組件復(fù)用,一般采用高階組件和 Render Props,它們本質(zhì)是將復(fù)用邏輯提升到父組件中,很容易產(chǎn)生很多包裝組件,帶來(lái)嵌套地域。 組件邏輯變得越來(lái)越復(fù)雜,尤其是生命周期函數(shù)中常常包含一些不相關(guān)的邏輯,完全不相關(guān)的代碼卻在同一個(gè)方法中組合在一起。如此很容易產(chǎn)生 bug,并且導(dǎo)致邏輯不一致。 復(fù)雜的class組件,使用class組件,需要理解 JavaScript 中 this 的工作方式,不能忘記綁定事件處理器等操作,代碼復(fù)雜且冗余。除此之外,class組件也會(huì)讓一些react優(yōu)化措施失效。
針對(duì)上面提到的問(wèn)題,react團(tuán)隊(duì)研發(fā)了hooks,它主要有兩方面作用:
用于在函數(shù)組件中引入狀態(tài)管理和生命周期方法 取代高階組件和render props來(lái)實(shí)現(xiàn)抽象和可重用性
優(yōu)點(diǎn)也很明顯:
避免在被廣泛使用的函數(shù)組件在后期迭代過(guò)程中,需要承擔(dān)一些副作用,而必須重構(gòu)成類組件,它幫助函數(shù)組件引入狀態(tài)管理和生命周期方法。
Hooks 出現(xiàn)之后,我們將復(fù)用邏輯提取到組件頂層,而不是強(qiáng)行提升到父組件中。這樣就能夠避免 HOC 和 Render Props 帶來(lái)的「嵌套地域」
避免上面陳述的class組件帶來(lái)的那些問(wèn)題
2. 知道hoc和render props嗎,它們有什么作用?有什么弊端?
Render Props 組件和高階組件主要用來(lái)實(shí)現(xiàn)抽象和可重用性。
弊端就是高階組件和 Render Props 本質(zhì)上都是將復(fù)用邏輯提升到父組件中,很容易產(chǎn)生很多包裝組件,帶來(lái)的「嵌套地域」。
由于所有抽象邏輯都被其他 React 組件所隱藏,應(yīng)用變成了一棵沒有可讀性的組件樹。而那些可見的組件也很難在瀏覽器的 DOM 中進(jìn)行跟蹤。
2.1 Render Props
什么是Render Props
render props模式是一種非常靈活復(fù)用性非常高的模式,它可以把特定行為或功能封裝成一個(gè)組件,提供給其他組件使用讓其他組件擁有這樣的能力。他把組件可以動(dòng)態(tài)渲染的地方暴露給外部,你不用再關(guān)注組件的內(nèi)部實(shí)現(xiàn),只要把數(shù)據(jù)通過(guò)函數(shù)傳出去就好。
使用場(chǎng)景:
通用業(yè)務(wù)邏輯的抽取 當(dāng)兩個(gè)平級(jí)組件之間需要單向依賴的時(shí)候,比如兩個(gè)同級(jí)組件A、B,A組件需要跟隨B組件的內(nèi)部狀態(tài)來(lái)改變自己的內(nèi)部狀態(tài),我們就說(shuō)A依賴B;或者B依賴A
2.2 Hoc
hoc是 React 中用于重用組件邏輯的高級(jí)技術(shù),它是一個(gè)函數(shù),能夠接受一個(gè)組件并返回一個(gè)新的組件。
實(shí)現(xiàn)高階組件的兩種方式:
屬性代理。高階組件通過(guò)包裹的React組件來(lái)操作props 反向繼承。高階組件繼承于被包裹的React組件
2.2.1屬性代理
a. 什么是屬性代理
屬性代理組件繼承自React.Component,通過(guò)傳遞給被包裝的組件props得名
// 屬性代理,把高階組件接收到的屬性傳遞給傳進(jìn)來(lái)的組件
function HOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}
b. 屬性代理的用途
更改 props,可以對(duì)傳遞的包裹組件的WrappedComponent的props進(jìn)行控制 通過(guò) refs 獲取組件實(shí)例
/*
可以通過(guò) ref 獲取關(guān)鍵詞 this(WrappedComponent 的實(shí)例)
當(dāng) WrappedComponent 被渲染后,ref 上的回調(diào)函數(shù) proc 將會(huì)執(zhí)行,此時(shí)就有了這個(gè) WrappedComponent 的實(shí)例的引用
*/
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 反向繼承
反向繼承是繼承自傳遞過(guò)來(lái)的組件
function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
return super.render()
}
}
}
反向繼承允許高階組件通過(guò) this 關(guān)鍵詞獲取 WrappedComponent,意味著它可以獲取到 state,props,組件生命周期(component lifecycle)鉤子,以及渲染方法(render),所以我們主要用它來(lái)做渲染劫持,比如在渲染方法中讀取或更改 React Elements tree,或者有條件的渲染等。
2.1.3 高階組件相關(guān)的面試題
這怎么在高階組件里面訪問(wèn)組件實(shí)例**
答案見上面你的項(xiàng)目中怎么使用的高階組件**
a. 項(xiàng)目中經(jīng)常存在在配置系統(tǒng)中配置開關(guān)/全局常量,然后在頁(yè)面需要請(qǐng)求配置來(lái)做控制,如果在每個(gè)需要調(diào)用全局設(shè)置的地方都去請(qǐng)求一下接口,就會(huì)有一種不優(yōu)雅的感覺,這個(gè)時(shí)候我就想到利用高階組件抽象一下。
b. 項(xiàng)目開發(fā)過(guò)程中,經(jīng)常也會(huì)遇到需要對(duì)當(dāng)前頁(yè)面的一些事件的默認(rèn)執(zhí)行做阻止,我們也可以寫一個(gè)高階組件等。hooks和hoc和render props有什么不同?
它們之間最大的不同在于,后兩者僅僅是一種開發(fā)模式,而自定義的hooks是react提供的API模式,它既能更加自然的融入到react的渲染過(guò)程也更加符合react的函數(shù)編程理念。
介紹下常用的hooks?
useState(),狀態(tài)鉤子。為函數(shù)組建提供內(nèi)部狀態(tài)
// 我們實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的useState
let memoizedStates = [ ] // 多個(gè)useState 時(shí)需要使用數(shù)組來(lái)存
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(),共享鉤子。該鉤子的作用是,在組件之間共享狀態(tài)。可以解決react逐層通過(guò)Porps傳遞數(shù)據(jù),它接受React.createContext()的返回結(jié)果作為參數(shù),使用useContext將不再需要Provider 和 Consumer。
useReducer(),Action 鉤子。useReducer() 提供了狀態(tài)管理,其基本原理是通過(guò)用戶在頁(yè)面中發(fā)起action, 從而通過(guò)reducer方法來(lái)改變state, 從而實(shí)現(xiàn)頁(yè)面和狀態(tài)的通信。使用很像redux
useEffect(),副作用鉤子。它接收兩個(gè)參數(shù), 第一個(gè)是進(jìn)行的異步操作, 第二個(gè)是數(shù)組,用來(lái)給出Effect的依賴項(xiàng)
useRef(),獲取組件的實(shí)例;渲染周期之間共享數(shù)據(jù)的存儲(chǔ)(state不能存儲(chǔ)跨渲染周期的數(shù)據(jù),因?yàn)閟tate的保存會(huì)觸發(fā)組件重渲染)
useRef傳入一個(gè)參數(shù)initValue,并創(chuàng)建一個(gè)對(duì)象{ current: initValue }給函數(shù)組件使用,在整個(gè)生命周期中該對(duì)象保持不變。
useMemo和useCallback:可緩存函數(shù)的引用或值,useMemo用在計(jì)算值的緩存,注意不用濫用。經(jīng)常用在下面兩種場(chǎng)景(要保持引用相等;對(duì)于組件內(nèi)部用到的 object、array、函數(shù)等,如果用在了其他 Hook 的依賴數(shù)組中,或者作為 props 傳遞給了下游組件,應(yīng)該使用 useMemo/useCallback)
useLayoutEffect:會(huì)在所有的 DOM 變更之后同步調(diào)用 effect,可以使用它來(lái)讀取 DOM 布局并同步觸發(fā)重渲染
描述下hooks下怎么模擬生命周期函數(shù),模擬的生命周期和class中的生命周期有什么區(qū)別嗎?
// componentDidMount,必須加[],不然會(huì)默認(rèn)每次渲染都執(zhí)行
useEffect(()=>{
}, [])
// componentDidUpdate
useEffect(()=>{
document.title = `You clicked ${count} times`;
return()=>{
// 以及 componentWillUnmount 執(zhí)行的內(nèi)容
}
}, [count])
// shouldComponentUpdate, 只有 Parent 組件中的 count state 更新了,Child 才會(huì)重新渲染,否則不會(huì)。
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>
}
這里有一個(gè)點(diǎn)需要注意,就是默認(rèn)的useEffect(不帶[])中return的清理函數(shù),它和componentWillUnmount有本質(zhì)區(qū)別的,默認(rèn)情況下return,在每次useEffect執(zhí)行前都會(huì)執(zhí)行,并不是只有組件卸載的時(shí)候執(zhí)行。
useEffect在副作用結(jié)束之后,會(huì)延遲一段時(shí)間執(zhí)行,并非同步執(zhí)行,和compontDidMount有本質(zhì)區(qū)別。遇到dom操作,最好使用useLayoutEffect。
hooks中的坑,以及為什么?
不要在循環(huán),條件或嵌套函數(shù)中調(diào)用Hook,必須始終在React函數(shù)的頂層使用Hook。這是因?yàn)镽eact需要利用調(diào)用順序來(lái)正確更新相應(yīng)的狀態(tài),以及調(diào)用相應(yīng)的鉤子函數(shù)。一旦在循環(huán)或條件分支語(yǔ)句中調(diào)用Hook,就容易導(dǎo)致調(diào)用順序的不一致性,從而產(chǎn)生難以預(yù)料到的后果。
使用useState時(shí)候,使用push,pop,splice等直接更改數(shù)組對(duì)象的坑,demo中使用push直接更改數(shù)組無(wú)法獲取到新值,應(yīng)該采用析構(gòu)方式,但是在class里面不會(huì)有這個(gè)問(wèn)題。(這個(gè)的原因是push,pop,splice是直接修改原數(shù)組,react會(huì)認(rèn)為state并沒有發(fā)生變化,無(wú)法更新) 這里的坑很多的,經(jīng)常出現(xiàn)的就是每次修改數(shù)組的時(shí)候:
const [firstData, setFirstData]: any = useState([]);
const handleFirstAdd = () => {
// let temp = firstData
// 不要這么寫,直接修改原數(shù)組相當(dāng)于沒有更新
let temp = [...firstData];
// 必須這么寫,多層數(shù)組也要這么寫
temp.push({
value: "",
});
setFirstData(temp);
};
function Indicatorfilter() {
let [num, setNums] = useState([0, 1, 2, 3]);
const test = () => {
// 這里坑是直接采用push去更新num,setNums(num)是無(wú)法更新num的,必須使用num = [...num ,1]
num.push(1);
// num = [...num ,1]
setNums(num);
};
return (
<div className="filter">
<div onClick={test}>測(cè)試</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采用同樣的方式是沒有問(wèn)題的
this.state.nums.push(1);
this.setState({
nums: this.state.nums,
});
}
render() {
let { nums } = this.state;
return (
<div>
<div onClick={this.test}>測(cè)試</div>
<div>
{nums.map((item: any, index: number) => (
<div key={index}>{item}</div>
))}
</div>
</div>
);
}
}
useState設(shè)置狀態(tài)的時(shí)候,只有第一次生效,后期需要更新狀態(tài),必須通過(guò)useEffect
TableDeail是一個(gè)公共組件,在調(diào)用它的父組件里面,我們通過(guò)set改變columns的值,以為傳遞給TableDeail的columns是最新的值,所以tabColumn每次也是最新的值,但是實(shí)際tabColumn是最開始的值,不會(huì)隨著columns的更新而更新
const TableDeail = ({
columns,
}:TableData) => {
const [tabColumn, setTabColumn] = useState(columns)
}
正確的做法是通過(guò)useEffect改變這個(gè)值
const TableDeail = ({
columns,
}:TableData) => {
const [tabColumn, setTabColumn] = useState(columns)
useEffect(() =>{setTabColumn(columns)},[columns])
} 原文地址:https://blog.csdn.net/kellywong/article/details/106430977
最后
關(guān)注公眾號(hào),置頂公眾號(hào),鬼哥,一起前端進(jìn)階
公眾號(hào)里回復(fù)關(guān)鍵詞 資料包領(lǐng)取我整理的進(jìn)階資料包公眾號(hào)里回復(fù)關(guān)鍵詞 加群,加入前端進(jìn)階群文章點(diǎn)個(gè) 在看,支持一下把!
