如今網上有不少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輪子就結束了,讓咱們看看他有什麼功能