React16源碼解讀:開篇帶你搞懂幾個(gè)面試考點(diǎn)
如今,主流的前端框架React,Vue和Angular在前端領(lǐng)域已成三足鼎立之勢(shì),基于前端技術(shù)棧的發(fā)展現(xiàn)狀,大大小小的公司或多或少也會(huì)使用其中某一項(xiàng)或者多項(xiàng)技術(shù)棧,那么掌握并熟練使用其中至少一種也成為了前端人員必不可少的技能飯碗。當(dāng)然,框架的部分實(shí)現(xiàn)細(xì)節(jié)也常成為面試中的考察要點(diǎn),因此,一方面為了應(yīng)付面試官的連番追問(wèn),另一方面為了提升自己的技能水平,還是有必要對(duì)框架的底層實(shí)現(xiàn)原理有一定的涉獵。
當(dāng)然對(duì)于主攻哪門技術(shù)棧沒(méi)有嚴(yán)格的要求,挑選你自己喜歡的就好,在面試中面試官一般會(huì)先問(wèn)你最熟悉的是哪門技術(shù)棧,對(duì)于你不熟悉的領(lǐng)域,面試官可能也不會(huì)做太多的追問(wèn)。筆者在項(xiàng)目中一直是使用的Vue框架,其上手門檻低,也提供了比較全面和友好的官方文檔可供參考。但是可能因人而異,感覺(jué)自己還是比較喜歡React,也說(shuō)不出什么好壞,可能是自己最早接觸的前端框架吧,不過(guò)很遺憾,在之前的工作中一直派不上用場(chǎng),但即便如此,也阻擋不了自己對(duì)底層原理的好奇心。所以最近也是開始研究React的源碼,并對(duì)源碼的解讀過(guò)程做一下記錄,方便加深記憶。如果你的技術(shù)棧剛好是React,并且也對(duì)源碼感興趣,那么我們可以一起互相探討技術(shù)難點(diǎn),讓整個(gè)閱讀源碼的過(guò)程變得更加容易和有趣。
1、準(zhǔn)備階段
在facebook的github上,目前React的最新版本為v16.12.0,我們知道在React的v16版本之后引入了新的Fiber架構(gòu),這種架構(gòu)使得任務(wù)擁有了暫停和恢復(fù)機(jī)制,將一個(gè)大的更新任務(wù)拆分為一個(gè)一個(gè)執(zhí)行單元,充分利用瀏覽器在每一幀的空閑時(shí)間執(zhí)行任務(wù),無(wú)空閑時(shí)間則延遲執(zhí)行,從而避免了任務(wù)的長(zhǎng)時(shí)間運(yùn)行導(dǎo)致阻塞主線程同步任務(wù)的執(zhí)行。為了了解這種Fiber架構(gòu),這里選擇了一個(gè)比較適中的v16.10.2的版本,沒(méi)有選擇最新的版本是因?yàn)樵谧钚掳姹局幸瞥艘恍┡f的兼容處理方案,雖說(shuō)這些方案只是為了兼容,但是其思想還是比較先進(jìn)的,值得我們推敲學(xué)習(xí),所以先將其保留下來(lái),這里選擇v16.10.2版本的另外一個(gè)原因是React在v16.10.0的版本中涉及到兩個(gè)比較重要的優(yōu)化點(diǎn):

在上圖中指出,在任務(wù)調(diào)度(Scheduler)階段有兩個(gè)性能的優(yōu)化點(diǎn),解釋如下:
將任務(wù)隊(duì)列的內(nèi)部數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換成最小二叉堆的形式以提升隊(duì)列的性能(在最小堆中我們能夠以最快的速度找到最小的那個(gè)值,因?yàn)槟莻€(gè)值一定在堆的頂部,有效減少整個(gè)數(shù)據(jù)結(jié)構(gòu)的查找時(shí)間)。
使用周期更短的
postMessage循環(huán)的方式而不是使用requestAnimationFrame這種與幀邊界對(duì)齊的方式(這種優(yōu)化方案指得是在將任務(wù)進(jìn)行延遲后恢復(fù)執(zhí)行的階段,前后兩種方案都是宏任務(wù),但是宏任務(wù)也有順序之分,postMessage的優(yōu)先級(jí)比requestAnimationFrame高,這也就意味著延遲任務(wù)能夠更快速地恢復(fù)并執(zhí)行)。
當(dāng)然現(xiàn)在不太理解的話沒(méi)關(guān)系,后續(xù)會(huì)有單獨(dú)的文章來(lái)介紹任務(wù)調(diào)度這一塊內(nèi)容,遇到上述兩個(gè)優(yōu)化點(diǎn)的時(shí)候會(huì)進(jìn)行詳細(xì)說(shuō)明,在開始閱讀源碼之前,我們可以使用create-react-app來(lái)快速搭建一個(gè)React項(xiàng)目,后續(xù)的示例代碼可以在此項(xiàng)目上進(jìn)行編寫:
//?項(xiàng)目搭建完成后React默認(rèn)為最新版v16.12.0
create-react-app?react-learning
//?為了保證版本一致,手動(dòng)將其修改為v16.10.2
npm?install?--save?react@16.10.2?react-dom@16.10.2
//?運(yùn)行項(xiàng)目
npm?start
執(zhí)行以上步驟后,不出意外的話,瀏覽器中會(huì)正常顯示出項(xiàng)目的默認(rèn)界面。得益于在Reactv16.8版本之后推出的React Hooks功能,讓我們?cè)谠瓉?lái)的無(wú)狀態(tài)函數(shù)組件中也能進(jìn)行狀態(tài)管理,以及使用相應(yīng)的生命周期鉤子,甚至在新版的create-react-app腳手架中,根組件App已經(jīng)由原來(lái)的類組件的寫法升級(jí)為了推薦的函數(shù)定義組件的方式,但是原來(lái)的類組件的寫法并沒(méi)有被廢棄掉,事實(shí)上我們項(xiàng)目中還是會(huì)大量充斥著類組件的寫法,因此為了了解這種類組件的實(shí)現(xiàn)原理,我們暫且將App根組件的函數(shù)定義的寫法回退到類組件的形式,并對(duì)其內(nèi)容進(jìn)行簡(jiǎn)單修改:
//?src?->?App.js
import?React,?{Component}?from?'react';
function?List({data})?{
????return?(
????????<ul?className="data-list">
????????????{
????????????????data.map(item?=>?{
????????????????????return?<li?className="data-item"?key={item}>{item}li>
????????????????})
????????????}
????????ul>
????);
}
export?default?class?App?extends?Component?{
????constructor(props)?{
????????super(props);
????????this.state?=?{
????????????data:?[1,?2,?3]
????????};
????}
????render()?{
????????return?(
????????????<div?className="container">
????????????????<h1?className="title">React?learningh1>
????????????????<List?data={this.state.data}?/>
????????????div>
????????);
????}
}
經(jīng)過(guò)以上簡(jiǎn)單修改后,然后我們通過(guò)調(diào)用
//?src?->?index.js
ReactDOM.render(<App?/>,?document.getElementById('root'));
來(lái)將組件掛載到DOM容器中,最終得到App組件的DOM結(jié)構(gòu)如下所示:
<div?class="container">
????<h1?class="title">React?learningh1>
????<ul?class="data-list">
????????<li?class="data-item">1li>
????????<li?class="data-item">2li>
????????<li?class="data-item">3li>
????ul>
div>
因此我們分析React源碼的入口也將會(huì)是從ReactDOM.render方法開始一步一步分析組件渲染的整個(gè)流程,但是在此之前,我們有必要先了解幾個(gè)重要的前置知識(shí)點(diǎn),這幾個(gè)知識(shí)點(diǎn)將會(huì)更好地幫助我們理解源碼的函數(shù)調(diào)用棧中的參數(shù)意義和其他的一些細(xì)節(jié)。
2、前置知識(shí)
首先我們需要明確的是,在上述示例中,App組件的render方法返回的是一段HTML結(jié)構(gòu),在普通的函數(shù)中這種寫法是不支持的,所以我們一般需要相應(yīng)的插件來(lái)在背后支撐,在React中為了支持這種jsx語(yǔ)法提供了一個(gè)Babel預(yù)置工具包@babel/preset-react,其中這個(gè)preset又包含了兩個(gè)比較核心的插件:
@babel/plugin-syntax-jsx:這個(gè)插件的作用就是為了讓Babel編譯器能夠正確解析出jsx語(yǔ)法。@babel/plugin-transform-react-jsx:在解析完jsx語(yǔ)法后,因?yàn)槠浔举|(zhì)上是一段HTML結(jié)構(gòu),因此為了讓JS引擎能夠正確識(shí)別,我們就需要通過(guò)該插件將jsx語(yǔ)法編譯轉(zhuǎn)換為另外一種形式。在默認(rèn)情況下,會(huì)使用React.createElement來(lái)進(jìn)行轉(zhuǎn)換,當(dāng)然我們也可以在.babelrc文件中來(lái)進(jìn)行手動(dòng)設(shè)置。
//?.babelrc
{
????"plugins":?[
????????["@babel/plugin-transform-react-jsx",?{
????????????"pragma":?"Preact.h",?//?default?pragma?is?React.createElement
????????????"pragmaFrag":?"Preact.Fragment",?//?default?is?React.Fragment
????????????"throwIfNamespace":?false?//?defaults?to?true
????????}]
????]
}
這里為了方便起見(jiàn),我們可以直接使用Babel官方實(shí)驗(yàn)室來(lái)查看轉(zhuǎn)換后的結(jié)果,對(duì)應(yīng)上述示例,轉(zhuǎn)換后的結(jié)果如下所示:
//?轉(zhuǎn)換前
render()?{
????return?(
????????<div?className="container">
????????????<h1?className="title">React?learningh1>
????????????<List?data={this.state.data}?/>
????????div>
????);
}
//?轉(zhuǎn)換后
render()?{
????return?React.createElement("div",?{
????????className:?"content"
????},?
????React.createElement("header",?null,?"React?learning"),?
????React.createElement(List,?{?data:?this.state.data?}));
}
可以看到jsx語(yǔ)法最終被轉(zhuǎn)換成由React.createElement方法組成的嵌套調(diào)用鏈,可能你之前已經(jīng)了解過(guò)這個(gè)API,或者接觸過(guò)一些偽代碼實(shí)現(xiàn),這里我們就基于源碼,深入源碼內(nèi)部來(lái)看看其背后為我們做了哪些事情。
2.1 createElement & ReactElement
為了保證源碼的一致性,也建議你將React版本和筆者保持一致,采用v16.10.2版本,可以通過(guò)facebook的github官方渠道進(jìn)行獲取,下載下來(lái)之后我們通過(guò)如下路徑來(lái)打開我們需要查看的文件:
//?react-16.10.2?->?packages?->?react?->?src?->?React.js?
在React.js文件中,我們直接跳轉(zhuǎn)到第63行,可以看到React變量作為一個(gè)對(duì)象字面量,包含了很多我們所熟知的方法,包括在v16.8版本之后推出的React Hooks方法:
const?React?=?{
??Children:?{
????map,
????forEach,
????count,
????toArray,
????only,
??},
??createRef,
??Component,
??PureComponent,
??createContext,
??forwardRef,
??lazy,
??memo,
??//?一些有用的React?Hooks方法
??useCallback,
??useContext,
??useEffect,
??useImperativeHandle,
??useDebugValue,
??useLayoutEffect,
??useMemo,
??useReducer,
??useRef,
??useState,
??Fragment:?REACT_FRAGMENT_TYPE,
??Profiler:?REACT_PROFILER_TYPE,
??StrictMode:?REACT_STRICT_MODE_TYPE,
??Suspense:?REACT_SUSPENSE_TYPE,
??unstable_SuspenseList:?REACT_SUSPENSE_LIST_TYPE,
??//?重點(diǎn)先關(guān)注這里,生產(chǎn)模式下使用后者
??createElement:?__DEV__???createElementWithValidation?:?createElement,
??cloneElement:?__DEV__???cloneElementWithValidation?:?cloneElement,
??createFactory:?__DEV__???createFactoryWithValidation?:?createFactory,
??isValidElement:?isValidElement,
??version:?ReactVersion,
??unstable_withSuspenseConfig:?withSuspenseConfig,
??__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:?ReactSharedInternals,
這里我們暫且先關(guān)注createElement方法,在生產(chǎn)模式下它來(lái)自于與React.js同級(jí)別的ReactElement.js文件,我們打開該文件,并直接跳轉(zhuǎn)到第312行,可以看到createElement方法的函數(shù)定義(去除了一些__DEV__環(huán)境才會(huì)執(zhí)行的代碼):
/**
?*?該方法接收包括但不限于三個(gè)參數(shù),與上述示例中的jsx語(yǔ)法經(jīng)過(guò)轉(zhuǎn)換之后的實(shí)參進(jìn)行對(duì)應(yīng)
?*?@param?type?表示當(dāng)前節(jié)點(diǎn)的類型,可以是原生的DOM標(biāo)簽字符串,也可以是函數(shù)定義組件或者其它類型
?*?@param?config?表示當(dāng)前節(jié)點(diǎn)的屬性配置信息
?*?@param?children?表示當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn),可以不傳,也可以傳入原始的字符串文本,甚至可以傳入多個(gè)子節(jié)點(diǎn)
?*?@returns?返回的是一個(gè)ReactElement對(duì)象
?*/
export?function?createElement(type,?config,?children)?{
??let?propName;
??//?Reserved?names?are?extracted
??//?用于存放config中的屬性,但是過(guò)濾了一些內(nèi)部受保護(hù)的屬性名
??const?props?=?{};
??//?將config中的key和ref屬性使用變量進(jìn)行單獨(dú)保存
??let?key?=?null;
??let?ref?=?null;
??let?self?=?null;
??let?source?=?null;
??//?config為null表示節(jié)點(diǎn)沒(méi)有設(shè)置任何相關(guān)屬性
??if?(config?!=?null)?{
????//?有效性判斷,判斷?config.ref?!==?undefined
????if?(hasValidRef(config))?{
??????ref?=?config.ref;
????}
????//?有效性判斷,判斷?config.key?!==?undefined
????if?(hasValidKey(config))?{
??????key?=?''?+?config.key;
????}
????self?=?config.__self?===?undefined???null?:?config.__self;
????source?=?config.__source?===?undefined???null?:?config.__source;
????//?Remaining?properties?are?added?to?a?new?props?object
????//?用于將config中的所有屬性在過(guò)濾掉內(nèi)部受保護(hù)的屬性名后,將剩余的屬性全部拷貝到props對(duì)象中存儲(chǔ)
????//?const?RESERVED_PROPS?=?{
????//???key:?true,
????//???ref:?true,
????//???__self:?true,
????//???__source:?true,
????//?};
????for?(propName?in?config)?{
??????if?(
????????hasOwnProperty.call(config,?propName)?&&
????????!RESERVED_PROPS.hasOwnProperty(propName)
??????)?{
????????props[propName]?=?config[propName];
??????}
????}
??}
??//?Children?can?be?more?than?one?argument,?and?those?are?transferred?onto
??//?the?newly?allocated?props?object.
??//?由于子節(jié)點(diǎn)的數(shù)量不限,因此從第三個(gè)參數(shù)開始,判斷剩余參數(shù)的長(zhǎng)度
??//?具有多個(gè)子節(jié)點(diǎn)則props.children屬性存儲(chǔ)為一個(gè)數(shù)組
??const?childrenLength?=?arguments.length?-?2;
??if?(childrenLength?===?1)?{
????//?單節(jié)點(diǎn)的情況下props.children屬性直接存儲(chǔ)對(duì)應(yīng)的節(jié)點(diǎn)
????props.children?=?children;
??}?else?if?(childrenLength?>?1)?{
????//?多節(jié)點(diǎn)的情況下則根據(jù)子節(jié)點(diǎn)數(shù)量創(chuàng)建一個(gè)數(shù)組
????const?childArray?=?Array(childrenLength);
????for?(let?i?=?0;?i???????childArray[i]?=?arguments[i?+?2];
????}
????props.children?=?childArray;
??}
??//?Resolve?default?props
??//?此處用于解析靜態(tài)屬性defaultProps
??//?針對(duì)于類組件或函數(shù)定義組件的情況,可以單獨(dú)設(shè)置靜態(tài)屬性defaultProps
??//?如果有設(shè)置defaultProps,則遍歷每個(gè)屬性并將其賦值到props對(duì)象中(前提是該屬性在props對(duì)象中對(duì)應(yīng)的值為undefined)
??if?(type?&&?type.defaultProps)?{
????const?defaultProps?=?type.defaultProps;
????for?(propName?in?defaultProps)?{
??????if?(props[propName]?===?undefined)?{
????????props[propName]?=?defaultProps[propName];
??????}
????}
??}
??//?最終返回一個(gè)ReactElement對(duì)象
??return?ReactElement(
????type,
????key,
????ref,
????self,
????source,
????ReactCurrentOwner.current,
????props,
??);
}
經(jīng)過(guò)上述分析我們可以得出,在類組件的render方法中最終返回的是由多個(gè)ReactElement對(duì)象組成的多層嵌套結(jié)構(gòu),所有的子節(jié)點(diǎn)信息均存放在父節(jié)點(diǎn)的props.children屬性中。我們將源碼定位到ReactElement.js的第111行,可以看到ReactElement函數(shù)的完整實(shí)現(xiàn):
/**
?*?為一個(gè)工廠函數(shù),每次執(zhí)行都會(huì)創(chuàng)建并返回一個(gè)ReactElement對(duì)象
?*?@param?type?表示節(jié)點(diǎn)所對(duì)應(yīng)的類型,與React.createElement方法的第一個(gè)參數(shù)保持一致
?*?@param?key?表示節(jié)點(diǎn)所對(duì)應(yīng)的唯一標(biāo)識(shí),一般在列表渲染中我們需要為每個(gè)節(jié)點(diǎn)設(shè)置key屬性
?*?@param?ref?表示對(duì)節(jié)點(diǎn)的引用,可以通過(guò)React.createRef()或者useRef()來(lái)創(chuàng)建引用
?*?@param?self?該屬性只有在開發(fā)環(huán)境才存在
?*?@param?source?該屬性只有在開發(fā)環(huán)境才存在
?*?@param?owner?一個(gè)內(nèi)部屬性,指向ReactCurrentOwner.current,表示一個(gè)Fiber節(jié)點(diǎn)
?*?@param?props?表示該節(jié)點(diǎn)的屬性信息,在React.createElement中通過(guò)config,children參數(shù)和defaultProps靜態(tài)屬性得到
?*?@returns?返回一個(gè)ReactElement對(duì)象
?*/
const?ReactElement?=?function(type,?key,?ref,?self,?source,?owner,?props)?{
??const?element?=?{
????//?This?tag?allows?us?to?uniquely?identify?this?as?a?React?Element
????//?這里僅僅加了一個(gè)$$typeof屬性,用于標(biāo)識(shí)這是一個(gè)React?Element
????$$typeof:?REACT_ELEMENT_TYPE,
????//?Built-in?properties?that?belong?on?the?element
????type:?type,
????key:?key,
????ref:?ref,
????props:?props,
????//?Record?the?component?responsible?for?creating?this?element.
????_owner:?owner,
??};
??...
??return?element;
};
一個(gè)ReactElement對(duì)象的結(jié)構(gòu)相對(duì)而言還是比較簡(jiǎn)單,主要是增加了一個(gè)$$typeof屬性用于標(biāo)識(shí)該對(duì)象是一個(gè)React Element類型。REACT_ELEMENT_TYPE在支持Symbol類型的環(huán)境中為symbol類型,否則為number類型的數(shù)值。與REACT_ELEMENT_TYPE對(duì)應(yīng)的還有很多其他的類型,均存放在shared/ReactSymbols目錄中,這里我們可以暫且只關(guān)心這一種,后面遇到其他類型再來(lái)細(xì)看。
2.2 Component & PureComponent
了解完ReactElement對(duì)象的結(jié)構(gòu)之后,我們?cè)倩氐街暗氖纠ㄟ^(guò)繼承React.Component我們將App組件修改為了一個(gè)類組件,我們不妨先來(lái)研究下React.Component的底層實(shí)現(xiàn)。React.Component的源碼存放在packages/react/src/ReactBaseClasses.js文件中,我們將源碼定位到第21行,可以看到Component構(gòu)造函數(shù)的完整實(shí)現(xiàn):
/**
?*?構(gòu)造函數(shù),用于創(chuàng)建一個(gè)類組件的實(shí)例
?*?@param?props?表示所擁有的屬性信息
?*?@param?context?表示所處的上下文信息
?*?@param?updater?表示一個(gè)updater對(duì)象,這個(gè)對(duì)象非常重要,用于處理后續(xù)的更新調(diào)度任務(wù)
?*/
function?Component(props,?context,?updater)?{
??this.props?=?props;
??this.context?=?context;
??//?If?a?component?has?string?refs,?we?will?assign?a?different?object?later.
??//?該屬性用于存儲(chǔ)類組件實(shí)例的引用信息
??//?在React中我們可以有多種方式來(lái)創(chuàng)建引用
??//?通過(guò)字符串的方式,如:
??//?通過(guò)回調(diào)函數(shù)的方式,如: this.inputRef = input;}?/>
??//?通過(guò)React.createRef()的方式,如:this.inputRef = React.createRef(null);?
??//?通過(guò)useRef()的方式,如:this.inputRef = useRef(null);?
??this.refs?=?emptyObject;
??//?We?initialize?the?default?updater?but?the?real?one?gets?injected?by?the
??//?renderer.
??//?當(dāng)state發(fā)生變化的時(shí)候,需要updater對(duì)象去處理后續(xù)的更新調(diào)度任務(wù)
??//?這部分涉及到任務(wù)調(diào)度的內(nèi)容,在后續(xù)分析到任務(wù)調(diào)度階段的時(shí)候再來(lái)細(xì)看
??this.updater?=?updater?||?ReactNoopUpdateQueue;
}
//?在原型上新增了一個(gè)isReactComponent屬性用于標(biāo)識(shí)該實(shí)例是一個(gè)類組件的實(shí)例
//?這個(gè)地方曾經(jīng)有面試官考過(guò),問(wèn)如何區(qū)分函數(shù)定義組件和類組件
//?函數(shù)定義組件是沒(méi)有這個(gè)屬性的,所以可以通過(guò)判斷原型上是否擁有這個(gè)屬性來(lái)進(jìn)行區(qū)分
Component.prototype.isReactComponent?=?{};
/**
?*?用于更新?tīng)顟B(tài)
?*?@param?partialState?表示下次需要更新的狀態(tài)
?*?@param?callback?在組件更新之后需要執(zhí)行的回調(diào)
?*/
Component.prototype.setState?=?function(partialState,?callback)?{
??...
??this.updater.enqueueSetState(this,?partialState,?callback,?'setState');
};
/**
?*?用于強(qiáng)制重新渲染
?*?@param?callback?在組件重新渲染之后需要執(zhí)行的回調(diào)
?*/
Component.prototype.forceUpdate?=?function(callback)?{
??this.updater.enqueueForceUpdate(this,?callback,?'forceUpdate');
};
上述內(nèi)容中涉及到任務(wù)調(diào)度的會(huì)在后續(xù)講解到調(diào)度階段的時(shí)候再來(lái)細(xì)講,現(xiàn)在我們知道可以通過(guò)原型上的isReactComponent屬性來(lái)區(qū)分函數(shù)定義組件和類組件。事實(shí)上,在源碼中就是通過(guò)這個(gè)屬性來(lái)區(qū)分Class Component和Function Component的,可以找到以下方法:
//?返回true則表示類組件,否則表示函數(shù)定義組件
function?shouldConstruct(Component)?{
??return?!!(Component.prototype?&&?Component.prototype.isReactComponent);
}
與Component構(gòu)造函數(shù)對(duì)應(yīng)的,還有一個(gè)PureComponent構(gòu)造函數(shù),這個(gè)我們應(yīng)該還是比較熟悉的,通過(guò)淺比較判斷組件前后傳遞的屬性是否發(fā)生修改來(lái)決定是否需要重新渲染組件,在一定程度上避免組件重渲染導(dǎo)致的性能問(wèn)題。同樣的,在ReactBaseClasses.js文件中,我們來(lái)看看PureComponent的底層實(shí)現(xiàn):
//?通過(guò)借用構(gòu)造函數(shù),實(shí)現(xiàn)典型的寄生組合式繼承,避免原型污染
function?ComponentDummy()?{}
ComponentDummy.prototype?=?Component.prototype;
function?PureComponent(props,?context,?updater)?{
??this.props?=?props;
??this.context?=?context;
??//?If?a?component?has?string?refs,?we?will?assign?a?different?object?later.
??this.refs?=?emptyObject;
??this.updater?=?updater?||?ReactNoopUpdateQueue;
}
//?將PureComponent的原型指向借用構(gòu)造函數(shù)的實(shí)例
const?pureComponentPrototype?=?(PureComponent.prototype?=?new?ComponentDummy());
//?重新設(shè)置構(gòu)造函數(shù)的指向
pureComponentPrototype.constructor?=?PureComponent;
//?Avoid?an?extra?prototype?jump?for?these?methods.
//?將Component.prototype和PureComponent.prototype進(jìn)行合并,減少原型鏈查找所浪費(fèi)的時(shí)間(原型鏈越長(zhǎng)所耗費(fèi)的時(shí)間越久)
Object.assign(pureComponentPrototype,?Component.prototype);
//?這里是與Component的區(qū)別之處,PureComponent的原型上擁有一個(gè)isPureReactComponent屬性
pureComponentPrototype.isPureReactComponent?=?true;
通過(guò)以上分析,我們就可以初步得出Component和PureComponent之間的差異,可以通過(guò)判斷原型上是否擁有isPureReactComponent屬性來(lái)進(jìn)行區(qū)分,當(dāng)然更細(xì)粒度的區(qū)分,還需要在閱讀后續(xù)的源碼內(nèi)容之后才能見(jiàn)分曉。
3、面試考點(diǎn)
看完以上內(nèi)容,按道理來(lái)說(shuō)以下幾個(gè)可能的面試考點(diǎn)應(yīng)該就不成問(wèn)題了,或者說(shuō)至少也不會(huì)遇到一個(gè)字也回答不了的尷尬局面,試試看吧:
在React中為何能夠支持
jsx語(yǔ)法類組件的
render方法執(zhí)行后最終返回的結(jié)果是什么手寫代碼實(shí)現(xiàn)一個(gè)
createElement方法如何判斷一個(gè)對(duì)象是不是
React Element如何區(qū)分類組件和函數(shù)定義組件
Component和PureComponent之間的關(guān)系如何區(qū)分
Component和PureComponent
4、總結(jié)
本文作為React16源碼解讀的開篇,先講解了幾個(gè)比較基礎(chǔ)的前置知識(shí)點(diǎn),這些知識(shí)點(diǎn)有助于我們?cè)诤罄m(xù)分析組件的任務(wù)調(diào)度和渲染過(guò)程時(shí)能夠更好地去理解源碼。閱讀源碼的過(guò)程是痛苦的,一個(gè)原因是源碼量巨大,文件依賴關(guān)系復(fù)雜容易讓人產(chǎn)生恐懼退縮心理,另一個(gè)是閱讀源碼是個(gè)漫長(zhǎng)的過(guò)程,期間可能會(huì)占用你學(xué)習(xí)其他新技術(shù)的時(shí)間,讓你無(wú)法完全靜下心來(lái)。但是其實(shí)我們要明白的是,學(xué)習(xí)源碼不只是為了應(yīng)付面試,源碼中其實(shí)有很多我們可以借鑒的設(shè)計(jì)模式或者使用技巧,如果我們可以學(xué)習(xí)并應(yīng)用到我們正在做的項(xiàng)目中,也不失為一件有意義的事情。后續(xù)文章就從ReactDOM.render方法開始,一步一步分析組件渲染的整個(gè)流程,我們也不需要去搞懂每一行代碼,畢竟每個(gè)人的思路不太一樣,但是關(guān)鍵步驟我們還是需要去多花時(shí)間理解的。

往期推薦



最后
歡迎加我微信,拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...


