一名中/高級(jí)前端工程師的自檢清單-React 篇
點(diǎn)擊上方 前端Q,關(guān)注公眾號(hào)
回復(fù)加群,加入前端Q技術(shù)交流群
你真的了解 React 嗎?我們?cè)诿嬖囍型婕?React 時(shí),第一個(gè)問題就是“解釋 React 是什么”。解釋一種技術(shù)是什么,在面試中也是非常常見的引起 話題的題目。本篇文章我就帶你掌握這一類概念題的解答技巧。
一. 說說對(duì) React 的理解,有哪些特性
官方的解釋:React 是一個(gè) UI 庫,它的核心思想是UI=F(data), 即界面的呈現(xiàn)是由函數(shù)傳入的參數(shù)決定的
開發(fā)者不再需要關(guān)心界面時(shí)如何渲染的,只要關(guān)心數(shù)據(jù)的生成和傳遞,這大大提高的開發(fā)者的開發(fā)效率,節(jié)省了開發(fā)時(shí)間
其次 React 設(shè)計(jì)的
-
使用類似 HTML 的 JSX語法來描述視圖 -
通過 虛擬DOM修改真實(shí)DOM -
通過 setState修改數(shù)據(jù) -
在不同的 生命周期階段做不同的事 -
源碼底層對(duì)真實(shí) DOM 事件進(jìn)行封裝,使用 事件委托的方式來捕獲 DOM 事件 -
....
等特性進(jìn)一步簡化的 真實(shí) DOM 操作的復(fù)雜性
二. 說說真實(shí) DOM 與虛擬 DOM 的區(qū)別,優(yōu)缺點(diǎn)
2.1 虛擬 DOM 是什么
-
真實(shí)DOM就是我們?cè)跒g覽器開發(fā)者工具中看到的DOM結(jié)構(gòu) -
虛擬DOM簡單來說就是 JS 對(duì)象,此對(duì)象中的字段包含了對(duì)真實(shí)DOM的描述: -
type:是什么標(biāo)簽/元素 -
props:標(biāo)簽/元素有哪些屬性 -
children:是否有子元素
2.2 虛擬 DOM 大概是如何工作的
當(dāng) DOM 操作(渲染更新)比較頻繁時(shí),
React 底層會(huì)先將前后兩次的虛擬DOM 樹進(jìn)行對(duì)比,
定位出具體需要更新的部分,生成一個(gè)補(bǔ)丁集,
最后只把“補(bǔ)丁”打在需要更新的那部分真實(shí)DOM 上,實(shí)現(xiàn)精準(zhǔn)的“差量更新”。
2.3 虛擬 DOM 的優(yōu)點(diǎn)
-
解決了頻繁操作真實(shí) DOM 的低效率工作-不直接操作 DOM,數(shù)據(jù)驅(qū)動(dòng)視圖,也在一定程度上提升了性能 -
解決了擴(kuò)平臺(tái)開發(fā)的問題,因?yàn)樘摂M DOM 描述的東西可以是真實(shí) DOM,也可以是安卓界面。IOS 界面等等,這就可以對(duì)接不同平臺(tái)的渲染邏輯。從而實(shí)現(xiàn)"一次編碼,多端運(yùn)行"(如 React,React Native)
2.4 虛擬 DOM 的缺點(diǎn)
如果當(dāng)虛擬 DOM 的構(gòu)建和diff的過程相對(duì)復(fù)雜(比如很多遞歸遍歷等操作),那么虛擬 DOM 的 JS 計(jì)算是比較耗時(shí)的
三. 說說 Diff 算法
一般的原始 diff 思路算法復(fù)雜度是O(n^3),即循環(huán)遞歸進(jìn)行樹節(jié)點(diǎn)的一一對(duì)比
但 React 的 diff 算法是 O (n) 復(fù)雜度的思路
當(dāng)對(duì)比兩棵虛擬 DOM 樹時(shí),React 首先比較兩棵樹的根節(jié)點(diǎn)。不同類型的根節(jié)點(diǎn)元素會(huì)有不同的形態(tài)
當(dāng)對(duì)比兩個(gè)相同類型的 React 元素時(shí),React 會(huì)保留 DOM 節(jié)點(diǎn),僅比對(duì)及更新有改變的屬性。
-
當(dāng)根節(jié)點(diǎn)為不同類型的元素時(shí),React 會(huì)拆卸原有的樹并且建立起新的樹,這大大減少了 Diff過程中冗余的遞歸操作 -
當(dāng)對(duì)比兩個(gè)相同類型的 React 元素時(shí),React 會(huì)保留 DOM 節(jié)點(diǎn),僅比對(duì)及更新有改變的屬性 -
列表形式的子元素比較:React 引入了 key 屬性。當(dāng)子元素?fù)碛?key時(shí),React 使用key來匹配原有樹上的子元素以及最新樹上的子元素,如果key不同 不同則會(huì)拆卸原有的key節(jié)點(diǎn)并且建立起新的key節(jié)點(diǎn)
詳細(xì)內(nèi)容請(qǐng)參考React 官方文檔- Diffing 算法[2]
四. 說說 React 聲明周期有哪些不同階段,每個(gè)階段對(duì)應(yīng)的方法是什么
4.1 創(chuàng)建階段
-
constructor():組件的構(gòu)造函數(shù),組件更新到界面上之前會(huì)先調(diào)用 -
用于初始化內(nèi)部狀態(tài),很少使用 -
唯一可以直接修改 state的地方 -
static getDerivedStateFromProps(nextProps, prevState):用于從外部的屬性去初始化一些內(nèi)部的狀態(tài) -
當(dāng) state需要從props初始化時(shí)使用 -
盡量不要使用,維護(hù) state/props狀態(tài)一致性會(huì)增加復(fù)雜度 -
每次 render都會(huì)調(diào)用 -
典型場(chǎng)景: 表單控件獲取默認(rèn)值 -
render()::組件必須定義的一個(gè)生命周期方法,用來描述 虛擬 DOM 結(jié)構(gòu) -
componentDidMount(): 用于數(shù)據(jù)請(qǐng)求,定義一些外部資源等等副作用 -
UI 渲染完成后調(diào)用 -
只執(zhí)行一次 -
典型場(chǎng)景:獲取外部資源
4.2 更新階段
-
static getDerivedStateFromProps(nextProps, prevState) -
shouldComponentUpdate(nextProps, nextState):告訴組件是否需要重新渲染,用于性能優(yōu)化,比如判定指定 props 發(fā)生改變,組件才進(jìn)行重新渲染 -
決定虛擬 DOM 是否需要重繪 -
一般可以由 PureComponent自動(dòng)實(shí)現(xiàn) -
典型場(chǎng)景:性能優(yōu)化 -
render() -
getSnapshotBeforeUpdate(prevProps, prevState) -
在最近一次渲染輸出(提交到 DOM 節(jié)點(diǎn))之前調(diào)用,state 已更新 -
與 componentDidUpdate搭配使用 -
典型場(chǎng)景:捕獲 render之前的 DOM 狀態(tài) -
componentDidUpdate(prevProps, prevState) -
每次 UI 更新時(shí)被調(diào)用 -
典型場(chǎng)景:頁面需要根據(jù) props變化重新獲取數(shù)據(jù)
4.3 卸載階段
-
componentWillUnmount(): 做些資源釋放,卸載副作用的事情
-
此方法中可以執(zhí)行必要的清理操作,例如,清除 timer,取消網(wǎng)絡(luò)請(qǐng)求或清除在
詳細(xì)內(nèi)容請(qǐng)參考React 知識(shí)體系之生命周期及使用場(chǎng)景[3]
五. 說說對(duì) State 和 Props 的理解,有什么區(qū)別
-
state用于組件內(nèi)部數(shù)據(jù)傳遞,state數(shù)據(jù)可以通過this.setSate或者useState進(jìn)行修改 -
props用于組件外部組件數(shù)據(jù)傳遞,props不能直接修改.主要使用場(chǎng)景是: -
兄弟組件通信 -
父子組件通信 -
"爺孫組件"組件通信 -
props的使用范圍雖然更加廣泛,但也有其局限性:對(duì)于嵌套層次較深的組件,如果使用props傳遞數(shù)據(jù),會(huì)導(dǎo)致代碼冗余,增加數(shù)據(jù)傳遞的復(fù)雜度
六. super 和 super(props)有什么區(qū)別
在 JavaScript 中,super 指的是父類構(gòu)造函數(shù)。(在我們的示例中,它指向 React.Component 實(shí)現(xiàn)。)
在調(diào)用父類的構(gòu)造函數(shù)之前,你是不能在 constructor 中使用 this 關(guān)鍵字的。JavaScript 不允許這個(gè)行為
class Checkbox extends React.Component {
constructor(props) {
// ?? 還不能使用 `this`
super(props);
// ? 現(xiàn)在可以了
this.state = { isOn: true };
}
// ...
}
復(fù)制代碼
為什么一定要傳遞 props 呢?為了讓 React.Component 構(gòu)造函數(shù)能夠初始化 this.props
React 內(nèi)部代碼:
// React 內(nèi)部
class Component {
constructor(props) {
this.props = props;
// ...
}
}
復(fù)制代碼
實(shí)例代碼:
class Button extends React.Component {
constructor(props) {
super(); // ? We forgot to pass props
console.log(props); // ? {}
console.log(this.props); // ? undefined
}
// ...
}
復(fù)制代碼
class Button extends React.Component {
constructor(props) {
super(props); // ? We passed props
console.log(props); // ? {}
console.log(this.props); // ? {}
}
// ...
}
復(fù)制代碼
有了 Hooks 以后,我們幾乎就不需要 super 和 this 了
詳細(xì)內(nèi)容請(qǐng)參考為什么我們要寫 super(props) ?[4]
七. 說說 React 中的 setState 機(jī)制
7.1 合成事件、鉤子函數(shù)中的 setState
-
在鉤子函數(shù)中 setSate拿不到最新值 -
在合成事件中執(zhí)行多個(gè)同樣的 setSate,最終只會(huì)執(zhí)行一次,并且也拿不到最新值
原因:
-
一次 setState就會(huì)觸發(fā)一次re-render(重渲染) -
為了避免頻繁的 re-ernder,setState被設(shè)計(jì)成異步的形式 -
每來一個(gè) setState,就把它塞進(jìn)一個(gè)隊(duì)列里“攢起來”。等時(shí)機(jī)成熟,再把“攢起來”的state結(jié)果做合并(對(duì)于相同屬性的設(shè)置,React 只會(huì)為其保留最后一次的更新),最后只針對(duì)最新的state值走一次更新流程。這個(gè)過程,叫作**批量更新**
7.2 setTimeout/setInterval、原生 DOM中的 setState
-
在 setTimeout/setInterval中設(shè)置setState,可以拿到最新的值 -
在 原生 DOM事件中設(shè)置setState,可以拿到最新的值
原因:
setState 的“異步”并不是說內(nèi)部由異步代碼實(shí)現(xiàn),其實(shí)源碼本身執(zhí)行的過程和代碼都是同步的,
只是合成事件和鉤子函數(shù)的調(diào)用順序在更新之前,導(dǎo)致在合成事件和鉤子函數(shù)中沒法立馬拿到更新后的值,形式了所謂的“異步”
setState 的批量更新優(yōu)化也是建立在“異步”(合成事件、鉤子函數(shù))之上的,在原生事件和 setTimeout 中不會(huì)批量更新
詳細(xì)內(nèi)容請(qǐng)參考setState 到底是同步的,還是異步的?[5]
八. 說說對(duì) React 事件機(jī)制的理解
8.1 React 中的事件是什么
React 中的事件叫合成事件:React 底層使用事件委托的方式對(duì)真實(shí) DOM 事件進(jìn)行了封裝,使合成事件具有更好的瀏覽器兼容性和性能
8.2 合成事件的大致原理
當(dāng)事件在具體的 DOM 節(jié)點(diǎn)上被觸發(fā)后,最終都會(huì)冒泡到 document 上,document 上所綁定的統(tǒng)一事件處理程序會(huì)將事件分發(fā)到具體的組件實(shí)例
8.3 React 為什么要重新設(shè)計(jì)出一個(gè)合成事件
合成事件是 React 自定義的事件對(duì)象,它符合 W3C 規(guī)范,在底層抹平了不同瀏覽器的差異,在上層面向開發(fā)者暴露統(tǒng)一的、穩(wěn)定的、與 DOM 原生事件相同的事件接口。
開發(fā)者們由此便不必再關(guān)注煩瑣的兼容性問題,可以專注于業(yè)務(wù)邏輯的開發(fā)。
-
雖然合成事件并不是原生 DOM 事件,但它保存了原生 DOM 事件的引用。當(dāng)你需要訪問原生 DOM 事件對(duì)象時(shí),可以通過合成事件對(duì)象的 e.nativeEvent屬性獲取到它 -
合成事件無法獲取到真實(shí) DOM,但可以通過 React 提供 refAPI 進(jìn)行獲取
詳細(xì)內(nèi)容請(qǐng)參考React 事件與 DOM 事件有何不同?[6]
九. React 事件綁定的方式有哪些
9.1 類組件
9.1.1 render 方法中使用 bind
這種方式在組件每次 render 渲染的時(shí)候,都會(huì)重新進(jìn)行 bind 的操作,影響性能
class App extends React.Component {
handleClick() {
console.log("this > ", this);
}
render() {
return <div onClick={this.handleClick.bind(this)}>test</div>;
}
}
復(fù)制代碼
9.1.2 render 方法中使用箭頭函數(shù)
通過 ES6 的上下文來將 this 的指向綁定給當(dāng)前組件,同樣在每一次 render 的時(shí)候都會(huì)生成新的方法,影響性能
class App extends React.Component {
handleClick() {
console.log("this > ", this);
}
render() {
return <div onClick={(e) => this.handleClick(e)}>test</div>;
}
}
復(fù)制代碼
9.1.3 constructor 中 bind
在 constructor 中預(yù)先 bind 當(dāng)前組件,可以避免在 render 操作中重復(fù)綁定
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log("this > ", this);
}
render() {
return <div onClick={this.handleClick}>test</div>;
}
}
復(fù)制代碼
9.1.4 定義階段使用箭頭函數(shù)綁定
能夠避免在 render 操作中重復(fù)綁定
class App extends React.Component {
constructor(props) {
super(props);
}
handleClick = () => {
console.log("this > ", this);
};
render() {
return <div onClick={this.handleClick}>test</div>;
}
}
復(fù)制代碼
9.2 函數(shù)式組件
-
箭頭函數(shù)
函數(shù)組件沒有實(shí)例,因此沒有this
const App = () => {
handleClick = (e) => {
console.log(e);
};
return <div onClick={this.handleClick}>test</div>;
};
復(fù)制代碼
詳細(xì)內(nèi)容請(qǐng)參考React 構(gòu)建組件的方式有哪些[7]
十. React 構(gòu)建組件的方式有哪些
-
類組件 -
高階組件 -
render props -
純函數(shù)組件 -
Hooks組件 -
自定義 Hooks
詳細(xì)內(nèi)容請(qǐng)參考React 構(gòu)建組件的方式有哪些[8]
十一. React 中組件通信的方式有哪些
-
單個(gè)組件內(nèi)部數(shù)據(jù)傳遞 -
state -
父組件向子組件傳遞 -
props -
子組件向父組件傳遞 -
props -
兄弟組件之間的通信 -
props -
父組件向后代組件傳遞 -
props -
Context API -
Redux -
發(fā)布-訂閱模式 -
EventBus -
非關(guān)系組件傳遞 -
Context API -
Redux -
發(fā)布-訂閱模式 -
EventBus
十二. React 的 key 有什么作用
說到 React 的 key,就要說到 React 的 Diff 算法
詳細(xì)內(nèi)容請(qǐng)參考React 列表循環(huán)為什么需要 key[9]
往期推薦
大廠面試過程復(fù)盤(微信/阿里/頭條,附答案篇)
面試題:說說事件循環(huán)機(jī)制(滿分答案來了)
專心工作只想搞錢的前端女程序員的2020
最后
歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...
點(diǎn)個(gè)在看支持我吧
往期推薦
最后
歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...
參考資料
https://juejin.cn/post/6978685539985653767: https://juejin.cn/post/6978685539985653767
[2]https://zh-hans.reactjs.org/docs/reconciliation.html#the-diffing-algorithm: https://link.juejin.cn?target=https%3A%2F%2Fzh-hans.reactjs.org%2Fdocs%2Freconciliation.html%23the-diffing-algorithm
[3]https://juejin.cn/post/6981739846461030408: https://juejin.cn/post/6981739846461030408
[4]https://overreacted.io/zh-hans/why-do-we-write-super-props/: https://link.juejin.cn?target=https%3A%2F%2Foverreacted.io%2Fzh-hans%2Fwhy-do-we-write-super-props%2F
[5]https://www.yuque.com/u221766/xgl0mb/oxl3ik: https://link.juejin.cn?target=https%3A%2F%2Fwww.yuque.com%2Fu221766%2Fxgl0mb%2Foxl3ik
[6]https://www.yuque.com/u221766/xgl0mb/iu68y0: https://link.juejin.cn?target=https%3A%2F%2Fwww.yuque.com%2Fu221766%2Fxgl0mb%2Fiu68y0
[7]https://juejin.cn/post/6952907248393781284#heading-2: https://juejin.cn/post/6952907248393781284#heading-2
[8]https://juejin.cn/post/6952907248393781284: https://juejin.cn/post/6952907248393781284
[9]https://juejin.cn/post/6940974776441634823: https://juejin.cn/post/6940974776441634823
來源:望道同學(xué)
https://juejin.cn/post/6981831831112908831
