当前位置: 首页 > 科技观察

深入理解react(源码分析)

时间:2023-03-17 13:17:00 科技观察

理解ReactElement和ReactClass的概念我们先来理解两个概念:ReactElement是描述DOM节点或组件实例的文字对象。它包含一些信息,包括组件类型类型和属性道具。就像描述DOM节点的元素(虚拟节点)。它们可以通过React.createElement方法或者jsx写法创建,分为DOMElement和ComponentElements两种:DOMElements当节点的type属性为字符串时,表示普通节点,如div,span{type:'button',props:{className:'buttonbutton-blue',children:{type:'b',props:{children:'OK!'}}}}组件元素当节点的type属性为a时函数或者一个类,它代表一个自定义的节点:{type:'b',props:{children:children}}}};}}//ComponentElements{type:Button,props:{color:'blue',children:'OK!'}}ReactClassReactClass是Component我们平时写的component(类或者函数),比如上面的Button类。ReactClass实例化后,调用render方法返回DOMElement。React渲染过程理解://element是ComponentElementsReactDOM.render({type:Form,props:{isSubmitted:false,buttonText:'OK!'}},document.getElementById('root'));调用React.render方法,将我们的元素根虚拟节点渲染到容器元素中。element可以是字符串文本元素,也可以是上面介绍的ReactElement(分为DOMElements,ComponentElements)。根据元素类型的不同,分别实例化ReactDOMTextComponent、ReactDOMComponent、ReactCompositeComponent类。这些类用于管理ReactElement,负责将不同的ReactElement转化为DOM(mountComponent方法),负责更新DOM(receiveComponent方法,updateComponent方法,下面会介绍)等。ReactCompositeComponent实例调用mountComponent方法后,在内部调用render方法并返回DOM元素。然后递归到步骤2?如图。React更新机制中的每一类元素都必须处理自己的更新:自定义元素的更新主要是更新render产生的节点,交给render产生的节点对应的组件来管理更新。文本节点的更新很简单,直接更新副本即可。浏览器基础元素的更新分为两部分:首先是更新属性,比较前后属性的差异,在本地进行更新。并处理特殊属性,例如事件绑定。然后是子节点的更新。子节点的更新主要是找出差异对象。在查找差异对象时,也会使用上面的shouldUpdateReactComponent进行判断。如果可以直接更新,则递归调用子节点的更新,也是递归的。查找差异对象。不能直接更新删除以前的对象或添加新对象。然后根据不同对象对dom元素进行操作(位置改变、删除、添加等)。第一步:调用this.setStateReactClass.prototype.setState=function(newState){//this._reactInternalInstance是ReactCompositeComponent的一个实例this._reactInternalInstance.receiveComponent(null,newState);}第二步:调用内部的receiveComponent方法这里是main分为三种情况,文本元素,基础元素,自定义元素。自定义元素:receiveComponent方法源码//receiveComponent方法ReactCompositeComponent.prototype.receiveComponent=function(nextElement,transaction,nextContext){varprevElement=this._currentElement;varprevContext=this._context;this._pendingElement=null;this.updateComponent(transaction,prevElement,nextElement,prevContext,nextContext);}updateComponent方法源码//updateComponent方法ReactCompositeComponent.prototype.updateComponent=function(transaction,prevParentElement,nextParentElement,prevUnmaskedContext,nextUnmaskedContext){//简写.....//不是状态更新是props更新if(prevParentElement!==nextParentElement){willReceive=true;}if(willReceive&&inst.componentWillReceiveProps){//调用生命周期componentWillReceiveProps方法}//是否更新elementif(inst.shouldComponentUpdate){//If提供了shouldComponentUpdate方法shouldUpdate=inst.shouldComponentUpdate(nextProps,nextState,nextContext);}else{if(this._compositeType===CompositeTypes.PureClass){//如果是PureClass,props和states的浅比较shouldUpdate=!shallowEqual(prevProps,nextProps)||!shallowEqual(inst.state,nextState);}}if(shouldUpdate){//更新元素this._performComponentUpdate(nextParentElement,nextProps,nextState,nextContext,transaction,nextUnmaskedContext);}else{//不更新元素,但是还是Setpropsandstatethis._currentElement=nextParentElement;this._context=nextUnmaskedContext;inst.props=nextProps;inst.state=nextState;inst.context=nextContext;}//...}内部_performComponentUpdate方法源码函数shouldUpdateReactComponent(prevElement,nextElement){varprevEmpty=prevElement===null||prevElement===false;varnextEmpty=nextElement===null||nextElement===false;if(prevEmpty||nextEmpty){returnprevEmpty===nextEmpty;}varprevType=typeofprevElement;varnextType=typeofnextElement;if(prevType==='string'||prevType==='number'){//如果之前的ReactElement对象类型是字符串或者数字,那么新的ReactElement对象类型是同样是字符串或者数字,需要更新,新的ReactElement对象类型是对象,不应该更新,直接替换return(nextType==='string'||nextType==='number');}else{//如果之前的ReactElement对象类型是对象,那么新的ReactElement对象类型也是对象,标签类型和键值相同,需要更新return(nextType==='object'&&prevElement.type===nextElement.type&&prevElement.key===nextElement.key);}}文本元素:receiveComponent方法源代码ReactDOMTextComponent.prototype.receiveComponent(nextText,transaction){//与之前保存的字符串进行比较{this._stringText=nextStringText;varcommentNodes=this.getHostNode();//替换文本元素DOMChildrenOperations.replaceDelimitedText(commentNodes[0],commentNodes[1],nextStringText);}}}基础元素:receiveComponent方法源码ReactDOMComponent.prototype.receiveComponent=function(nextElement,transaction,context){varprevElement=this._currentElement;this._currentElement=nextElement;this.updateComponent(transaction,prevElement,nextElement,context);}updateComponent方法源码ReactDOMComponent.prototype.updateComponent=function(transaction,prevElement,nextElement,context){//略.....//需要单独更新属性this._updateDOMProperties(lastProps,nextProps,transaction,isCustomComponentTag);//更新子节点this._updateDOMChildren(lastProps,nextProps,transaction,context);//...}this._updateDOMChildren方法内部调用了diff算法,请看下一节.......reactDiff算法diff算法源码_updateChildren:function(nextNestedChildrenElements,transaction,context){varprevChildren=this._renderedChildren;varremovedNodes={};varmountImages=[];//获取新的子元素数组varnextChildren=this._reconcilerUpdateChildren(prevChildren,nextNestedChildrenElements,mountImages,removedtransactNodes,);ildren&next!prevChildren){return;}varupdates=null;varname;varnextIndex=0;varlastIndex=0;varnextMountIndex=0;varlastPlacedNode=null;for(nameinnextChildren){if(!nextChildren.hasOwnProperty(name)){continue;}varprevChild=prevChildren&&prevChildren[名字];varnextChild=nextChildren[name];if(prevChild===nextChild){//相同的引用,说明使用了相同的组件,所以我们需要做移动操作//移动已有的子节点//注意:这里根据nextIndex,lastIndex决定是否移动updates=enqueue(updates,this.moveChild(prevChild,lastPlacedNode,nextIndex,lastIndex));//updatelastIndexlastIndex=Math.max(prevChild._mountIndex,lastIndex);//更新组件的.mountIndex属性prevChild._mountIndex=nextIndex;}else{if(prevChild){//更新lastIndexlastIndex=Math.max(prevChild._mountIndex,lastIndex);}//在指定位置添加新的子节点updates=enqueue(updates,this._mountChildAtIndex(nextChild,mountImages[nextMountIndex],lastPlacedNode,nextIndex,transaction,context));nextMountIndex++;}//更新nextIndexnextIndex++;lastPlacedNode=ReactReconciler.getHostNode(nextChild);}//移除不存在的旧子节点、与旧子节点不同的旧子节点和新子节点for(nameinremovedNodes){if(removedNodes.hasOwnProperty(name)){updates=enqueue(updates,this._unmountChild(prevChildren[name],removedNodes[name]));}}}React虚拟节点的优势和总结在UI方面,不需要立即更新视图,而是生成虚拟DOM后统一渲染。组件机制。每个组件独立管理,层层嵌套,相互独立,渲染功能在react内部实现。差异算法。根据基本元素的key值,判断是递归更新子节点,还是删除旧节点,增加新节点。综上所述,为了更好地利用react的虚拟DOM和diff算法的优势,我们需要对react页面进行适当的优化和组织。例如,将页面渲染的ReactElement节点分解为多个组件。为需要优化的组件手动添加shouldComponentUpdate以避免不必要的重新渲染。