生命周期
初始化階段
constructor
構(gòu)造函數(shù)getDefaultProps
props
默認值getInitialState
state
默認值掛載階段
componentWillMount
組件初始化渲染前調(diào)用render
組件渲染componentDidMount
組件掛載到DOM
后調(diào)用更新階段
componentWillReceiveProps
組件將要接收新props
前調(diào)用shouldComponentUpdate
組件是否需要更新componentWillUpdate
組件更新前調(diào)用render
組件渲染componentDidUpdate
組件更新后調(diào)用卸載階段
componentWillUnmount
組件卸載前調(diào)用
生命周期
初始化階段
constructor
構(gòu)造函數(shù)getDefaultProps
props
默認值getInitialState
state
默認值掛載階段
staticgetDerivedStateFromProps(props,state)
render
componentDidMount
getDerivedStateFromProps:組件每次被 rerender
的時候耐薯,包括在組件構(gòu)建之后(虛擬 dom
之后炎疆,實際 dom
掛載之前),每次獲取新的 props
或 state
之后讯屈;每次接收新的props之后都會返回一個對象作為新的 state
,返回null則說明不需要更新 state
添祸;配合 componentDidUpdate
艳馒,可以覆蓋 componentWillReceiveProps
的所有用法
更新階段
staticgetDerivedStateFromProps(props,state)
shouldComponentUpdate
render
getSnapshotBeforeUpdate(prevProps,prevState)
componentDidUpdate
getSnapshotBeforeUpdate:觸發(fā)時間:
update發(fā)生的時候,在
render之后匆浙,在組件
dom渲染之前安寺;返回一個值,作為
componentDidUpdate的第三個參數(shù)首尼;配合
componentDidUpdate, 可以覆蓋
componentWillUpdate`的所有用法
卸載階段
componentWillUnmount
錯誤處理
componentDidCatch
React16
新的生命周期棄用了 componentWillMount挑庶、componentWillReceivePorps言秸,componentWillUpdate
新增了 getDerivedStateFromProps、getSnapshotBeforeUpdate
來代替棄用的三個鉤子函數(shù)迎捺。
React16并沒有刪除這三個鉤子函數(shù)举畸,但是不能和新增的鉤子函數(shù)混用,
React17將會刪除這三個鉤子函數(shù)凳枝,新增了對錯誤的處理(
componentDidCatch`)
setState是同步的還是異步的抄沮?
- 生命周期和合成事件中
在 React
的生命周期和合成事件中, React
仍然處于他的更新機制中岖瑰,這時無論調(diào)用多少次 setState
叛买,都會不會立即執(zhí)行更新,而是將要更新的·存入 _pendingStateQueue
蹋订,將要更新的組件存入 dirtyComponent
率挣。
當上一次更新機制執(zhí)行完畢,以生命周期為例辅辩,所有組件难礼,即最頂層組件 didmount
后會將批處理標志設(shè)置為 false
。這時將取出 dirtyComponent
中的組件以及 _pendingStateQueue
中的 state
進行更新玫锋。這樣就可以確保組件不會被重新渲染多次蛾茉。
當我們在執(zhí)行 setState
后立即去獲取 state
,這時是獲取不到更新后的 state
的撩鹿,因為處于 React
的批處理機制中谦炬, state
被暫存起來,待批處理機制完成之后节沦,統(tǒng)一進行更新键思。
所以。setState
本身并不是異步的甫贯,而是 React
的批處理機制給人一種異步的假象吼鳞。
-
異步代碼和原生事件中
當我們在異步代碼中調(diào)用setState
時,根據(jù)JavaScript
的異步機制叫搁,會將異步代碼先暫存赔桌,等所有同步代碼執(zhí)行完畢后在執(zhí)行,這時React
的批處理機制已經(jīng)走完渴逻,處理標志設(shè)被設(shè)置為false
疾党,這時再調(diào)用setState
即可立即執(zhí)行更新,拿到更新后的結(jié)果惨奕。
在原生事件中調(diào)用 setState
并不會出發(fā) React
的批處理機制雪位,所以立即能拿到最新結(jié)果。
- 最佳實踐
setState
的第二個參數(shù)接收一個函數(shù)梨撞,該函數(shù)會在 React
的批處理機制完成之后調(diào)用雹洗,所以你想在調(diào)用 setState
后立即獲取更新后的值香罐,請在該回調(diào)函數(shù)中獲取。
為什么有時連續(xù)多次setState只有一次生效队伟?
原因就是 React
會批處理機制中存儲的多個 setState
進行合并穴吹,來看下 React
源碼中的 _assign
函數(shù)幽勒,類似于 Object
的 assign
:
如果傳入的是對象嗜侮,很明顯會被合并成一次,所以上面的代碼兩次打印的結(jié)果是相同的
- 最佳實踐
React
會對多次連續(xù)的 setState
進行合并啥容,如果你想立即使用上次 setState
后的結(jié)果進行下一次 setState
锈颗,可以讓 setState
接收一個函數(shù)而不是一個對象。這個函數(shù)用上一個 state
作為第一個參數(shù)咪惠,將此次更新被應(yīng)用時的 props
做為第二個參數(shù)击吱。
React如何實現(xiàn)自己的事件機制?
React
事件并沒有綁定在真實的 Dom
節(jié)點上遥昧,而是通過事件代理覆醇,在最外層的 document
上對事件進行統(tǒng)一分發(fā)。
組件掛載炭臭、更新時:
通過
lastProps
永脓、nextProps
判斷是否新增、刪除事件分別調(diào)用事件注冊鞋仍、卸載方法常摧。調(diào)用
EventPluginHub
的enqueuePutListener
進行事件存儲獲取
document
對象。根據(jù)事件名稱(如
onClick
威创、onCaptureClick
)判斷是進行冒泡還是捕獲落午。判斷是否存在
addEventListener
方法,否則使用attachEvent
(兼容IE)肚豺。給
document
注冊原生事件回調(diào)為dispatchEvent
(統(tǒng)一的事件分發(fā)機制)溃斋。
事件初始化:
EventPluginHub
負責(zé)管理React
合成事件的callback
,它將callback
存儲在listenerBank
中吸申,另外還存儲了負責(zé)合成事件的Plugin
梗劫。獲取綁定事件的元素的唯一標識
key
。將
callback
根據(jù)事件類型呛谜,元素的唯一標識key
存儲在listenerBank
中在跳。listenerBank
的結(jié)構(gòu)是:listenerBank[registrationName][key]
。
觸發(fā)事件時:
觸發(fā)
document
注冊原生事件的回調(diào)dispatchEvent
獲取到觸發(fā)這個事件最深一級的元素
遍歷這個元素的所有父元素隐岛,依次對每一級元素進行處理猫妙。
構(gòu)造合成事件。
將每一級的合成事件存儲在
eventQueue
事件隊列中聚凹。遍歷
eventQueue
割坠。通過
isPropagationStopped
判斷當前事件是否執(zhí)行了阻止冒泡方法齐帚。如果阻止了冒泡,停止遍歷彼哼,否則通過
executeDispatch
執(zhí)行合成事件对妄。釋放處理完成的事件。
React
在自己的合成事件中重寫了 stopPropagation
方法敢朱,將 isPropagationStopped
設(shè)置為 true
剪菱,然后在遍歷每一級事件的過程中根據(jù)此遍歷判斷是否繼續(xù)執(zhí)行。這就是 React
自己實現(xiàn)的冒泡機制拴签。
為何React事件要自己綁定this孝常?
在上面提到的事件處理流程中, React
在 document
上進行統(tǒng)一的事件分發(fā)构灸, dispatchEvent
通過循環(huán)調(diào)用所有層級的事件來模擬事件冒泡和捕獲曹阔。
在 React
源碼中稿茉,當具體到某一事件處理函數(shù)將要調(diào)用時园蝠,將調(diào)用 invokeGuardedCallback
方法茂装。
function invokeGuardedCallback(name, func, a) {
try {
func(a);
} catch (x) {
if (caughtError === null) {
caughtError = x;
}
}}
可見彼妻,事件處理函數(shù)是直接調(diào)用的,并沒有指定調(diào)用的組件,所以不進行手動綁定的情況下直接獲取到的 this
是不準確的,所以我們需要手動將當前組件綁定到 this
上重斑。
原生事件和React事件的區(qū)別笛丙?
React
事件使用駝峰命名,而不是全部小寫。通過
JSX
, 你傳遞一個函數(shù)作為事件處理程序,而不是一個字符串送滞。在
React
中你不能通過返回false
來阻止默認行為。必須明確調(diào)用preventDefault
。
React的合成事件是什么?
React
根據(jù) W3C
規(guī)范定義了每個事件處理函數(shù)的參數(shù)旅东,即合成事件荤牍。
事件處理程序?qū)鬟f SyntheticEvent
的實例劈榨,這是一個跨瀏覽器原生事件包裝器。它具有與瀏覽器原生事件相同的接口晦嵌,包括 stopPropagation()
和 preventDefault()
同辣,在所有瀏覽器中他們工作方式都相同。
React
合成的 SyntheticEvent
采用了事件池惭载,這樣做可以大大節(jié)省內(nèi)存旱函,而不會頻繁的創(chuàng)建和銷毀事件對象。
另外棕兼,不管在什么瀏覽器環(huán)境下陡舅,瀏覽器會將該事件類型統(tǒng)一創(chuàng)建為合成事件,從而達到了瀏覽器兼容的目的伴挚。
React和原生事件的執(zhí)行順序是什么靶衍?可以混用嗎?
React
的所有事件都通過 document
進行統(tǒng)一分發(fā)茎芋。當真實 Dom
觸發(fā)事件后冒泡到 document
后才會對 React
事件進行處理颅眶。
所以原生的事件會先執(zhí)行,然后執(zhí)行 React
合成事件田弥,最后執(zhí)行真正在 document
上掛載的事件
React
事件和原生事件最好不要混用涛酗。原生事件中如果執(zhí)行了 stopPropagation
方法,則會導(dǎo)致其他 React
事件失效。因為所有元素的事件將無法冒泡到 document
上商叹,導(dǎo)致所有的 React
事件都將無法被觸發(fā)燕刻。。
虛擬Dom是什么剖笙?
在原生的 JavaScript
程序中卵洗,我們直接對 DOM
進行創(chuàng)建和更改,而 DOM
元素通過我們監(jiān)聽的事件和我們的應(yīng)用程序進行通訊弥咪。
而 React
會先將你的代碼轉(zhuǎn)換成一個 JavaScript
對象过蹂,然后這個 JavaScript
對象再轉(zhuǎn)換成真實 DOM
。這個 JavaScript
對象就是所謂的虛擬 DOM
聚至。
當我們需要創(chuàng)建或更新元素時酷勺, React
首先會讓這個 VitrualDom
對象進行創(chuàng)建和更改,然后再將 VitrualDom
對象渲染成真實DOM扳躬。
當我們需要對 DOM
進行事件監(jiān)聽時脆诉,首先對 VitrualDom
進行事件監(jiān)聽, VitrualDom
會代理原生的 DOM
事件從而做出響應(yīng)坦报。
虛擬Dom比普通Dom更快嗎库说?
很多文章說 VitrualDom
可以提升性能,這一說法實際上是很片面的片择。
直接操作 DOM
是非常耗費性能的,這一點毋庸置疑骚揍。但是 React
使用 VitrualDom
也是無法避免操作 DOM
的字管。
如果是首次渲染, VitrualDom
不具有任何優(yōu)勢信不,甚至它要進行更多的計算嘲叔,消耗更多的內(nèi)存。
VitrualDom
的優(yōu)勢在于 React
的 Diff
算法和批處理策略抽活, React
在頁面更新之前硫戈,提前計算好了如何進行更新和渲染 DOM
。實際上下硕,這個計算過程我們在直接操作 DOM
時丁逝,也是可以自己判斷和實現(xiàn)的,但是一定會耗費非常多的精力和時間梭姓,而且往往我們自己做的是不如 React
好的霜幼。所以,在這個過程中 React
幫助我們"提升了性能"誉尖。
所以罪既,我更傾向于說, VitrualDom
幫助我們提高了開發(fā)效率,在重復(fù)渲染時它幫助我們計算如何更高效的更新琢感,而不是它比 DOM
操作更快丢间。
虛擬Dom中的$$typeof屬性的作用是什么?
ReactElement
中有一個 $$typeof
屬性驹针,它被賦值為 REACT_ELEMENT_TYPE
:
var REACT_ELEMENT_TYPE =`
(typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) ||
0xeac7;
可見千劈, $$typeof
是一個 Symbol
類型的變量,這個變量可以防止 XSS
牌捷。
如果你的服務(wù)器有一個漏洞墙牌,允許用戶存儲任意 JSON
對象, 而客戶端代碼需要一個字符串暗甥,這可能會成為一個問題:
// JSON
let expectedTextButGotJSON = {
type: 'div',
props: {
dangerouslySetInnerHTML: {
__html: '/* put your exploit here */'
},
},
};
let message = { text: expectedTextButGotJSON };
<p>
{message.text}
</p>
JSON
中不能存儲 Symbol
類型的變量喜滨。
ReactElement.isValidElement
函數(shù)用來判斷一個 React
組件是否是有效的〕贩溃可見 React
渲染時會把沒有 $$typeof
標識虽风,以及規(guī)則校驗不通過的組件過濾掉。
當你的環(huán)境不支持 Symbol
時寄月, $$typeof
被賦值為 0xeac7
辜膝,至于為什么, React
開發(fā)者給出了答案:
0xeac7
看起來有點像React
漾肮。
React組件的渲染流程是什么厂抖?
使用
React.createElement
或JSX
編寫React
組件,實際上所有的JSX
代碼最后都會轉(zhuǎn)換成React.createElement(...)
克懊,Babel
幫助我們完成了這個轉(zhuǎn)換的過程忱辅。createElement
函數(shù)對key
和ref
等特殊的props
進行處理,并獲取defaultProps
對默認props
進行賦值谭溉,并且對傳入的孩子節(jié)點進行處理墙懂,最終構(gòu)造成一個ReactElement
對象(所謂的虛擬DOM
)。ReactDOM.render
將生成好的虛擬DOM
渲染到指定容器上扮念,其中采用了批處理损搬、事務(wù)等機制并且對特定瀏覽器進行了性能優(yōu)化,最終轉(zhuǎn)換為真實DOM
柜与。
為什么代碼中一定要引入React巧勤?
JSX
只是為 React.createElement(component,props,...children)
方法提供的語法糖。
所有的 JSX
代碼最后都會轉(zhuǎn)換成 React.createElement(...)
旅挤, Babel
幫助我們完成了這個轉(zhuǎn)換的過程踢关。
所以使用了 JSX
的代碼都必須引入 React
。
為什么React組件首字母必須大寫粘茄?
babel
在編譯時會判斷 JSX
中組件的首字母签舞,當首字母為小寫時秕脓,其被認定為原生 DOM
標簽, createElement
的第一個變量被編譯為字符串儒搭;當首字母為大寫時吠架,其被認定為自定義組件, createElement
的第一個變量被編譯為對象搂鲫;
React在渲染真實Dom時做了哪些性能優(yōu)化傍药?
在 IE(8-11)
和 Edge
瀏覽器中,一個一個插入無子孫的節(jié)點魂仍,效率要遠高于插入一整個序列化完整的節(jié)點樹拐辽。
React
通過 lazyTree
,在 IE(8-11)
和 Edge
中進行單個節(jié)點依次渲染節(jié)點擦酌,而在其他瀏覽器中則首先將整個大的 DOM
結(jié)構(gòu)構(gòu)建好俱诸,然后再整體插入容器。
并且赊舶,在單獨渲染節(jié)點時睁搭, React
還考慮了 fragment
等特殊節(jié)點,這些節(jié)點則不會一個一個插入渲染笼平。
什么是高階組件园骆?如何實現(xiàn)?
高階組件可以看作 React
對裝飾模式的一種實現(xiàn)寓调,高階組件就是一個函數(shù)锌唾,且該函數(shù)接受一個組件作為參數(shù),并返回一個新的組件捶牢。
高階組件( HOC
)是 React
中的高級技術(shù)鸠珠,用來重用組件邏輯。但高階組件本身并不是 ReactAPI
秋麸。它只是一種模式,這種模式是由 React
自身的組合性質(zhì)必然產(chǎn)生的炬太。
function visible(WrappedComponent) {
return class extends Component {
render() {
const { visible, ...props } = this.props;
if (visible === false) return null;
return <WrappedComponent {...props} />;
}
}
}
</pre>
上面的代碼就是一個 HOC
的簡單應(yīng)用灸蟆,函數(shù)接收一個組件作為參數(shù),并返回一個新組件亲族,新組建可以接收一個 visible props
炒考,根據(jù) visible
的值來判斷是否渲染Visible。
我們可以通過以下兩種方式實現(xiàn)高階組件:
屬性代理
函數(shù)返回一個我們自己定義的組件霎迫,然后在 render
中返回要包裹的組件斋枢,這樣我們就可以代理所有傳入的 props
,并且決定如何渲染知给,實際上 瓤帚,這種方式生成的高階組件就是原組件的父組件描姚,上面的函數(shù) visible
就是一個 HOC
屬性代理的實現(xiàn)方式。
function proxyHOC(WrappedComponent) {
return class extends Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
}
對比原生組件增強的項:
可操作所有傳入的
props
可操作組件的生命周期
可操作組件的
static
方法獲取
refs
反向繼承
返回一個組件戈次,繼承原組件轩勘,在 render
中調(diào)用原組件的 render
。由于繼承了原組件怯邪,能通過this訪問到原組件的 生命周期绊寻、props、state悬秉、render
等澄步,相比屬性代理它能操作更多的屬性。
function inheritHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
return super.render();
}
}
}
對比原生組件增強的項:
可操作所有傳入的
props
可操作組件的生命周期
可操作組件的
static
方法獲取
refs
可操作
state
可以渲染劫持
HOC在業(yè)務(wù)場景中有哪些實際應(yīng)用場景和泌?
HOC
可以實現(xiàn)的功能:
組合渲染
條件渲染
操作
props
獲取
refs
狀態(tài)管理
操作
state
渲染劫持
HOC
在業(yè)務(wù)中的實際應(yīng)用場景:
日志打點
權(quán)限控制
雙向綁定
表單校驗
高階組件(HOC)和Mixin的異同點是什么村缸?
Mixin
和 HOC
都可以用來解決 React
的代碼復(fù)用問題。
Mixin
可能會相互依賴允跑,相互耦合王凑,不利于代碼維護不同的
Mixin
中的方法可能會相互沖突Mixin
非常多時,組件是可以感知到的聋丝,甚至還要為其做相關(guān)處理索烹,這樣會給代碼造成滾雪球式的復(fù)雜性
而 HOC
的出現(xiàn)可以解決這些問題:
高階組件就是一個沒有副作用的純函數(shù),各個高階組件不會互相依賴耦合
高階組件也有可能造成沖突弱睦,但我們可以在遵守約定的情況下避免這些行為
高階組件并不關(guān)心數(shù)據(jù)使用的方式和原因百姓,而被包裹的組件也不關(guān)心數(shù)據(jù)來自何處。高階組件的增加不會為原組件增加負擔(dān)
Hook有哪些優(yōu)勢况木?
- 減少狀態(tài)邏輯復(fù)用的風(fēng)險
Hook
和 Mixin
在用法上有一定的相似之處垒拢,但是 Mixin
引入的邏輯和狀態(tài)是可以相互覆蓋的,而多個 Hook
之間互不影響火惊,這讓我們不需要在把一部分精力放在防止避免邏輯復(fù)用的沖突上求类。在不遵守約定的情況下使用 HOC
也有可能帶來一定沖突,比如 props
覆蓋等等屹耐,使用 Hook
則可以避免這些問題尸疆。
- 避免地獄式嵌套
大量使用 HOC
的情況下讓我們的代碼變得嵌套層級非常深,使用 HOC
惶岭,我們可以實現(xiàn)扁平式的狀態(tài)邏輯復(fù)用寿弱,而避免了大量的組件嵌套。
- 讓組件更容易理解
在使用 class
組件構(gòu)建我們的程序時按灶,他們各自擁有自己的狀態(tài)症革,業(yè)務(wù)邏輯的復(fù)雜使這些組件變得越來越龐大,各個生命周期中會調(diào)用越來越多的邏輯鸯旁,越來越難以維護噪矛。使用 Hook
量蕊,可以讓你更大限度的將公用邏輯抽離,將一個組件分割成更小的函數(shù)摩疑,而不是強制基于生命周期方法進行分割危融。
- 使用函數(shù)代替class
相比函數(shù),編寫一個 class
可能需要掌握更多的知識雷袋,需要注意的點也越多吉殃,比如 this
指向、綁定事件等等楷怒。另外蛋勺,計算機理解一個 class
比理解一個函數(shù)更快。Hooks
讓你可以在 classes
之外使用更多 React
的新特性鸠删。