高頻react面試題20道(附詳解)
大廠技術(shù) 高級(jí)前端 Node進(jìn)階
點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群
作者:前端要努力
原文:https://github.com/whylisa/front-end-interview
上次分享了一篇vue面試題大全,有小伙伴私聊需要 react 的,這篇是react相關(guān)不錯(cuò)的面試題講解,學(xué)習(xí)下!
1. React 事件機(jī)制
<div onClick={this.handleClick.bind(this)}>點(diǎn)我</div>
React并不是將click事件綁定到了div的真實(shí)DOM上,而是在document處監(jiān)聽了所有的事件,當(dāng)事件發(fā)生并且冒泡到document處的時(shí)候,React將事件內(nèi)容封裝并交由真正的處理函數(shù)運(yùn)行。這樣的方式不僅僅減少了內(nèi)存的消耗,還能在組件掛在銷毀時(shí)統(tǒng)一訂閱和移除事件。
除此之外,冒泡到document上的事件也不是原生的瀏覽器事件,而是由react自己實(shí)現(xiàn)的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的話應(yīng)該調(diào)用event.preventDefault()方法,而不是調(diào)用event.stopProppagation()方法。
JSX 上寫的事件并沒有綁定在對(duì)應(yīng)的真實(shí) DOM 上,而是通過事件代理的方式,將所有的事件都統(tǒng)一綁定在了 document 上。這樣的方式不僅減少了內(nèi)存消耗,還能在組件掛載銷毀時(shí)統(tǒng)一訂閱和移除事件。
另外冒泡到 document 上的事件也不是原生瀏覽器事件,而是 React 自己實(shí)現(xiàn)的合成事件(SyntheticEvent)。因此我們?nèi)绻幌胍录芭莸脑?,調(diào)用 event.stopPropagation 是無效的,而應(yīng)該調(diào)用 event.preventDefault。
實(shí)現(xiàn)合成事件的目的如下:
合成事件首先抹平了瀏覽器之間的兼容問題,另外這是一個(gè)跨瀏覽器原生事件包裝器,賦予了跨瀏覽器開發(fā)的能力; 對(duì)于原生瀏覽器事件來說,瀏覽器會(huì)給監(jiān)聽器創(chuàng)建一個(gè)事件對(duì)象。如果你有很多的事件監(jiān)聽,那么就需要分配很多的事件對(duì)象,造成高額的內(nèi)存分配問題。但是對(duì)于合成事件來說,有一個(gè)事件池專門來管理它們的創(chuàng)建和銷毀,當(dāng)事件需要被使用時(shí),就會(huì)從池子中復(fù)用對(duì)象,事件回調(diào)結(jié)束后,就會(huì)銷毀事件對(duì)象上的屬性,從而便于下次復(fù)用事件對(duì)象。
2. React的事件和普通的HTML事件有什么不同?
區(qū)別:
對(duì)于事件名稱命名方式,原生事件為全小寫,react 事件采用小駝峰; 對(duì)于事件函數(shù)處理語法,原生事件為字符串,react 事件為函數(shù); react 事件不能采用 return false 的方式來阻止瀏覽器的默認(rèn)行為,而必須要地明確地調(diào)用 preventDefault()來阻止默認(rèn)行為。
合成事件是 react 模擬原生 DOM 事件所有能力的一個(gè)事件對(duì)象,其優(yōu)點(diǎn)如下:
兼容所有瀏覽器,更好的跨平臺(tái); 將事件統(tǒng)一存放在一個(gè)數(shù)組,避免頻繁的新增與刪除(垃圾回收)。 方便 react 統(tǒng)一管理和事務(wù)機(jī)制。
事件的執(zhí)行順序?yàn)樵录葓?zhí)行,合成事件后執(zhí)行,合成事件會(huì)冒泡綁定到 document 上,所以盡量避免原生事件與合成事件混用,如果原生事件阻止冒泡,可能會(huì)導(dǎo)致合成事件不執(zhí)行,因?yàn)樾枰芭莸絛ocument 上合成事件才會(huì)執(zhí)行。
3. React 組件中怎么做事件代理?它的原理是什么?
React基于Virtual DOM實(shí)現(xiàn)了一個(gè)SyntheticEvent層(合成事件層),定義的事件處理器會(huì)接收到一個(gè)合成事件對(duì)象的實(shí)例,它符合W3C標(biāo)準(zhǔn),且與原生的瀏覽器事件擁有同樣的接口,支持冒泡機(jī)制,所有的事件都自動(dòng)綁定在最外層上。
在React底層,主要對(duì)合成事件做了兩件事:
**事件委派:**React會(huì)把所有的事件綁定到結(jié)構(gòu)的最外層,使用統(tǒng)一的事件監(jiān)聽器,這個(gè)事件監(jiān)聽器上維持了一個(gè)映射來保存所有組件內(nèi)部事件監(jiān)聽和處理函數(shù)。 **自動(dòng)綁定:**React組件中,每個(gè)方法的上下文都會(huì)指向該組件的實(shí)例,即自動(dòng)綁定this為當(dāng)前組件。
4. React 高階組件、Render props、hooks 有什么區(qū)別,為什么要不斷迭代
這三者是目前react解決代碼復(fù)用的主要方式:
高階組件(HOC)是 React 中用于復(fù)用組件邏輯的一種高級(jí)技巧。HOC 自身不是 React API 的一部分,它是一種基于 React 的組合特性而形成的設(shè)計(jì)模式。具體而言,高階組件是參數(shù)為組件,返回值為新組件的函數(shù)。 render props是指一種在 React 組件之間使用一個(gè)值為函數(shù)的 prop 共享代碼的簡(jiǎn)單技術(shù),更具體的說,render prop 是一個(gè)用于告知組件需要渲染什么內(nèi)容的函數(shù) prop。 通常,render props 和高階組件只渲染一個(gè)子節(jié)點(diǎn)。讓 Hook 來服務(wù)這個(gè)使用場(chǎng)景更加簡(jiǎn)單。這兩種模式仍有用武之地,(例如,一個(gè)虛擬滾動(dòng)條組件或許會(huì)有一個(gè) renderltem 屬性,或是一個(gè)可見的容器組件或許會(huì)有它自己的 DOM 結(jié)構(gòu))。但在大部分場(chǎng)景下,Hook 足夠了,并且能夠幫助減少嵌套。
(1)HOC
官方解釋∶
“高階組件(HOC)是 React 中用于復(fù)用組件邏輯的一種高級(jí)技巧。HOC 自身不是 React API 的一部分,它是一種基于 React 的組合特性而形成的設(shè)計(jì)模式。
”
簡(jiǎn)言之,HOC是一種組件的設(shè)計(jì)模式,HOC接受一個(gè)組件和額外的參數(shù)(如果需要),返回一個(gè)新的組件。HOC 是純函數(shù),沒有副作用。
// hoc的定義
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: selectData(DataSource, props)
};
}
// 一些通用的邏輯處理
render() {
// ... 并使用新數(shù)據(jù)渲染被包裝的組件!
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
// 使用
const BlogPostWithSubscription = withSubscription(BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id));
HOC的優(yōu)缺點(diǎn)∶
優(yōu)點(diǎn)∶ 邏輯服用、不影響被包裹組件的內(nèi)部邏輯。 缺點(diǎn)∶ hoc傳遞給被包裹組件的props容易和被包裹后的組件重名,進(jìn)而被覆蓋
**(2)**Render props
官方解釋∶
“"render prop"是指一種在 React 組件之間使用一個(gè)值為函數(shù)的 prop 共享代碼的簡(jiǎn)單技術(shù)
”
具有render prop 的組件接受一個(gè)返回React元素的函數(shù),將render的渲染邏輯注入到組件內(nèi)部。在這里,"render"的命名可以是任何其他有效的標(biāo)識(shí)符。
// DataProvider組件內(nèi)部的渲染邏輯如下
class DataProvider extends React.Components {
state = {
name: 'Tom'
}
render() {
return (
<div>
<p>共享數(shù)據(jù)組件自己內(nèi)部的渲染邏輯</p>
{ this.props.render(this.state) }
</div>
);
}
}
// 調(diào)用方式
<DataProvider render={data => (
<h1>Hello {data.name}</h1>
)}/>
由此可以看到,render props的優(yōu)缺點(diǎn)也很明顯∶
優(yōu)點(diǎn):數(shù)據(jù)共享、代碼復(fù)用,將組件內(nèi)的state作為props傳遞給調(diào)用者,將渲染邏輯交給調(diào)用者。 缺點(diǎn):無法在 return 語句外訪問數(shù)據(jù)、嵌套寫法不夠優(yōu)雅
**(3)**Hooks
官方解釋∶
“Hook是 React 16.8 的新增特性。它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。通過自定義hook,可以復(fù)用代碼邏輯。
”
// 自定義一個(gè)獲取訂閱數(shù)據(jù)的hook
function useSubscription() {
const data = DataSource.getComments();
return [data];
}
//
function CommentList(props) {
const {data} = props;
const [subData] = useSubscription();
...
}
// 使用
<CommentList data='hello' />
以上可以看出,hook解決了hoc的prop覆蓋的問題,同時(shí)使用的方式解決了render props的嵌套地獄的問題。hook的優(yōu)點(diǎn)如下∶
使用直觀; 解決hoc的prop 重名問題; 解決render props 因共享數(shù)據(jù) 而出現(xiàn)嵌套地獄的問題; 能在return之外使用數(shù)據(jù)的問題。
需要注意的是:hook只能在組件頂層使用,不可在分支語句中使用。
總結(jié)∶
Hoc、render props和hook都是為了解決代碼復(fù)用的問題,但是hoc和render props都有特定的使用場(chǎng)景和明顯的缺點(diǎn)。hook是react16.8更新的新的API,讓組件邏輯復(fù)用更簡(jiǎn)潔明了,同時(shí)也解決了hoc和render props的一些缺點(diǎn)。
5. 對(duì)React-Fiber的理解,它解決了什么問題?
React V15 在渲染時(shí),會(huì)遞歸比對(duì) VirtualDOM 樹,找出需要變動(dòng)的節(jié)點(diǎn),然后同步更新它們, 一氣呵成。這個(gè)過程期間, React 會(huì)占據(jù)瀏覽器資源,這會(huì)導(dǎo)致用戶觸發(fā)的事件得不到響應(yīng),并且會(huì)導(dǎo)致掉幀,導(dǎo)致用戶感覺到卡頓。
為了給用戶制造一種應(yīng)用很快的“假象”,不能讓一個(gè)任務(wù)長(zhǎng)期霸占著資源。可以將瀏覽器的渲染、布局、繪制、資源加載(例如 HTML 解析)、事件響應(yīng)、腳本執(zhí)行視作操作系統(tǒng)的“進(jìn)程”,需要通過某些調(diào)度策略合理地分配 CPU 資源,從而提高瀏覽器的用戶響應(yīng)速率, 同時(shí)兼顧任務(wù)執(zhí)行效率。
所以 React 通過Fiber 架構(gòu),讓這個(gè)執(zhí)行過程變成可被中斷?!斑m時(shí)”地讓出 CPU 執(zhí)行權(quán),除了可以讓瀏覽器及時(shí)地響應(yīng)用戶的交互,還有其他好處:
分批延時(shí)對(duì)DOM進(jìn)行操作,避免一次性操作大量 DOM 節(jié)點(diǎn),可以得到更好的用戶體驗(yàn); 給瀏覽器一點(diǎn)喘息的機(jī)會(huì),它會(huì)對(duì)代碼進(jìn)行編譯優(yōu)化(JIT)及進(jìn)行熱代碼優(yōu)化,或者對(duì) reflow 進(jìn)行修正。
**核心思想:**Fiber 也稱協(xié)程或者纖程。它和線程并不一樣,協(xié)程本身是沒有并發(fā)或者并行能力的(需要配合線程),它只是一種控制流程的讓出機(jī)制。讓出 CPU 的執(zhí)行權(quán),讓 CPU 能在這段時(shí)間執(zhí)行其他的操作。渲染的過程可以被中斷,可以將控制權(quán)交回瀏覽器,讓位給高優(yōu)先級(jí)的任務(wù),瀏覽器空閑后再恢復(fù)渲染。
6. React.Component 和 React.PureComponent 的區(qū)別
PureComponent表示一個(gè)純組件,可以用來優(yōu)化React程序,減少render函數(shù)執(zhí)行的次數(shù),從而提高組件的性能。
在React中,當(dāng)prop或者state發(fā)生變化時(shí),可以通過在shouldComponentUpdate生命周期函數(shù)中執(zhí)行return false來阻止頁面的更新,從而減少不必要的render執(zhí)行。React.PureComponent會(huì)自動(dòng)執(zhí)行 shouldComponentUpdate。
不過,pureComponent中的 shouldComponentUpdate() 進(jìn)行的是淺比較,也就是說如果是引用數(shù)據(jù)類型的數(shù)據(jù),只會(huì)比較不是同一個(gè)地址,而不會(huì)比較這個(gè)地址里面的數(shù)據(jù)是否一致。淺比較會(huì)忽略屬性和或狀態(tài)突變情況,其實(shí)也就是數(shù)據(jù)引用指針沒有變化,而數(shù)據(jù)發(fā)生改變的時(shí)候render是不會(huì)執(zhí)行的。如果需要重新渲染那么就需要重新開辟空間引用數(shù)據(jù)。PureComponent一般會(huì)用在一些純展示組件上。
使用pureComponent的好處:當(dāng)組件更新時(shí),如果組件的props或者state都沒有改變,render函數(shù)就不會(huì)觸發(fā)。省去虛擬DOM的生成和對(duì)比過程,達(dá)到提升性能的目的。這是因?yàn)閞eact自動(dòng)做了一層淺比較。
7. Component, Element, Instance 之間有什么區(qū)別和聯(lián)系?
**元素:**一個(gè)元素 element是一個(gè)普通對(duì)象(plain object),描述了對(duì)于一個(gè)DOM節(jié)點(diǎn)或者其他組件component,你想讓它在屏幕上呈現(xiàn)成什么樣子。元素element可以在它的屬性props中包含其他元素(譯注:用于形成元素樹)。創(chuàng)建一個(gè)React元素element成本很低。元素element創(chuàng)建之后是不可變的。**組件:**一個(gè)組件 component可以通過多種方式聲明。可以是帶有一個(gè)render()方法的類,簡(jiǎn)單點(diǎn)也可以定義為一個(gè)函數(shù)。這兩種情況下,它都把屬性props作為輸入,把返回的一棵元素樹作為輸出。**實(shí)例:**一個(gè)實(shí)例 instance是你在所寫的組件類component class中使用關(guān)鍵字this所指向的東西(譯注:組件實(shí)例)。它用來存儲(chǔ)本地狀態(tài)和響應(yīng)生命周期事件很有用。
函數(shù)式組件(Functional component)根本沒有實(shí)例instance。類組件(Class component)有實(shí)例instance,但是永遠(yuǎn)也不需要直接創(chuàng)建一個(gè)組件的實(shí)例,因?yàn)镽eact幫我們做了這些。
8. React.createClass和extends Component的區(qū)別有哪些?
React.createClass和extends Component的bai區(qū)別主要在于:
(1)語法區(qū)別
createClass本質(zhì)上是一個(gè)工廠函數(shù),extends的方式更加接近最新的ES6規(guī)范的class寫法。兩種方式在語法上的差別主要體現(xiàn)在方法的定義和靜態(tài)屬性的聲明上。 createClass方式的方法定義使用逗號(hào),隔開,因?yàn)閏reatClass本質(zhì)上是一個(gè)函數(shù),傳遞給它的是一個(gè)Object;而class的方式定義方法時(shí)務(wù)必謹(jǐn)記不要使用逗號(hào)隔開,這是ES6 class的語法規(guī)范。
(2)propType 和 getDefaultProps
React.createClass:通過proTypes對(duì)象和getDefaultProps()方法來設(shè)置和獲取props. React.Component:通過設(shè)置兩個(gè)屬性propTypes和defaultProps
(3)狀態(tài)的區(qū)別
React.createClass:通過getInitialState()方法返回一個(gè)包含初始值的對(duì)象 React.Component:通過constructor設(shè)置初始狀態(tài)
(4)this區(qū)別
React.createClass:會(huì)正確綁定this React.Component:由于使用了 ES6,這里會(huì)有些微不同,屬性并不會(huì)自動(dòng)綁定到 React 類的實(shí)例上。
(5)Mixins
React.createClass:使用 React.createClass 的話,可以在創(chuàng)建組件時(shí)添加一個(gè)叫做 mixins 的屬性,并將可供混合的類的集合以數(shù)組的形式賦給 mixins。 如果使用 ES6 的方式來創(chuàng)建組件,那么 React mixins的特性將不能被使用了。
9. React 高階組件是什么,和普通組件有什么區(qū)別,適用什么場(chǎng)景
官方解釋∶
“高階組件(HOC)是 React 中用于復(fù)用組件邏輯的一種高級(jí)技巧。HOC 自身不是 React API 的一部分,它是一種基于 React 的組合特性而形成的設(shè)計(jì)模式。
”
高階組件(HOC)就是一個(gè)函數(shù),且該函數(shù)接受一個(gè)組件作為參數(shù),并返回一個(gè)新的組件,它只是一種組件的設(shè)計(jì)模式,這種設(shè)計(jì)模式是由react自身的組合性質(zhì)必然產(chǎn)生的。我們將它們稱為純組件,因?yàn)樗鼈兛梢越邮苋魏蝿?dòng)態(tài)提供的子組件,但它們不會(huì)修改或復(fù)制其輸入組件中的任何行為。
// hoc的定義
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: selectData(DataSource, props)
};
}
// 一些通用的邏輯處理
render() {
// ... 并使用新數(shù)據(jù)渲染被包裝的組件!
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
// 使用
const BlogPostWithSubscription = withSubscription(BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id));
1)HOC的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)∶ 邏輯服用、不影響被包裹組件的內(nèi)部邏輯。 缺點(diǎn)∶hoc傳遞給被包裹組件的props容易和被包裹后的組件重名,進(jìn)而被覆蓋
2)適用場(chǎng)景
代碼復(fù)用,邏輯抽象 渲染劫持 State 抽象和更改 Props 更改
3)具體應(yīng)用例子
**權(quán)限控制:**利用高階組件的 條件渲染 特性可以對(duì)頁面進(jìn)行權(quán)限控制,權(quán)限控制一般分為兩個(gè)維度:頁面級(jí)別和 頁面元素級(jí)別
// HOC.js
function withAdminAuth(WrappedComponent) {
return class extends React.Component {
state = {
isAdmin: false,
}
async UNSAFE_componentWillMount() {
const currentRole = await getCurrentUserRole();
this.setState({
isAdmin: currentRole === 'Admin',
});
}
render() {
if (this.state.isAdmin) {
return <WrappedComponent {...this.props} />;
} else {
return (<div>您沒有權(quán)限查看該頁面,請(qǐng)聯(lián)系管理員!</div>);
}
}
};
}
// pages/page-a.js
class PageA extends React.Component {
constructor(props) {
super(props);
// something here...
}
UNSAFE_componentWillMount() {
// fetching data
}
render() {
// render page with data
}
}
export default withAdminAuth(PageA);
// pages/page-b.js
class PageB extends React.Component {
constructor(props) {
super(props);
// something here...
}
UNSAFE_componentWillMount() {
// fetching data
}
render() {
// render page with data
}
}
export default withAdminAuth(PageB);
**組件渲染性能追蹤:**借助父組件子組件生命周期規(guī)則捕獲子組件的生命周期,可以方便的對(duì)某個(gè)組件的渲染時(shí)間進(jìn)行記錄∶
class Home extends React.Component {
render() {
return (<h1>Hello World.</h1>);
}
}
function withTiming(WrappedComponent) {
return class extends WrappedComponent {
constructor(props) {
super(props);
this.start = 0;
this.end = 0;
}
UNSAFE_componentWillMount() {
super.componentWillMount && super.componentWillMount();
this.start = Date.now();
}
componentDidMount() {
super.componentDidMount && super.componentDidMount();
this.end = Date.now();
console.log(`${WrappedComponent.name} 組件渲染時(shí)間為 ${this.end - this.start} ms`);
}
render() {
return super.render();
}
};
}
export default withTiming(Home);
注意:withTiming 是利用 反向繼承 實(shí)現(xiàn)的一個(gè)高階組件,功能是計(jì)算被包裹組件(這里是 Home 組件)的渲染時(shí)間。
頁面復(fù)用
const withFetching = fetching => WrappedComponent => {
return class extends React.Component {
state = {
data: [],
}
async UNSAFE_componentWillMount() {
const data = await fetching();
this.setState({
data,
});
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />;
}
}
}
// pages/page-a.js
export default withFetching(fetching('science-fiction'))(MovieList);
// pages/page-b.js
export default withFetching(fetching('action'))(MovieList);
// pages/page-other.js
export default withFetching(fetching('some-other-type'))(MovieList);
10. 對(duì)componentWillReceiveProps 的理解
該方法當(dāng)props發(fā)生變化時(shí)執(zhí)行,初始化render時(shí)不執(zhí)行,在這個(gè)回調(diào)函數(shù)里面,你可以根據(jù)屬性的變化,通過調(diào)用this.setState()來更新你的組件狀態(tài),舊的屬性還是可以通過this.props來獲取,這里調(diào)用更新狀態(tài)是安全的,并不會(huì)觸發(fā)額外的render調(diào)用。
**使用好處:**在這個(gè)生命周期中,可以在子組件的render函數(shù)執(zhí)行前獲取新的props,從而更新子組件自己的state。可以將數(shù)據(jù)請(qǐng)求放在這里進(jìn)行執(zhí)行,需要傳的參數(shù)則從componentWillReceiveProps(nextProps)中獲取。而不必將所有的請(qǐng)求都放在父組件中。于是該請(qǐng)求只會(huì)在該組件渲染時(shí)才會(huì)發(fā)出,從而減輕請(qǐng)求負(fù)擔(dān)。componentWillReceiveProps在初始化render的時(shí)候不會(huì)執(zhí)行,它會(huì)在Component接受到新的狀態(tài)(Props)時(shí)被觸發(fā),一般用于父組件狀態(tài)更新時(shí)子組件的重新渲染。
11. 哪些方法會(huì)觸發(fā) React 重新渲染?重新渲染 render 會(huì)做些什么?
(1)哪些方法會(huì)觸發(fā) react 重新渲染?
setState()方法被調(diào)用
setState 是 React 中最常用的命令,通常情況下,執(zhí)行 setState 會(huì)觸發(fā) render。但是這里有個(gè)點(diǎn)值得關(guān)注,執(zhí)行 setState 的時(shí)候不一定會(huì)重新渲染。當(dāng) setState 傳入 null 時(shí),并不會(huì)觸發(fā) render。
class App extends React.Component {
state = {
a: 1
};
render() {
console.log("render");
return (
<React.Fragement>
<p>{this.state.a}</p>
<button
onClick={() => {
this.setState({ a: 1 }); // 這里并沒有改變 a 的值
}}
>
Click me
</button>
<button onClick={() => this.setState(null)}>setState null</button>
<Child />
</React.Fragement>
);
}
}
父組件重新渲染
只要父組件重新渲染了,即使傳入子組件的 props 未發(fā)生變化,那么子組件也會(huì)重新渲染,進(jìn)而觸發(fā) render
(2)重新渲染 render 會(huì)做些什么?
會(huì)對(duì)新舊 VNode 進(jìn)行對(duì)比,也就是我們所說的Diff算法。 對(duì)新舊兩棵樹進(jìn)行一個(gè)深度優(yōu)先遍歷,這樣每一個(gè)節(jié)點(diǎn)都會(huì)一個(gè)標(biāo)記,在到深度遍歷的時(shí)候,每遍歷到一和個(gè)節(jié)點(diǎn),就把該節(jié)點(diǎn)和新的節(jié)點(diǎn)樹進(jìn)行對(duì)比,如果有差異就放到一個(gè)對(duì)象里面 遍歷差異對(duì)象,根據(jù)差異的類型,根據(jù)對(duì)應(yīng)對(duì)規(guī)則更新VNode
React 的處理 render 的基本思維模式是每次一有變動(dòng)就會(huì)去重新渲染整個(gè)應(yīng)用。在 Virtual DOM 沒有出現(xiàn)之前,最簡(jiǎn)單的方法就是直接調(diào)用 innerHTML。Virtual DOM厲害的地方并不是說它比直接操作 DOM 快,而是說不管數(shù)據(jù)怎么變,都會(huì)盡量以最小的代價(jià)去更新 DOM。React 將 render 函數(shù)返回的虛擬 DOM 樹與老的進(jìn)行比較,從而確定 DOM 要不要更新、怎么更新。當(dāng) DOM 樹很大時(shí),遍歷兩棵樹進(jìn)行各種比對(duì)還是相當(dāng)耗性能的,特別是在頂層 setState 一個(gè)微小的修改,默認(rèn)會(huì)去遍歷整棵樹。盡管 React 使用高度優(yōu)化的 Diff 算法,但是這個(gè)過程仍然會(huì)損耗性能.
12. React如何判斷什么時(shí)候重新渲染組件?
組件狀態(tài)的改變可以因?yàn)?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;outline: 0px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">props的改變,或者直接通過setState方法改變。組件獲得新的狀態(tài),然后React決定是否應(yīng)該重新渲染組件。只要組件的state發(fā)生變化,React就會(huì)對(duì)組件進(jìn)行重新渲染。這是因?yàn)镽eact中的shouldComponentUpdate方法默認(rèn)返回true,這就是導(dǎo)致每次更新都重新渲染的原因。
當(dāng)React將要渲染組件時(shí)會(huì)執(zhí)行shouldComponentUpdate方法來看它是否返回true(組件應(yīng)該更新,也就是重新渲染)。所以需要重寫shouldComponentUpdate方法讓它根據(jù)情況返回true或者false來告訴React什么時(shí)候重新渲染什么時(shí)候跳過重新渲染。
13. React聲明組件有哪幾種方法,有什么不同?
React 聲明組件的三種方式:
函數(shù)式定義的 無狀態(tài)組件ES5原生方式 React.createClass定義的組件ES6形式的 extends React.Component定義的組件
(1)無狀態(tài)函數(shù)式組件
它是為了創(chuàng)建純展示組件,這種組件只負(fù)責(zé)根據(jù)傳入的props來展示,不涉及到state狀態(tài)的操作
組件不會(huì)被實(shí)例化,整體渲染性能得到提升,不能訪問this對(duì)象,不能訪問生命周期的方法
(2)ES5 原生方式 React.createClass // RFC
React.createClass會(huì)自綁定函數(shù)方法,導(dǎo)致不必要的性能開銷,增加代碼過時(shí)的可能性。
(3)E6繼承形式 React.Component // RCC
目前極為推薦的創(chuàng)建有狀態(tài)組件的方式,最終會(huì)取代React.createClass形式;相對(duì)于 React.createClass可以更好實(shí)現(xiàn)代碼復(fù)用。
無狀態(tài)組件相對(duì)于于后者的區(qū)別:
與無狀態(tài)組件相比,React.createClass和React.Component都是創(chuàng)建有狀態(tài)的組件,這些組件是要被實(shí)例化的,并且可以訪問組件的生命周期方法。
React.createClass與React.Component區(qū)別:
① 函數(shù)this自綁定
React.createClass創(chuàng)建的組件,其每一個(gè)成員函數(shù)的this都有React自動(dòng)綁定,函數(shù)中的this會(huì)被正確設(shè)置。 React.Component創(chuàng)建的組件,其成員函數(shù)不會(huì)自動(dòng)綁定this,需要開發(fā)者手動(dòng)綁定,否則this不能獲取當(dāng)前組件實(shí)例對(duì)象。
② 組件屬性類型propTypes及其默認(rèn)props屬性defaultProps配置不同
React.createClass在創(chuàng)建組件時(shí),有關(guān)組件props的屬性類型及組件默認(rèn)的屬性會(huì)作為組件實(shí)例的屬性來配置,其中defaultProps是使用getDefaultProps的方法來獲取默認(rèn)組件屬性的 React.Component在創(chuàng)建組件時(shí)配置這兩個(gè)對(duì)應(yīng)信息時(shí),他們是作為組件類的屬性,不是組件實(shí)例的屬性,也就是所謂的類的靜態(tài)屬性來配置的。
③ 組件初始狀態(tài)state的配置不同
React.createClass創(chuàng)建的組件,其狀態(tài)state是通過getInitialState方法來配置組件相關(guān)的狀態(tài); React.Component創(chuàng)建的組件,其狀態(tài)state是在constructor中像初始化組件屬性一樣聲明的。
14. 對(duì)有狀態(tài)組件和無狀態(tài)組件的理解及使用場(chǎng)景
(1)有狀態(tài)組件
特點(diǎn):
是類組件 有繼承 可以使用this 可以使用react的生命周期 使用較多,容易頻繁觸發(fā)生命周期鉤子函數(shù),影響性能 內(nèi)部使用 state,維護(hù)自身狀態(tài)的變化,有狀態(tài)組件根據(jù)外部組件傳入的 props 和自身的 state進(jìn)行渲染。
使用場(chǎng)景:
需要使用到狀態(tài)的。 需要使用狀態(tài)操作組件的(無狀態(tài)組件的也可以實(shí)現(xiàn)新版本react hooks也可實(shí)現(xiàn))
總結(jié):
類組件可以維護(hù)自身的狀態(tài)變量,即組件的 state ,類組件還有不同的生命周期方法,可以讓開發(fā)者能夠在組件的不同階段(掛載、更新、卸載),對(duì)組件做更多的控制。類組件則既可以充當(dāng)無狀態(tài)組件,也可以充當(dāng)有狀態(tài)組件。當(dāng)一個(gè)類組件不需要管理自身狀態(tài)時(shí),也可稱為無狀態(tài)組件。
(2)無狀態(tài)組件
特點(diǎn):
不依賴自身的狀態(tài)state 可以是類組件或者函數(shù)組件。 可以完全避免使用 this 關(guān)鍵字。(由于使用的是箭頭函數(shù)事件無需綁定) 有更高的性能。當(dāng)不需要使用生命周期鉤子時(shí),應(yīng)該首先使用無狀態(tài)函數(shù)組件 組件內(nèi)部不維護(hù) state ,只根據(jù)外部組件傳入的 props 進(jìn)行渲染的組件,當(dāng) props 改變時(shí),組件重新渲染。
使用場(chǎng)景:
組件不需要管理 state,純展示
優(yōu)點(diǎn):
簡(jiǎn)化代碼、專注于 render 組件不需要被實(shí)例化,無生命周期,提升性能。輸出(渲染)只取決于輸入(屬性),無副作用 視圖和數(shù)據(jù)的解耦分離
缺點(diǎn):
無法使用 ref 無生命周期方法 無法控制組件的重渲染,因?yàn)闊o法使用shouldComponentUpdate 方法,當(dāng)組件接受到新的屬性時(shí)則會(huì)重渲染
總結(jié):
組件內(nèi)部狀態(tài)且與外部無關(guān)的組件,可以考慮用狀態(tài)組件,這樣狀態(tài)樹就不會(huì)過于復(fù)雜,易于理解和管理。當(dāng)一個(gè)組件不需要管理自身狀態(tài)時(shí),也就是無狀態(tài)組件,應(yīng)該優(yōu)先設(shè)計(jì)為函數(shù)組件。比如自定義的 <Button/>、 <Input /> 等組件。
15. 對(duì)React中Fragment的理解,它的使用場(chǎng)景是什么?
在React中,組件返回的元素只能有一個(gè)根元素。為了不添加多余的DOM節(jié)點(diǎn),我們可以使用Fragment標(biāo)簽來包裹所有的元素,F(xiàn)ragment標(biāo)簽不會(huì)渲染出任何元素。React官方對(duì)Fragment的解釋:
“React 中的一個(gè)常見模式是一個(gè)組件返回多個(gè)元素。Fragments 允許你將子列表分組,而無需向 DOM 添加額外節(jié)點(diǎn)。
”
import React, { Component, Fragment } from 'react'
// 一般形式
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
// 也可以寫成以下形式
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
16. React如何獲取組件對(duì)應(yīng)的DOM元素?
可以用ref來獲取某個(gè)子節(jié)點(diǎn)的實(shí)例,然后通過當(dāng)前class組件實(shí)例的一些特定屬性來直接獲取子節(jié)點(diǎn)實(shí)例。ref有三種實(shí)現(xiàn)方法:
字符串格式:字符串格式,這是React16版本之前用得最多的,例如: <p ref="info">span</p>函數(shù)格式:ref對(duì)應(yīng)一個(gè)方法,該方法有一個(gè)參數(shù),也就是對(duì)應(yīng)的節(jié)點(diǎn)實(shí)例,例如: <p ref={ele => this.info = ele}></p>createRef方法:React 16提供的一個(gè)API,使用React.createRef()來實(shí)現(xiàn)
17. React中可以在render訪問refs嗎?為什么?
<>
<span id="name" ref={this.spanRef}>{this.state.title}</span>
<span>{
this.spanRef.current ? '有值' : '無值'
}</span>
</>
不可以,render 階段 DOM 還沒有生成,無法獲取 DOM。DOM 的獲取需要在 pre-commit 階段和 commit 階段:
18. 對(duì)React的插槽(Portals)的理解,如何使用,有哪些使用場(chǎng)景
React 官方對(duì) Portals 的定義:
“Portal 提供了一種將子節(jié)點(diǎn)渲染到存在于父組件以外的 DOM 節(jié)點(diǎn)的優(yōu)秀的方案
”
Portals 是React 16提供的官方解決方案,使得組件可以脫離父組件層級(jí)掛載在DOM樹的任何位置。通俗來講,就是我們 render 一個(gè)組件,但這個(gè)組件的 DOM 結(jié)構(gòu)并不在本組件內(nèi)。
Portals語法如下:
ReactDOM.createPortal(child, container);
第一個(gè)參數(shù) child 是可渲染的 React 子項(xiàng),比如元素,字符串或者片段等; 第二個(gè)參數(shù) container 是一個(gè) DOM 元素。
一般情況下,組件的render函數(shù)返回的元素會(huì)被掛載在它的父級(jí)組件上:
import DemoComponent from './DemoComponent';
render() {
// DemoComponent元素會(huì)被掛載在id為parent的div的元素上
return (
<div id="parent">
<DemoComponent />
</div>
);
}
然而,有些元素需要被掛載在更高層級(jí)的位置。最典型的應(yīng)用場(chǎng)景:當(dāng)父組件具有overflow: hidden或者z-index的樣式設(shè)置時(shí),組件有可能被其他元素遮擋,這時(shí)就可以考慮要不要使用Portal使組件的掛載脫離父組件。例如:對(duì)話框,模態(tài)窗。
import DemoComponent from './DemoComponent';
render() {
// react會(huì)將DemoComponent組件直接掛載在真實(shí)的 dom 節(jié)點(diǎn) domNode 上,生命周期還和16版本之前相同。
return ReactDOM.createPortal(
<DemoComponent />,
domNode,
);
}
19. 在React中如何避免不必要的render?
React 基于虛擬 DOM 和高效 Diff 算法的完美配合,實(shí)現(xiàn)了對(duì) DOM 最小粒度的更新。大多數(shù)情況下,React 對(duì) DOM 的渲染效率足以業(yè)務(wù)日常。但在個(gè)別復(fù)雜業(yè)務(wù)場(chǎng)景下,性能問題依然會(huì)困擾我們。此時(shí)需要采取一些措施來提升運(yùn)行性能,其很重要的一個(gè)方向,就是避免不必要的渲染(Render)。這里提下優(yōu)化的點(diǎn):
shouldComponentUpdate 和 PureComponent
在 React 類組件中,可以利用 shouldComponentUpdate或者 PureComponent 來減少因父組件更新而觸發(fā)子組件的 render,從而達(dá)到目的。shouldComponentUpdate 來決定是否組件是否重新渲染,如果不希望組件重新渲染,返回 false 即可。
利用高階組件
在函數(shù)組件中,并沒有 shouldComponentUpdate 這個(gè)生命周期,可以利用高階組件,封裝一個(gè)類似 PureComponet 的功能
使用 React.memo
React.memo 是 React 16.6 新的一個(gè) API,用來緩存組件的渲染,避免不必要的更新,其實(shí)也是一個(gè)高階組件,與 PureComponent 十分類似,但不同的是, React.memo只能用于函數(shù)組件。
20. 對(duì) React-Intl 的理解,它的工作原理?
React-intl是雅虎的語言國(guó)際化開源項(xiàng)目FormatJS的一部分,通過其提供的組件和API可以與ReactJS綁定。
React-intl提供了兩種使用方法,一種是引用React組件,另一種是直接調(diào)取API,官方更加推薦在React項(xiàng)目中使用前者,只有在無法使用React組件的地方,才應(yīng)該調(diào)用框架提供的API。它提供了一系列的React組件,包括數(shù)字格式化、字符串格式化、日期格式化等。
在React-intl中,可以配置不同的語言包,他的工作原理就是根據(jù)需要,在語言包之間進(jìn)行切換。
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。

“分享、點(diǎn)贊、在看” 支持一波??


