深入理解react(源碼分析)
理解ReactElement和ReactClass的概念
首先讓我們理解兩個(gè)概念:
ReactElement
一個(gè)描述DOM節(jié)點(diǎn)或component實(shí)例的字面級(jí)對(duì)象。它包含一些信息,包括組件類(lèi)型type和屬性props。就像一個(gè)描述DOM節(jié)點(diǎn)的元素(虛擬節(jié)點(diǎn))。它們可以被創(chuàng)建通過(guò)React.createElement方法或jsx寫(xiě)法
分為DOM Element和Component Elements兩類(lèi):
- DOM Elements
當(dāng)節(jié)點(diǎn)的type屬性為字符串時(shí),它代表是普通的節(jié)點(diǎn),如div,span
- {
- type: 'button',
- props: {
- className: 'button button-blue',
- children: {
- type: 'b',
- props: {
- children: 'OK!'
- }
- }
- }
- }
- Component Elements
當(dāng)節(jié)點(diǎn)的type屬性為一個(gè)函數(shù)或一個(gè)類(lèi)時(shí),它代表自定義的節(jié)點(diǎn)
- class Button extends React.Component {
- render() {
- const { children, color } = this.props;
- return {
- type: 'button',
- props: {
- className: 'button button-' + color,
- children: {
- type: 'b',
- props: {
- children: children
- }
- }
- }
- };
- }
- }
- // Component Elements
- {
- type: Button,
- props: {
- color: 'blue',
- children: 'OK!'
- }
- }
ReactClass
ReactClass是平時(shí)我們寫(xiě)的Component組件(類(lèi)或函數(shù)),例如上面的Button類(lèi)。ReactClass實(shí)例化后調(diào)用render方法可返回DOM Element。
react渲染過(guò)程
過(guò)程理解:
- // element是 Component Elements
- ReactDOM.render({
- type: Form,
- props: {
- isSubmitted: false,
- buttonText: 'OK!'
- }
- }, document.getElementById('root'));
- 調(diào)用React.render方法,將我們的element根虛擬節(jié)點(diǎn)渲染到container元素中。element可以是一個(gè)字符串文本元素,也可以是如上介紹的ReactElement(分為DOM Elements, Component Elements)。
- 根據(jù)element的類(lèi)型不同,分別實(shí)例化ReactDOMTextComponent, ReactDOMComponent, ReactCompositeComponent類(lèi)。這些類(lèi)用來(lái)管理ReactElement,負(fù)責(zé)將不同的ReactElement轉(zhuǎn)化成DOM(mountComponent方法),負(fù)責(zé)更新DOM(receiveComponent方法,updateComponent方法, 如下會(huì)介紹)等。
- ReactCompositeComponent實(shí)例調(diào)用mountComponent方法后內(nèi)部調(diào)用render方法,返回了DOM Elements。再對(duì)如圖的步驟2⃣遞歸。
react更新機(jī)制
每個(gè)類(lèi)型的元素都要處理好自己的更新:
- 自定義元素的更新,主要是更新render出的節(jié)點(diǎn),做甩手掌柜交給render出的節(jié)點(diǎn)的對(duì)應(yīng)component去管理更新。
- text節(jié)點(diǎn)的更新很簡(jiǎn)單,直接更新文案。
- 瀏覽器基本元素的更新,分為兩塊:
- 先是更新屬性,對(duì)比出前后屬性的不同,局部更新。并且處理特殊屬性,比如事件綁定。
- 然后是子節(jié)點(diǎn)的更新,子節(jié)點(diǎn)更新主要是找出差異對(duì)象,找差異對(duì)象的時(shí)候也會(huì)使用上面的shouldUpdateReactComponent來(lái)判斷,如果是可以直接更新的就會(huì)遞歸調(diào)用子節(jié)點(diǎn)的更新,這樣也會(huì)遞歸查找差異對(duì)象。不可直接更新的刪除之前的對(duì)象或添加新的對(duì)象。之后根據(jù)差異對(duì)象操作dom元素(位置變動(dòng),刪除,添加等)。
***步:調(diào)用this.setState
- ReactClass.prototype.setState = function(newState) {
- //this._reactInternalInstance是ReactCompositeComponent的實(shí)例
- this._reactInternalInstance.receiveComponent(null, newState);
- }
第二步:調(diào)用內(nèi)部receiveComponent方法
這里主要分三種情況,文本元素,基本元素,自定義元素。
自定義元素:
receiveComponent方法源碼
- // receiveComponent方法
- ReactCompositeComponent.prototype.receiveComponent = function(nextElement, transaction, nextContext) {
- var prevElement = this._currentElement;
- var prevContext = this._context;
- this._pendingElement = null;
- this.updateComponent(
- transaction,
- prevElement,
- nextElement,
- prevContext,
- nextContext
- );
- }
updateComponent方法源碼
- // updateComponent方法
- ReactCompositeComponent.prototype.updateComponent = function(
- transaction,
- prevParentElement,
- nextParentElement,
- prevUnmaskedContext,
- nextUnmaskedContext
- ) {
- // 簡(jiǎn)寫(xiě).....
- // 不是state更新而是props更新
- if (prevParentElement !== nextParentElement) {
- willReceive = true;
- }
- if (willReceive && inst.componentWillReceiveProps) {
- // 調(diào)用生命周期componentWillReceiveProps方法
- }
- // 是否更新元素
- if (inst.shouldComponentUpdate) {
- // 如果提供shouldComponentUpdate方法
- shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
- } else {
- if (this._compositeType === CompositeTypes.PureClass) {
- // 如果是PureClass,淺層對(duì)比props和state
- shouldUpdate =
- !shallowEqual(prevProps, nextProps) ||
- !shallowEqual(inst.state, nextState);
- }
- }
- if (shouldUpdate) {
- // 更新元素
- this._performComponentUpdate(
- nextParentElement,
- nextProps,
- nextState,
- nextContext,
- transaction,
- nextUnmaskedContext
- );
- } else {
- // 不更新元素,但仍然設(shè)置props和state
- this._currentElement = nextParentElement;
- this._context = nextUnmaskedContext;
- inst.props = nextProps;
- inst.state = nextState;
- inst.context = nextContext;
- }
- // .......
- }
內(nèi)部_performComponentUpdate方法源碼
- function shouldUpdateReactComponent(prevElement, nextElement){
- var prevEmpty = prevElement === null || prevElement === false;
- var nextEmpty = nextElement === null || nextElement === false;
- if (prevEmpty || nextEmpty) {
- return prevEmpty === nextEmpty;
- }
- var prevType = typeof prevElement;
- var nextType = typeof nextElement;
- if (prevType === 'string' || prevType === 'number') {
- // 如果先前的ReactElement對(duì)象類(lèi)型是字符串或數(shù)字,新的ReactElement對(duì)象類(lèi)型也是字符串或數(shù)字,則需要更新,新的ReactElement對(duì)象類(lèi)型是對(duì)象,則不應(yīng)該更新,直接替換。
- return (nextType === 'string' || nextType === 'number');
- } else {
- // 如果先前的ReactElement對(duì)象類(lèi)型是對(duì)象,新的ReactElement對(duì)象類(lèi)型也是對(duì)象,并且標(biāo)簽類(lèi)型和key值相同,則需要更新
- return (
- nextType === 'object' &&
- prevElement.type === nextElement.type &&
- prevElement.key === nextElement.key
- );
- }
- }
文本元素:
receiveComponent方法源碼
- ReactDOMTextComponent.prototype.receiveComponent(nextText, transaction) {
- //跟以前保存的字符串比較
- if (nextText !== this._currentElement) {
- this._currentElement = nextText;
- var nextStringText = '' + nextText;
- if (nextStringText !== this._stringText) {
- this._stringText = nextStringText;
- var commentNodes = this.getHostNode();
- // 替換文本元素
- DOMChildrenOperations.replaceDelimitedText(
- commentNodes[0],
- commentNodes[1],
- nextStringText
- );
- }
- }
- }
基本元素:
receiveComponent方法源碼
- ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) {
- var prevElement = this._currentElement;
- this._currentElement = nextElement;
- this.updateComponent(transaction, prevElement, nextElement, context);
- }
updateComponent方法源碼
- ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) {
- // 略.....
- //需要單獨(dú)的更新屬性
- this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag);
- //再更新子節(jié)點(diǎn)
- this._updateDOMChildren(
- lastProps,
- nextProps,
- transaction,
- context
- );
- // ......
- }
this._updateDOMChildren方法內(nèi)部調(diào)用diff算法,請(qǐng)看下一節(jié)........
react Diff算法
diff算法源碼
- _updateChildren: function(nextNestedChildrenElements, transaction, context) {
- var prevChildren = this._renderedChildren;
- var removedNodes = {};
- var mountImages = [];
- // 獲取新的子元素?cái)?shù)組
- var nextChildren = this._reconcilerUpdateChildren(
- prevChildren,
- nextNestedChildrenElements,
- mountImages,
- removedNodes,
- transaction,
- context
- );
- if (!nextChildren && !prevChildren) {
- return;
- }
- var updates = null;
- var name;
- var nextIndex = 0;
- var lastIndex = 0;
- var nextMountIndex = 0;
- var lastPlacedNode = null;
- for (name in nextChildren) {
- if (!nextChildren.hasOwnProperty(name)) {
- continue;
- }
- var prevChild = prevChildren && prevChildren[name];
- var nextChild = nextChildren[name];
- if (prevChild === nextChild) {
- // 同一個(gè)引用,說(shuō)明是使用的同一個(gè)component,所以我們需要做移動(dòng)的操作
- // 移動(dòng)已有的子節(jié)點(diǎn)
- // NOTICE:這里根據(jù)nextIndex, lastIndex決定是否移動(dòng)
- updates = enqueue(
- updates,
- this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex)
- );
- // 更新lastIndex
- lastIndex = Math.max(prevChild._mountIndex, lastIndex);
- // 更新component的.mountIndex屬性
- prevChild._mountIndex = nextIndex;
- } else {
- if (prevChild) {
- // 更新lastIndex
- lastIndex = Math.max(prevChild._mountIndex, lastIndex);
- }
- // 添加新的子節(jié)點(diǎn)在指定的位置上
- updates = enqueue(
- updates,
- this._mountChildAtIndex(
- nextChild,
- mountImages[nextMountIndex],
- lastPlacedNode,
- nextIndex,
- transaction,
- context
- )
- );
- nextMountIndex++;
- }
- // 更新nextIndex
- nextIndex++;
- lastPlacedNode = ReactReconciler.getHostNode(nextChild);
- }
- // 移除掉不存在的舊子節(jié)點(diǎn),和舊子節(jié)點(diǎn)和新子節(jié)點(diǎn)不同的舊子節(jié)點(diǎn)
- for (name in removedNodes) {
- if (removedNodes.hasOwnProperty(name)) {
- updates = enqueue(
- updates,
- this._unmountChild(prevChildren[name], removedNodes[name])
- );
- }
- }
- }
react的優(yōu)點(diǎn)與總結(jié)
優(yōu)點(diǎn)
- 虛擬節(jié)點(diǎn)。在UI方面,不需要立刻更新視圖,而是生成虛擬DOM后統(tǒng)一渲染。
- 組件機(jī)制。各個(gè)組件獨(dú)立管理,層層嵌套,互不影響,react內(nèi)部實(shí)現(xiàn)的渲染功能。
- 差異算法。根據(jù)基本元素的key值,判斷是否遞歸更新子節(jié)點(diǎn),還是刪除舊節(jié)點(diǎn),添加新節(jié)點(diǎn)。
總結(jié)
想要更好的利用react的虛擬DOM,diff算法的優(yōu)勢(shì),我們需要正確的優(yōu)化、組織react頁(yè)面。例如將一個(gè)頁(yè)面render的ReactElement節(jié)點(diǎn)分解成多個(gè)組件。在需要優(yōu)化的組件手動(dòng)添加 shouldComponentUpdate 來(lái)避免不需要的 re-render。