头条资讯网_今日热点_娱乐才是你关心的时事

今日热点 时事资讯
娱乐头条才是你关心的新闻
首页 > 头条资讯 > 科技

React源码解析

作者|Video++极链科技前端Team超凡

整理|包包

前言

React起源于Facebook的内部项目,是一个用于构建用户界面的Javascript库。其拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。

本文希望通过参考React源码,依葫芦画瓢地完成React的雏形。来帮助理解其内部的实现原理,知其然更要知其所以然。

虚拟DOM(VirtualDOM)

了解React的都知道,其高效的原因,是因为React按照页面的DOM结构,利用Javascript在内存中构建了一套相同结构的虚拟内存树模型,这个内存模型就称为VirtualDOM。每当页面产生了变化,React的diff算法会先在内存模型中进行比对,提取出差异点,在将VirtualDOM转化为原生DOM输出时,按照差异点,只patch出有变动的部分。

下面是VirtualDOM节点的定义:

入口

一切都是从React.render(,document.body)开始的,所以先来看看React是怎么定义的?

React中主要包括:

•render(virtualDom,container)命令式调用,一般用于应用入口,将虚拟DOM渲染在container容器中;

•createElement(name,props,children)创建组件时使用,JSX是其语法糖;

•Component以ES6中的类式语法声明时使用。

createElement(type,props,children)

createElement()的主要作用是根据给定type创建VirtualDOM节点,JSX是它的语法糖形式;其type参数可以是原生的html标签名(如:div、tag等),也可以是React组件类或函数。

组件的实现

React的所有组件,按照类型可以分为三种:

•文本展示类型(TextComponent)

•原生DOM类型(DomComponent)

•自定义类型(CompositeComponent)

每种类型的组件,都需要处理初始化和更新两种逻辑,具体会在下面两个函数中实现:

•mountComponent(rootNodeId)用于处理初始化逻辑

•updateComponent()用于处理更新逻辑

初始化mountComponent()的实现

mountComponent()的实现思路是,根据virtualDom对象生成HTML代码并返回。

首先定义类型组件的基类Component,它只是简单地记录了传入的virtualDom对象,并初始化了组件节点ID。

下面是不同类型组件初始化渲染逻辑的各自实现。

•TextComponent

作为纯展示类型组件,TextComponent只是简单地将需要展示的内容,使用标签包装并返回就可以了。

•DomComponent

DomComponent类型在处理原生DOM时,需要额外注意一下原生事件部分的处理。

•CompositeComponent

在实现CompositeComponent类型的初始化渲染逻辑之前,先看一下React组件的定义语法。

声明语法中,App继承自React.Component,所以我们先来实现Component这个类。

这里的React.Component不要与上面的Component混淆,Component是不同组件类型的基类,抽象了组件渲染与更新;而React.Component则是Composite这种类型组件声明时的基类。

在React.Component中,简单地声明了控制数据流向的props属性,以及组件实例内部用于触发更新的setState()函数。

在了解了React.Component的定义之后,我们回到CompositeComponent,开始实现mountComponent()的逻辑。

首先要了解的是,在composite类型组件中,vDom对象中的type,指向的是组件类的定义,因此mountComponent()函数要做的工作,就是使用vDom的props属性来创建一个type的实例。

思考一下,在JSX语法中,解析器碰到标签后,就会去查找到MyInput的定义,上面说过JSX只是createElement的语法糖,因此背后调用的是React.createElement(MyInput)。在React规范中,可以使用类或函数来声明组件,因此在mountComponent()中使用newtype(),就可以构造出MyInput的实例了。

更新流程updateComponent()的实现

实现完组件的初始化之后,接下来要实现组件的更新逻辑。

React开放了setState()用于组件更新,回顾上面React.Component中setState()的定义,实际调用的是this._reactInternalInstance.updateComponent(null,newState)这个函数。而this._reactInternalInstance指向CompositeComponent,困此更新逻辑交回CompositeComponent.updateComponent()来完成。

•CompositeComponent

Composite类型组件的更新函数,需要处理两种流程:

当被定义在其它组件的render函数中时,其包裹组件会构建出新的vDom对象,根据传入新的vDom来处理更新;

当组件内部使用setState()触发时,根据新的state来更新;

了解这两种方式的区别,可以帮助我们理解下面updateComponent函数的实现。

我们梳理一下更新流程:

组件在初始化时,记录下了render组件的实例,即this._renderedComponent;

在更新环节,重新render()得到新的VDomnextRenderVDom;

通过比对前后两个VDom的type和key,来判断是执行原来_renderedComponent的updateComponent函数,还是重新生成新的组件;

上面使用到了shouldUpdateReactComponent这个比对函数,来对vDom的type和key进行比对,其实现如下:

上面这个处理逻辑,就是diff算法的第一个规则:当两个VDom节点的类型不一致时,重新构建该组件的VirtualDOM树结构。

•TextComponentText类型组件作为颗粒度最小的组件,更新逻辑非常简单,展示新的文本内容即可。

•DomComponent

因为diff算法的介入,Dom类型的处理逻辑相对复杂。可以分两步来处理,第一步更新组件输出的容器DOM上面的属性;第二步处理子级DOM。

_updateProperties()函数对比新旧props,完成属性及事件的处理。特别注意一下事件处理部分,需要注销掉原来DOM上注册的事件。

_updateDOMChildren()用于处理children部分的更新,这部分的逻辑相对复杂,也是diff算法的优化点所在。

注:下面的说明中,以名称中含/'children/'来标识集合,/'child/'指代集合项。

i.使用nextChildrenVDoms数据生成新的nextChildrenComponent;

•DomComponent在初始化流程中,_mountComponent()函数会将组件集合保存下来,存入实例的_renderedChildrenComponent属性中,通过遍历该属性,可以取得childComponent实例上的_vDom;

•使用vDom来生成标识索引key,并以childComponent作为索引值,生成childrenComponent的Map结构;(对于Compotite类型,使用vDom.key作为标识索引key;对于Text和Dom类型,使用childComponent在childrenComponent中所处的索引位置作为标识索引key);

•使用nextChildrenVDoms生成新nextChildrenComponent的Map结构;在遍历vDom集合的过程中,会使用上面的标识索引key生成规则,来进行判定,看是复用之前的组件实例触发更新,还是创建一个新的组件;

ii.经过上面一步得到Map结构的prevChildren和nextChildren之后,会使用深度遍历算法,递归地比对树结构中,相同层级和位置的两个组件,将差异点保存为特定的diff标识结构,存入diffQueue队列中;

iii.遍历diffQueue,按照差异的类型,完成最终HTMLDOM的变动;

首先是_updateDOMChildren()里的的定义。由于在递归组件树的节点时,存在多次触发_updateDOMChildren()的情况;因此使用_updateDepth变量,在比对操作前+1,完成后-1,来判定整个树的更新是否全部完成,继而调用_patch()完成HTMLDOM的更新;

下面的_diff()中,实现了更新步骤中的1和2。

值得注意的是_diff过程中lastIndex变量的作用,其记录在遍历过程中,每次访问到的prevChildrenComponent中位置最靠后的组件,这是组件更新的一种排序上面的优化策略,可以参见这一篇文章当中的详细介绍:不可思议的reactdiff。

在计算出diffQueue的差异队列后,在_patch()函数中完成最终HTMLDOM的更新:

总结

至此,我们实现了一个简易版本的React框架,完成了组件类的定义、初始化及更新;并且梳理了核心diff算法。

下面简单做一下总结:

•组件分为3种类型来处理组件的初始化渲染和更新:TextComponent、DomComponent和CompositeComponent;

•virtualDom对象中,记录了组件类型type,唯一标识key和属性集合props;

•组件是由virtualDom创建而来,vDom上的type和key用来标识组件实例的唯一性;

•diff算法的核心,是对比新旧vDom对象,来完成部分组件实例的复用,并加入了排序优化策略。通过javascript大量计算的代价,来换取减少页面DOM重排的消耗,从而提高了渲染性能;

未经允许不得转载:头条资讯网_今日热点_娱乐才是你关心的时事 » React源码解析

分享到:更多 ()
来源:喜传播 编辑:科技

评论

留言/评论 共有条点评
昵称:
验证码:
匿名发表