如今网上有不少react原理解析这样的文章,可是每每这样的文章我看完事后却没有什么收获,由于行文思路太快,大部分就是写了几句话简单介绍下这段代码是用来干吗的,而后就贴上源码让你本身看,有可能做者本人是真的看懂了,可是对于大部分阅读这篇文章的人来讲,确是云里雾里。javascript
讲解一个框架的源码,最好的方式就是实现一个简易版的,这样在你实现的过程当中,读者就能了解到你总体的思路,也就能站在更高的层面上对框架有一个总体的认知,而不是陷在一些具体的技术细节上。java
这篇文章就很是棒的实现了一个简单的react框架,接下来属于对原文的翻译加上一些本身在使用过程当中的理解。node
首先先总体介绍经过这篇文章你能学到什么--咱们将实现一个简单的React,包括简单的组件级api和虚拟dom,文章也将分为如下四个部分react
元素携带者不少重要的信息,好比节点的type,props,children list,根据这些属性,能渲染出咱们须要的元素,它的树形结构以下git
{ "type": "ul", "props": { "className": "some-list" }, "children": [ { "type": "li", "props": { "className": "some-list__item" }, "children": [ "One" ] }, { "type": "li", "props": { "className": "some-list__item" }, "children": [ "Two" ] } ] } 复制代码
可是若是咱们平常写代码若是要写成这个样子,那咱们应该要疯了,因此通常咱们会写jsx的语法github
/** @jsx createElement */ const list = <ul className="some-list"> <li className="some-list__item">One</li> <li className="some-list__item">Two</li> </ul>; 复制代码
为了可以让他被编译成常规的方法,咱们须要加上注释来定义用哪一个函数,最终定义的函数被执行,最后会返回给一个虚拟DOM算法
const createElement = (type, props, ...children) => { props = props != null ? props : {}; return {type, props, children}; }; 复制代码
我为何这个地方要加注释呢,由于我在用babel打包jsx的语法的时候,貌似默认用的React里提供的CreateElement,因此当时我配置了.babelrc之后
发现它报了一个React is not defined错误,可是我安装的是做者这个简易的类React包,后来才知道在jsx前要加一段注释来告诉babel编译的时候用哪一个函数api
/** @jsx Gooact.createElement */
复制代码
这一节是将vdom渲染真实dombash
上一节咱们已经获得了根据jsx语法得出的虚拟dom树形结构,那么就该将这个虚拟dom结构渲染成真实dombabel
那么咱们在拿到一个树形结构的时候,如何判断这个节点应该渲染成真实dom的什么样子呢,这里就会有3种状况,第一种就是直接会返回一个字符串,那咱们就直接生成一个文本节点,若是返回的是一个咱们自定义的组件,那么咱们就在调用这个方法,若是是一个常规的dom组件,咱们就建立这样的一个dom元素,而后接着继续遍历它的子节点。
setAttribute就是将咱们设置在虚拟dom上的属性设置在真实dom上
const render = (vdom, parent=null) => { if (parent) parent.textContent = ''; const mount = parent ? (el => parent.appendChild(el)) : (el => el); if (typeof vdom == 'string' || typeof vdom == 'number') { return mount(document.createTextNode(vdom)); } else if (typeof vdom == 'boolean' || vdom === null) { return mount(document.createTextNode('')); } else if (typeof vdom == 'object' && typeof vdom.type == 'function') { return mount(Component.render(vdom)); } else if (typeof vdom == 'object' && typeof vdom.type == 'string') { const dom = document.createElement(vdom.type); for (const child of [].concat(...vdom.children)) // flatten dom.appendChild(render(child)); for (const prop in vdom.props) setAttribute(dom, prop, vdom.props[prop]); return mount(dom); } else { throw new Error(`Invalid VDOM: ${vdom}.`); } }; const setAttribute = (dom, key, value) => { if (typeof value == 'function' && key.startsWith('on')) { const eventType = key.slice(2).toLowerCase(); dom.__gooactHandlers = dom.__gooactHandlers || {}; dom.removeEventListener(eventType, dom.__gooactHandlers[eventType]); dom.__gooactHandlers[eventType] = value; dom.addEventListener(eventType, dom.__gooactHandlers[eventType]); } else if (key == 'checked' || key == 'value' || key == 'id') { dom[key] = value; } else if (key == 'key') { dom.__gooactKey = value; } else if (typeof value != 'object' && typeof value != 'function') { dom.setAttribute(key, value); } }; 复制代码
想象一个你有一个很深的结构,并且你还须要频繁的更新你的虚拟dom,若是你改变了一些,那么所有都要渲染,这无疑会消耗不少时间。
可是若是咱们有一个算法可以比较出新的虚拟dom和已有dom的差别,而后只更新那些改变的地方,这个地方就是常常说的React团队作了一些通过实践后的约定,将原本o(n)^3的时间复杂度下降到了o(n),主要就是下面两种主要的约定
const patch = (dom, vdom, parent=dom.parentNode) => { const replace = parent ? el => (parent.replaceChild(el, dom) && el) : (el => el); if (typeof vdom == 'object' && typeof vdom.type == 'function') { return Component.patch(dom, vdom, parent); } else if (typeof vdom != 'object' && dom instanceof Text) { return dom.textContent != vdom ? replace(render(vdom)) : dom; } else if (typeof vdom == 'object' && dom instanceof Text) { return replace(render(vdom)); } else if (typeof vdom == 'object' && dom.nodeName != vdom.type.toUpperCase()) { return replace(render(vdom)); } else if (typeof vdom == 'object' && dom.nodeName == vdom.type.toUpperCase()) { const pool = {}; const active = document.activeElement; for (const index in Array.from(dom.childNodes)) { const child = dom.childNodes[index]; const key = child.__gooactKey || index; pool[key] = child; } const vchildren = [].concat(...vdom.children); // flatten for (const index in vchildren) { const child = vchildren[index]; const key = child.props && child.props.key || index; dom.appendChild(pool[key] ? patch(pool[key], child) : render(child)); delete pool[key]; } for (const key in pool) { if (pool[key].__gooactInstance) pool[key].__gooactInstance.componentWillUnmount(); pool[key].remove(); } for (const attr of dom.attributes) dom.removeAttribute(attr.name); for (const prop in vdom.props) setAttribute(dom, prop, vdom.props[prop]); active.focus(); return dom; } }; 复制代码
组件是最像js中函数的概念了,咱们经过它可以展现出什么应该展现在屏幕上,它能够被定义成一个无状态的函数,或者是一个有生命周期的组件。
复制代码
class Component { constructor(props) { this.props = props || {}; this.state = null; } static render(vdom, parent=null) { const props = Object.assign({}, vdom.props, {children: vdom.children}); if (Component.isPrototypeOf(vdom.type)) { const instance = new (vdom.type)(props); instance.componentWillMount(); instance.base = render(instance.render(), parent); instance.base.__gooactInstance = instance; instance.base.__gooactKey = vdom.props.key; instance.componentDidMount(); return instance.base; } else { return render(vdom.type(props), parent); } } static patch(dom, vdom, parent=dom.parentNode) { const props = Object.assign({}, vdom.props, {children: vdom.children}); if (dom.__gooactInstance && dom.__gooactInstance.constructor == vdom.type) { dom.__gooactInstance.componentWillReceiveProps(props); dom.__gooactInstance.props = props; return patch(dom, dom.__gooactInstance.render()); } else if (Component.isPrototypeOf(vdom.type)) { const ndom = Component.render(vdom); return parent ? (parent.replaceChild(ndom, dom) && ndom) : (ndom); } else if (!Component.isPrototypeOf(vdom.type)) { return patch(dom, vdom.type(props)); } } setState(nextState) { if (this.base && this.shouldComponentUpdate(this.props, nextState)) { const prevState = this.state; this.componentWillUpdate(this.props, nextState); this.state = nextState; patch(this.base, this.render()); this.componentDidUpdate(this.props, prevState); } else { this.state = nextState; } } shouldComponentUpdate(nextProps, nextState) { return nextProps != this.props || nextState != this.state; } componentWillReceiveProps(nextProps) { return undefined; } componentWillUpdate(nextProps, nextState) { return undefined; } componentDidUpdate(prevProps, prevState) { return undefined; } componentWillMount() { return undefined; } componentDidMount() { return undefined; } componentWillUnmount() { return undefined; } } 复制代码
本次文章中新开发的gooact轮子就结束了,让咱们看看他有什么功能