-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
reactjs源码分析-上篇(首次渲染实现原理) #2
Comments
666 |
好犀利 |
|
很厉害 |
six six six |
初学者感觉获益匪浅!!对照着源码看真的是把React的核心都讲得很清晰了 |
双击666 看了这个清晰了好多 直接看源码头都大了~ |
Closed
|
通透 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
reactjs源码分析-上篇(首次渲染实现原理)
reactjs是目前比较火的前端框架,但是目前并没有很好的解释原理的项目。reactjs源码比较复杂不适合初学者去学习。所以本文通过实现一套简易版的reactjs,使得理解原理更加容易。包括:
声明:
所有实例源码都托管在github。点这里里面有分步骤的例子,可以一边看一边运行例子。
前言
前端的发展特别快,经历过jQuery一统天下的工具库时代后,现在各种框架又开始百家争鸣了。angular,ember,backbone,vue,avalon,ploymer还有reactjs,作为一个前端真是稍不留神就感觉要被淘汰了,就在去年大家还都是angularjs的粉丝,到了今年又开始各种狂追reactjs了。前端都是喜新厌旧的,不知道最后这些框架由谁来一统天下,用句很俗的话说,这是最好的时代也是最坏的时代。作为一个前端,只能多学点,尽量多的了解他们的原理。
reactjs的代码非常绕,对于没有后台开发经验的前端来说看起来会比较吃力。其实reactjs的核心内容并不多,主要是下面这些:
下面我们将一点点的来实现一个简易版的reactjs,实现上面的那些功能,最后用这个reactjs做一个todolist的小应用,看完这个,或者跟着敲一遍代码。希望让大家能够更好的理解reactjs的运行原理。
先从最简单的开始
我们先从渲染hello world开始吧。
我们看下面的代码:
假定这一行代码,就可以把
hello world
渲染到对应的div里面。我们来看看我们需要为此做些什么:
代码分为三个部分:
文本类型
的节点,在渲染,更新,删除时应该做什么操作,这边暂时只用到渲染,另外两个可以先忽略nextReactRootIndex作为每个component的标识id,不断加1,确保唯一性。这样我们以后可以通过这个标识找到这个元素。
可以看到我们把逻辑分为几个部分,主要的渲染逻辑放在了具体的componet类去定义。React.render负责调度整个流程,这里是调用instantiateReactComponent生成一个对应component类型的实例对象,然后调用此对象的mountComponent获取生成的内容。最后写到对应的container节点中。
可能有人问,这么p大点功能,有必要这么复杂嘛,别急。往下看才能体会这种分层的好处。
引入基本elemetnt
我们知道reactjs最大的卖点就是它的虚拟dom概念,我们一般使用
React.createElement
来创建一个虚拟dom元素。虚拟dom元素分为两种,一种是浏览器自带的基本元素比如 div p input form 这种,一种是自定义的元素。
这节我们先讨论浏览器的基本元素。
在reactjs里,当我们希望在hello world外面包一层div,并且带上一些属性,甚至事件时我们可以这么写:
上面使用
React.createElement
创建了一个基本元素,我们来看看简易版本React.createElement
的实现:createElement只是做了简单的参数修正,最终返回一个ReactElement实例对象也就是我们说的虚拟元素的实例。
好了有了元素实例,我们得把他渲染出来,此时render接受的是一个ReactElement而不是文本,我们先改造下instantiateReactComponent:
我们增加了一个判断,这样当render的不是文本而是浏览器的基本元素时。我们使用另外一种component来处理它渲染时应该返回的内容。这里就体现了工厂方法instantiateReactComponent的好处了,不管来了什么类型的node,都可以负责生产出一个负责渲染的component实例。这样render完全不需要做任何修改,只需要再做一种对应的component类型(这里是ReactDOMComponent)就行了。
所以重点我们来看看
ReactDOMComponent
的具体实现:我们增加了虚拟dom reactElement的定义,增加了一个新的componet类ReactDOMComponent。
这样我们就实现了渲染浏览器基本元素的功能了。
对于虚拟dom的渲染逻辑,本质上还是个递归渲染的东西,reactElement会递归渲染自己的子节点。可以看到我们通过instantiateReactComponent屏蔽了子节点的差异,只需要使用不同的componet类,这样都能保证通过mountComponent最终拿到渲染后的内容。
另外这边的事件也要说下,可以在传递props的时候传入{onClick:function(){}}这样的参数,这样就会在当前元素上添加事件,代理到document。由于reactjs本身全是在写js,所以监听的函数的传递变得特别简单。
自定义元素
上面实现了基本的元素内容,我们下面实现自定义元素的功能。
随着前端技术的发展浏览器的那些基本元素已经满足不了我们的需求了,如果你对webcomponents有一定的了解,就会知道人们一直在尝试扩展一些自己的标记。
reactjs通过虚拟dom做到了类似的功能,还记得我们上面element.type只是个简单的字符串,如果是个类呢?如果这个类恰好还有自己的生命周期管理,那扩展性就很高了。
我们看下reactjs怎么使用自定义元素:
React.createElement
接受的不再是字符串,而是一个class。React.createClass
生成一个自定义标记类,带有基本的生命周期:对reactjs稍微有点了解的应该都可以明白上面的用法。
我们先来看看React.createClass的实现:
可以看到createClass生成了一个继承ReactClass的子类,在构造函数里调用this.getInitialState获得最初的state。
我们这里只是返回了一个继承类的定义,那么具体的componentWillmount,这些生命周期函数在哪里调用呢。
看看我们上面的两种类型就知道,我们是时候为自定义元素也提供一个componet类了,在那个类里我们会实例化ReactClass,并且管理生命周期,还有父子组件依赖。
好,我们老规矩先改造instantiateReactComponent
很简单我们增加了一个判断,使用新的component类形来处理自定义的节点。我们看下
ReactCompositeComponent的具体实现:
实现并不难,ReactClass的render一定是返回一个虚拟节点(包括element和text),这个时候我们使用instantiateReactComponent去得到实例,再使用mountComponent拿到结果作为当前自定义元素的结果。
应该说本身自定义元素不负责具体的内容,他更多的是负责生命周期。具体的内容是由它的render方法返回的虚拟节点来负责渲染的。
本质上也是递归的去渲染内容的过程。同时因为这种递归的特性,父组件的componentWillMount一定在某个子组件的componentWillMount之前调用,而父组件的componentDidMount肯定在子组件之后,因为监听mountReady事件,肯定是子组件先监听的。
上面实现了三种类型的元素,其实我们发现本质上没有太大的区别,都是有自己对应component类来处理自己的渲染过程。
大概的关系是下面这样。
于是我们发现初始化的渲染流程都已经完成了。
结语
整个初次渲染的流程基本就分析完毕了。看看我们目前的进展,事件监听做了,虚拟dom有了。基本的组件生命周期也有了。我们这个小玩具已经可以简单跑跑了。下篇文章我们将一起去实现reactjs的更新机制,看看它最核心的diff算法是怎么回事。
The text was updated successfully, but these errors were encountered: