工做中使用react也很长一段时间了,虽然对它的用法,原理有了必定的了解,可是总感受停留在表面。本着知其然知其因此然的态度,我试着去看了react源码,几天下来,发现并不能看懂,反而更加云里雾里了- -!。既然看不懂,那就看看社区前辈们写的一些源码分析文章以及实现思路吧,又这么过了几天,总算是摸清点思路,因而在参考了前辈们的基础上,实现了一个简易版的react。
这个系列我打算分为3节,第一节介绍下实现的思路以及结构,第二节讲渲染,第三节讲更新。javascript
众所周知,react的核心是Virtual DOM,因此,咱们的思路也是围绕着Virtual DOM展开,包含Virtual DOM模型的创建,生命周期的管理,对比差别的diff算法,将Virtual DOM转化为原生DOM并展现的patch方法等,setState异步机制以及react合成事件因为尚未研究到,暂时先忽略,事件处理跟某位前辈的思路同样,也是使用jquery事件代替,这里咱们主要以实现渲染,更新为主,相信你在看完这个系列后,能对react的运行原理有必定理解。
项目地址:https://github.com/LuSuguru/f...,如下的全部代码都是经过es6编写,切勿用在生产环境。html
React的一切都基于Virtual DOM,咱们第一步天然先实现它,以下:java
/** * @param type :表明当前的节点属性 * @param key :用来标识element,用于优化之后的更新 * @param props:节点的属性 */ function VDom(type, key, props) { this.type = type this.key = key this.props = props } // 代码地址:src/react/reactElement.js
实现了vDom后,理所须要一个方法来将咱们写的元素转化为vDom。通常咱们都是JSX来建立元素的,但它只不过是React.createElment的语法糖。因此,接下来,咱们要实现的就是createElement方法:node
function createElement(type, config, ...children) { const props = {} config = config || {} // 获取key,用来标识element,方便之后高效的更新 const { key = null } = config let propName = '' // 复制config里的内容到props for (propName in config) { if (config.hasOwnProperty(propName) && propName !== 'key') { props[propName] = config[propName] } } // 转化children if (children.length === 1 && Array.isArray(children[0])) { props.children = children[0] } else { props.children = children } return new VDom(type, key, props) } // 代码地址:src/react/reactElement.js
这段代码也很是简单,根据咱们传入的参数,生成对应的vDomreact
咱们所建立的VDom类型分为3种:jquery
不一样的类型,确定有不一样的渲染和更新逻辑,咱们把这些逻辑与vDom一块儿,封装成对应的ReactComponent类,经过ReactComponent类控制vDom,这里我把它们命名为ReactTextComponent,ReactDomComponent,ReactCompositeComponent,分别对应三种类型。
首先是基类ReactComponet:git
// component基类,用来处理不一样的虚拟dom更新,渲染 class Component { constructor(element) { this._vDom = element // 用来标识当前component this._rootNodeId = null } } // 代码地址:src/react/component/ReactComponent.js
接着再让不一样类型的component继承这个基类,每种component类型都有mount和update两个方法,用来执行渲染和更新es6
class ReactDomComponent extends ReactComponent { // 渲染 mountComponent() {} // 更新 updateComponent() {} }
class ReactCompositeComponent extends ReactComponent { // 渲染 mountComponent() {} // 更新 updateComponent() {} }
class ReactTextComponent extends ReactComponent { // 渲染 mountComponent() {} // 更新 updateComponent() {} }
实现了ReactComponent后,咱们天然须要一个入口去获得ReactComponent并调用它的mount。在使用React时,一般都是经过github
import React from 'react' import ReactDOM from 'react-dom' class App extends React.Component { } ReactDOM.render(<App />, document.getElementById('root'))
这段代码来充当渲染的入口,下面咱们来实现这个入口,(为了方便说明,我把render方法也放在了React对象中)算法
import Component from './Component' import createElement from './ReactElement' import instantiateReactComponent from './component/util' import $ from 'jquery' const React = { nextReactRootIndex: 0, // 标识id,肯定每一个vDom的惟一性 Component, // 全部自定义组件的父类 createElement, // 建立vdom render(vDom, container) { // 入口 var componentInstance = instantiateReactComponent(vDom) //经过vDom生成Component var markup = componentInstance.mountComponent(this.nextReactRootIndex++) container.innerHTML = markup $(document).trigger('mountReady') } } // 代码地址:src/react/index.js
因为渲染和更新都已经封装在不一样的ReactComponent里,因此,这里也须要一个方法,根据不一样的vDom类型生成对应的ReactComponent,下面咱们就来实现这个方法:
// component工厂,用来返回一个component实例 function instantiateReactComponent(node) { // 文本节点的状况 if (typeof node === 'string' || typeof node === 'number') { return new ReactTextComponent(node) } // 浏览器默认节点的状况 if (typeof node === 'object' && typeof node.type === 'string') { return new ReactDomComponent(node) } // 自定义的元素节点 if (typeof node === 'object' && typeof node.type === 'function') { return new ReactCompositeComponent(node) } }
而后再调用入口ReactComponent的mount方法,获取渲染内容,再将其渲染出来就行。
以上就是实现一个react的整体思路,下节咱们重点放在不一样ReactComponet的mount上。
下一节地址:https://segmentfault.com/a/11...
参考资料,感谢几位前辈的分享:
https://www.cnblogs.com/sven3...
https://github.com/purplebamb...陈屹 《深刻React技术栈》