平時寫寫 react,卻不瞭解內部是怎麼把 jsx 轉化為 vdom,然後渲染在介面上,以及當資料流更新時,檢視又是怎麼更新的呢。
於是我查閱了大量資料後,自己手寫了一個簡單版的 react,從中大概能瞭解到 react 基本的執行機制。
react 一個很方便之處是我們可以像寫原生 html 那樣寫元件,這就是 jsx 語法,那麼 jsx 是如何轉化為 dom 的呢。首先通過 babel 語法樹解析轉化成為 vdom,它的結構大概是
{ type: 'div', props: { id: 'container' }, children: ['xxxxx'] }
之後通過 react 內部的 render 方法將 vdom 轉為 dom 節點。
render 方法實現如下:
const _render = (vdom, parent = null) => { // custom component 經過 babel 轉義 然後 React.createElement 返回的 vdom.type 型別是 function // <p id="label">title</p> vdom = { type: 'p', props: {id: 'label'}, children: ['title']} const mount = parent ? (el => parent.appendChild(el)) : (el => el); if (typeof vdom === 'string' || typeof vdom === 'number') { return mount(document.createTextNode(vdom)); } if (typeof vdom === 'boolean' || vdom === null) { return mount(document.createTextNode('')); } if (typeof vdom === 'object' && typeof vdom.type === 'function') { return Component.render(vdom, parent); } if (typeof vdom === 'object' && typeof vdom.type === 'string') { const dom = mount(document.createElement(vdom.type)); for (const child of [].concat(...vdom.children)) _render(child, dom); for (const prop in vdom.props) { if (Object.prototype.hasOwnProperty.call(vdom.props, prop)) { setAttribute(dom, prop, vdom.props[prop]); } } return dom; } throw new Error(`Invalid VDOM: ${vdom}.`); };
值得一提的是在 ReactDOM.render() 時,首先會遍歷所有節點,然後例項化節點,呼叫 componentWillMount 方法,接著 呼叫內部的 render 將 vdom 轉化為 真實 dom,接受一個 識別符號 key 值,這在更新元件時將會派上用場。緊接著呼叫 componentDidMount 方法。
接下來講一下 react 更新 state 時發生了什麼。眾所周知,傳統的比較兩棵樹是否相同的時間複雜度是 O(n^3),而 react 基於一套比較規則將時間複雜度降到了 O(n),這大大提高了計算的時間,提高了渲染的速度。因此 react 在更新狀態時的 patch 方法都做了什麼。其實就是基於 react 的比較演算法:1. 兩棵樹的節點型別都不同時則整棵樹都替換;2.當節點的 key 值相同時則直接遞迴比較所有子節點。