平時寫寫 react,卻不了解內部是怎么把 jsx 轉化為 vdom,然后渲染在界面上,以及當數據流更新時,視圖又是怎么更新的呢。
於是我查閱了大量資料后,自己手寫了一個簡單版的 react,從中大概能了解到 react 基本的運行機制。
react 一個很方便之處是我們可以像寫原生 html 那樣寫組件,這就是 jsx 語法,那么 jsx 是如何轉化為 dom 的呢。首先通過 babel 語法樹解析轉化成為 vdom,它的結構大概是
/* <div id="container">xxxxxx</div> */
{ 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 值相同時則直接遞歸比較所有子節點。