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