react的火熱程度已經達到了94.5k個start,本系列文章主要用簡單的代碼來實現一個react,來了解JSX、虛擬DOM、diff算法以及state和setState的設計。javascript
提到react,固然少不了vue,vue的api設計十分簡單 上手也很是容易,但黑魔法不少,使用起來有點虛, 而react沒有過多的api,它的深度體如今設計思想上,使用react開發則讓人比較踏實、能拿捏的住,這也是我喜歡react的緣由之一。css
寫react
怎麼少的了JSX
,JSX
是什麼,讓我來看個例子
如今有下面這段代碼:html
const el = <h3 className="title">Hello Javascript</h3>
這樣的js代碼若是不通過處理會報錯,jsx是語法糖,它讓這段代碼合法化,經過babel轉化後是這樣的:vue
const el = React.createElement( 'h3', { className: 'title' }, 'Hello Javascript' )
這種例子官網首頁也有demojava
開始編碼以前,先介紹兩個東西:parcel
和babel-plugin-transform-jsx
,等會咱們用parcel搭建一個開發工程,babel-plugin-transform-jsx
是babel
的一個插件,它能夠將jsx
語法轉成React.createElement(...)
。node
下面咱們開始react
parcel這裏就不介紹了,一句話概況就是爲你生成一個零配置的開發環境。git
yarn global add parcel-bundler
或 npm install -g parcel-bundler
simple-react
simple-react
中執行 yarn init -y
或 npm init -y
生成package.json
index.html
src
文件夾 再在src
下建立index.js
而後再index.html
中引入index.js
若是你先麻煩,能夠直接下載源碼修改。github
以上步驟完可能不完整,最好參考parcel裏的內容。以上工做完成後,咱們須要安裝babel-plugin-transform-jsx
:算法
npm insatll babel-plugin-transform-jsx --save-dev 或者 yarn add babel-plugin-transform-jsx --dev
而後添加.babelrc
文件,並在該文件中加入下面這段代碼:
{ "presets": ["env"], "plugins": [["transform-jsx", { "function": "React.createElement" }]] }
上面代碼的意思是 使用transform-jsx
插件,並配置爲經過React.createElement
方法來解析JSX
,固然你也能夠不用React.createElement
和自定義方法,好比preact
使用的h
方法。
如今咱們在index.js
裏開始編碼。
首先寫入代碼:
const el = <h3 className="title">Hello Javascript</h3>; console.log(el);
咱們在什麼都不寫的狀況下,打印看看el
是什麼。
打印報錯:React沒有定義。 這是由於在.babelrc
文件中,咱們使用的這段代碼起了做用:
["transform-jsx", { "function": "React.createElement" }]
上面說過,它會經過React.createElement
方法來轉譯JSX,那麼咱們就給出這個方法:
咱們把剛纔那段代碼改變一下:
const React = { createElement: function(...args) { return args[0]; } }; const el = <h3 className="title">Hello Javascript</h3>; console.log(el);
上面代碼添加了一個React
對象,並在其中添加一個createElement
方法,如今再執行一下看看打印出什麼:
由打印結果能夠看出,jsx
在使用React.createElement
方法轉譯時,createElement
方法應該是這樣的:
createElement({ elementName, attributes, children });
如今咱們改寫一下createElement
方法,讓key的名稱簡單一點:
const React = { createElement: function({ elementName, attributes, children }) { return { tag: elementName, attrs: attributes, children }; } };
如今能夠看到打印結果是:
咱們再打印個複雜點的DOM結構:
const el = ( <div style="color:red;"> Hello <span className="title">JavaScript</span> </div> ); console.log(el);
和咱們想要的結構同樣。
其實上面打印出來的就是虛擬DOM
,如今咱們要作的就是如何把虛擬DOM
轉成真正的DOM
對象並顯示在瀏覽器上。
要想將虛擬dom轉成真實dom並渲染到頁面上,就須要調用ReactDOM.render
,好比:
ReactDOM.render(<h1>Hello World</h1>, document.getElementById('root'));
這段代碼轉換後的樣子:
ReactDOM.render( React.createElement('h1', null, 'Hello World'), document.getElementById('root') );
這時,react會將<h1>Hello World</h1>
掛載到id爲root的dom下,從而在頁面上顯示出來。
如今咱們實現render
方法:
function render(vnode, container) { const dom = createDom(vnode); //將vnode轉成真實DOM container.appendChild(dom); }
上面代碼中先調用createDom
將虛擬dom轉成真實DOM
,而後掛載到container
下。
咱們來實現createDom
方法:
function createDom(vnode) { if (vnode === undefined || vnode === null || typeof vnode === 'boolean') { vnode = ''; } if (typeof vnode === 'string' || typeof vnode === 'number') { return document.createTextNode(String(vnode)); } const dom = document.createElement(vnode.tag); //設置屬性 if (vnode.attrs) { for (let key in vnode.attrs) { const value = vnode.attrs[key]; setAttribute(dom, key, value); } } //遞歸render子節點 vnode.children.forEach(child => render(child, dom)); return dom; }
因爲屬性的種類比較多,咱們抽出一個setAttribute方法來設置屬性:
function setAttribute(dom, key, value) { //className if (key === 'className') { dom.setAttribute('class', value); //事件 } else if (/on\w+/.test(key)) { key = key.toLowerCase(); dom[key] = value || ''; //style } else if (key === 'style') { if (typeof value === 'string') { dom.style.cssText = value || ''; } else if (typeof value === 'object') { // {width:'',height:20} for (let name in value) { //若是是數字能夠忽略px dom.style[name] = typeof value[name] === 'number' ? value[name] + 'px' : value[name]; } } //其餘 } else { dom.setAttribute(key, value); } }
如今render
方法已經完整的實現了,咱們將建立ReactDOM對象,將render方法掛上去:
const ReactDOM = { render: function(vnode, container) { container.innerHTML = ''; render(vnode, container); } };
這裏在調用render
以前加了一句container.innerHTML = ''
,就不解釋了,相信你們都明白。
那麼萬事具有,咱們來測試一下,直接上一個比較複雜的dom結構並加上屬性:
const element = ( <div className="Hello" onClick={() => alert(1)} style={{ color: 'red', fontSize: 30 }} > Hello <span style={{ color: 'blue' }}>javascript!</span> </div> ); ReactDOM.render(element, document.getElementById('root'));
打開頁面,是咱們想要的結果:
再看看控制檯的dom:
很完美,這是咱們想要的東西
該demo代碼在這裏喲~~
本文敘述了JSX和虛擬DOM,以及將虛擬DOM轉成真實DOM的過程,後面的文章會繼續敘述react中的組件、生命週期、diff算法和異步setState,敬請期待~