摘要:本系列文章在實現(xiàn)一個的同時理順框架的主干內(nèi)容虛擬組件生命周期算法從到實現(xiàn)系列和從到實現(xiàn)系列組件和生命周期先來回顧的生命周期,用流程圖表示如下該流程圖比較清晰地呈現(xiàn)了的生命周期。它們的目的都是降低空間復(fù)雜度。
本系列文章在實現(xiàn)一個 (x)react 的同時理順 React 框架的主干內(nèi)容(JSX/虛擬DOM/組件/生命周期/diff算法/...)
從 0 到 1 實現(xiàn) React 系列 —— JSX 和 Virtual DOM
從 0 到 1 實現(xiàn) React 系列 —— 組件和 state|props
生命周期先來回顧 React 的生命周期,用流程圖表示如下:
該流程圖比較清晰地呈現(xiàn)了 react 的生命周期。其分為 3 個階段 —— 生成期,存在期,銷毀期。
因為生命周期鉤子函數(shù)存在于自定義組件中,將之前 _render 函數(shù)作些調(diào)整如下:
// 原來的 _render 函數(shù),為了將職責(zé)拆分得更細(xì),將 virtual dom 轉(zhuǎn)為 real dom 的函數(shù)多帶帶抽離出來 function vdomToDom(vdom) { if (_.isFunction(vdom.nodeName)) { // 為了更加方便地書寫生命周期邏輯,將解析自定義組件邏輯和一般 html 標(biāo)簽的邏輯分離開 const component = createComponent(vdom) // 構(gòu)造組件 setProps(component) // 更改組件 props renderComponent(component) // 渲染組件,將 dom 節(jié)點賦值到 component return component.base // 返回真實 dom } ... }
我們可以在 setProps 函數(shù)內(nèi)(渲染前)加入 componentWillMount,componentWillReceiveProps 方法,setProps 函數(shù)如下:
function setProps(component) { if (component && component.componentWillMount) { component.componentWillMount() } else if (component.base && component.componentWillReceiveProps) { component.componentWillReceiveProps(component.props) // 后面待實現(xiàn) } }
而后我們在 renderComponent 函數(shù)內(nèi)加入 componentDidMount、shouldComponentUpdate、componentWillUpdate、componentDidUpdate 方法
function renderComponent(component) { if (component.base && component.shouldComponentUpdate) { const bool = component.shouldComponentUpdate(component.props, component.state) if (!bool && bool !== undefined) { return false // shouldComponentUpdate() 返回 false,則生命周期終止 } } if (component.base && component.componentWillUpdate) { component.componentWillUpdate() } const rendered = component.render() const base = vdomToDom(rendered) if (component.base && component.componentDidUpdate) { component.componentDidUpdate() } else if (component && component.componentDidMount) { component.componentDidMount() } if (component.base && component.base.parentNode) { // setState 進入此邏輯 component.base.parentNode.replaceChild(base, component.base) } component.base = base // 標(biāo)志符 }測試生命周期
測試如下用例:
class A extends Component { componentWillReceiveProps(props) { console.log("componentWillReceiveProps") } render() { return ({this.props.count}) } } class B extends Component { constructor(props) { super(props) this.state = { count: 1 } } componentWillMount() { console.log("componentWillMount") } componentDidMount() { console.log("componentDidMount") } shouldComponentUpdate(nextProps, nextState) { console.log("shouldComponentUpdate", nextProps, nextState) return true } componentWillUpdate() { console.log("componentWillUpdate") } componentDidUpdate() { console.log("componentDidUpdate") } click() { this.setState({ count: ++this.state.count }) } render() { console.log("render") return ( ) } } ReactDOM.render( , document.getElementById("root") )
頁面加載時輸出結(jié)果如下:
componentWillMount render componentDidMount
點擊按鈕時輸出結(jié)果如下:
shouldComponentUpdate componentWillUpdate render componentDidUpdatediff 的實現(xiàn)
在 react 中,diff 實現(xiàn)的思路是將新老 virtual dom 進行比較,將比較后的 patch(補?。╀秩镜巾撁嫔?,從而實現(xiàn)局部刷新;本文借鑒了 preact 和 simple-react 中的 diff 實現(xiàn),總體思路是將舊的 dom 節(jié)點和新的 virtual dom 節(jié)點進行了比較,根據(jù)不同的比較類型(文本節(jié)點、非文本節(jié)點、自定義組件)調(diào)用相應(yīng)的邏輯,從而實現(xiàn)頁面的局部渲染。代碼總體結(jié)構(gòu)如下:
/** * 比較舊的 dom 節(jié)點和新的 virtual dom 節(jié)點: * @param {*} oldDom 舊的 dom 節(jié)點 * @param {*} newVdom 新的 virtual dom 節(jié)點 */ function diff(oldDom, newVdom) { ... if (_.isString(newVdom)) { return diffTextDom(oldDom, newVdom) // 對比文本 dom 節(jié)點 } if (oldDom.nodeName.toLowerCase() !== newVdom.nodeName) { diffNotTextDom(oldDom, newVdom) // 對比非文本 dom 節(jié)點 } if (_.isFunction(newVdom.nodeName)) { return diffComponent(oldDom, newVdom) // 對比自定義組件 } diffAttribute(oldDom, newVdom) // 對比屬性 if (newVdom.children.length > 0) { diffChild(oldDom, newVdom) // 遍歷對比子節(jié)點 } return oldDom }
下面根據(jù)不同比較類型實現(xiàn)相應(yīng)邏輯。
對比文本節(jié)點首先進行較為簡單的文本節(jié)點的比較,代碼如下:
// 對比文本節(jié)點 function diffTextDom(oldDom, newVdom) { let dom = oldDom if (oldDom && oldDom.nodeType === 3) { // 如果老節(jié)點是文本節(jié)點 if (oldDom.textContent !== newVdom) { // 這里一個細(xì)節(jié):textContent/innerHTML/innerText 的區(qū)別 oldDom.textContent = newVdom } } else { // 如果舊 dom 元素不為文本節(jié)點 dom = document.createTextNode(newVdom) if (oldDom && oldDom.parentNode) { oldDom.parentNode.replaceChild(dom, oldDom) } } return dom }對比非文本節(jié)點
對比非文本節(jié)點,其思路為將同層級的舊節(jié)點替換為新節(jié)點,代碼如下:
// 對比非文本節(jié)點 function diffNotTextDom(oldDom, newVdom) { const newDom = document.createElement(newVdom.nodeName); [...oldDom.childNodes].map(newDom.appendChild) // 將舊節(jié)點下的元素添加到新節(jié)點下 if (oldDom && oldDom.parentNode) { oldDom.parentNode.replaceChild(oldDom, newDom) } }對比自定義組件
對比自定義組件的思路為:如果新老組件不同,則直接將新組件替換老組件;如果新老組件相同,則將新組件的 props 賦到老組件上,然后再對獲得新 props 前后的老組件做 diff 比較。代碼如下:
// 對比自定義組件 function diffComponent(oldDom, newVdom) { if (oldDom._component && (oldDom._component.constructor !== newVdom.nodeName)) { // 如果新老組件不同,則直接將新組件替換老組件 const newDom = vdomToDom(newVdom) oldDom._component.parentNode.insertBefore(newDom, oldDom._component) oldDom._component.parentNode.removeChild(oldDom._component) } else { setProps(oldDom._component, newVdom.attributes) // 如果新老組件相同,則將新組件的 props 賦到老組件上 renderComponent(oldDom._component) // 對獲得新 props 前后的老組件做 diff 比較(renderComponent 中調(diào)用了 diff) } }遍歷對比子節(jié)點
遍歷對比子節(jié)點的策略有兩個:一是只比較同層級的節(jié)點,二是給節(jié)點加上 key 屬性。它們的目的都是降低空間復(fù)雜度。代碼如下:
// 對比子節(jié)點 function diffChild(oldDom, newVdom) { const keyed = {} const children = [] const oldChildNodes = oldDom.childNodes for (let i = 0; i < oldChildNodes.length; i++) { if (oldChildNodes[i].key) { // 將含有 key 的節(jié)點存進對象 keyed keyed[oldChildNodes[i].key] = oldChildNodes[i] } else { // 將不含有 key 的節(jié)點存進數(shù)組 children children.push(oldChildNodes[i]) } } const newChildNodes = newVdom.children let child for (let i = 0; i < newChildNodes.length; i++) { if (keyed[newChildNodes[i].key]) { // 對應(yīng)上面存在 key 的情形 child = keyed[newChildNodes[i].key] keyed[newChildNodes[i].key] = undefined } else { // 對應(yīng)上面不存在 key 的情形 for (let j = 0; j < children.length; j++) { if (isSameNodeType(children[i], newChildNodes[i])) { // 如果不存在 key,則優(yōu)先找到節(jié)點類型相同的元素 child = children[i] children[i] = undefined break } } } diff(child, newChildNodes[i]) // 遞歸比較 } }測試
在生命周期的小節(jié)中,componentWillReceiveProps 方法還未跑通,稍加修改 setProps 函數(shù)即可:
/** * 更改屬性,componentWillMount 和 componentWillReceiveProps 方法 */ function setProps(component, attributes) { if (attributes) { component.props = attributes // 這段邏輯對應(yīng)上文自定義組件比較中新老組件相同時 setProps 的邏輯 } if (component && component.base && component.componentWillReceiveProps) { component.componentWillReceiveProps(component.props) } else if (component && component.componentWillMount) { component.componentWillMount() } }
來測試下生命周期小節(jié)中最后的測試用例:
生命周期測試
diff 測試
項目地址,關(guān)于如何 pr
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/96305.html
摘要:因為版本將真正廢棄這三生命周期到目前為止,的渲染機制遵循同步渲染首次渲染,更新時更新時卸載時期間每個周期函數(shù)各司其職,輸入輸出都是可預(yù)測,一路下來很順暢。通過進一步觀察可以發(fā)現(xiàn),預(yù)廢棄的三個生命周期函數(shù)都發(fā)生在虛擬的構(gòu)建期間,也就是之前。 showImg(https://segmentfault.com/img/bVbweoj?w=559&h=300); 背景 前段時間準(zhǔn)備前端招聘事項...
摘要:當(dāng)組件要被卸載之前,框架會調(diào)用函數(shù),之后就會卸載組件。開發(fā)者可以在這幾個生命周期函數(shù)中定義一些你想組件變化的操作或者做一些數(shù)據(jù)的改變。 react組件有兩個狀態(tài),一個是渲染狀態(tài),一個是卸載狀態(tài),而渲染狀態(tài)又分為初始渲染狀態(tài)(也可以說是創(chuàng)建狀態(tài))和重新渲染狀態(tài)(也可以說是存在狀態(tài),說明組件一直存在,會發(fā)生多次重新渲染)。這三個狀態(tài)下又會產(chǎn)生一系列的生命周期函數(shù),開發(fā)人員一般只需要了解其中...
摘要:前言的基本概念組件的構(gòu)建方法以及高級用法這背后的一切如何運轉(zhuǎn)深入內(nèi)部的實現(xiàn)機制和原理初探源碼代碼組織結(jié)構(gòu)包含一系列的工具方法插件包含一系列同構(gòu)方法包含一些公用或常用方法如等包含一些測試方法等包含一些邊界錯誤的測試用例是代碼的核心部分它包含了 前言 React的基本概念,API,組件的構(gòu)建方法以及高級用法,這背后的一切如何運轉(zhuǎn),深入Virtual DOM內(nèi)部的實現(xiàn)機制和原理. 初探Rea...
摘要:異步渲染利用事件循環(huán),延遲渲染函數(shù)的調(diào)用調(diào)用回調(diào)函數(shù)處理后跟函數(shù)的情況淺合并邏輯事件循環(huán),關(guān)于的事件循環(huán)和的事件循環(huán)后續(xù)會單獨寫篇文章。 showImg(https://segmentfault.com/img/remote/1460000015785464?w=640&h=280); 看源碼一個痛處是會陷進理不順主干的困局中,本系列文章在實現(xiàn)一個 (x)react 的同時理順 Rea...
摘要:背景介紹入門實例教程起源于的內(nèi)部項目,因為該公司對市場上所有框架,都不滿意,就決定自己寫一套,用來架設(shè)的網(wǎng)站。做出來以后,發(fā)現(xiàn)這套東西很好用,就在年月開源了。也就是說,通過鉤子函 react - JSX React 背景介紹 React 入門實例教程 React 起源于 Facebook 的內(nèi)部項目,因為該公司對市場上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套...
閱讀 2583·2021-10-11 10:58
閱讀 1164·2021-09-29 09:34
閱讀 1521·2021-09-26 09:46
閱讀 3847·2021-09-22 15:31
閱讀 744·2019-08-30 15:54
閱讀 1467·2019-08-30 13:20
閱讀 1263·2019-08-30 13:13
閱讀 1495·2019-08-26 13:52