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