工做中使用react也很長一段時間了,雖然對它的用法,原理有了必定的瞭解,可是總感受停留在表面。本着知其然知其因此然的態度,我試着去看了react源碼,幾天下來,發現並不能看懂,反而更加雲裏霧裏了- -!。既然看不懂,那就看看社區前輩們寫的一些源碼分析文章以及實現思路吧,又這麼過了幾天,總算是摸清點思路,因而在參考了前輩們的基礎上,實現了一個簡易版的react。
這個系列我打算分爲3節,第一節介紹下實現的思路以及結構,第二節講渲染,第三節講更新。javascript
衆所周知,react的核心是Virtual DOM,因此,咱們的思路也是圍繞着Virtual DOM展開,包含Virtual DOM模型的創建,生命週期的管理,對比差別的diff算法,將Virtual DOM轉化爲原生DOM並展現的patch方法等,setState異步機制以及react合成事件因爲尚未研究到,暫時先忽略,事件處理跟某位前輩的思路同樣,也是使用jquery事件代替,這裏咱們主要以實現渲染,更新爲主,相信你在看完這個系列後,能對react的運行原理有必定理解。
項目地址:https://github.com/LuSuguru/f...,如下的全部代碼都是經過es6編寫,切勿用在生產環境。html
React的一切都基於Virtual DOM,咱們第一步天然先實現它,以下:java
/** * @param type :表明當前的節點屬性 * @param key :用來標識element,用於優化之後的更新 * @param props:節點的屬性 */ function VDom(type, key, props) { this.type = type this.key = key this.props = props } // 代碼地址:src/react/reactElement.js
實現了vDom後,理所須要一個方法來將咱們寫的元素轉化爲vDom。通常咱們都是JSX來建立元素的,但它只不過是React.createElment的語法糖。因此,接下來,咱們要實現的就是createElement方法:node
function createElement(type, config, ...children) { const props = {} config = config || {} // 獲取key,用來標識element,方便之後高效的更新 const { key = null } = config let propName = '' // 複製config裏的內容到props for (propName in config) { if (config.hasOwnProperty(propName) && propName !== 'key') { props[propName] = config[propName] } } // 轉化children if (children.length === 1 && Array.isArray(children[0])) { props.children = children[0] } else { props.children = children } return new VDom(type, key, props) } // 代碼地址:src/react/reactElement.js
這段代碼也很是簡單,根據咱們傳入的參數,生成對應的vDomreact
咱們所建立的VDom類型分爲3種:jquery
不一樣的類型,確定有不一樣的渲染和更新邏輯,咱們把這些邏輯與vDom一塊兒,封裝成對應的ReactComponent類,經過ReactComponent類控制vDom,這裏我把它們命名爲ReactTextComponent,ReactDomComponent,ReactCompositeComponent,分別對應三種類型。
首先是基類ReactComponet:git
// component基類,用來處理不一樣的虛擬dom更新,渲染 class Component { constructor(element) { this._vDom = element // 用來標識當前component this._rootNodeId = null } } // 代碼地址:src/react/component/ReactComponent.js
接着再讓不一樣類型的component繼承這個基類,每種component類型都有mount和update兩個方法,用來執行渲染和更新es6
class ReactDomComponent extends ReactComponent { // 渲染 mountComponent() {} // 更新 updateComponent() {} }
class ReactCompositeComponent extends ReactComponent { // 渲染 mountComponent() {} // 更新 updateComponent() {} }
class ReactTextComponent extends ReactComponent { // 渲染 mountComponent() {} // 更新 updateComponent() {} }
實現了ReactComponent後,咱們天然須要一個入口去獲得ReactComponent並調用它的mount。在使用React時,一般都是經過github
import React from 'react' import ReactDOM from 'react-dom' class App extends React.Component { } ReactDOM.render(<App />, document.getElementById('root'))
這段代碼來充當渲染的入口,下面咱們來實現這個入口,(爲了方便說明,我把render方法也放在了React對象中)算法
import Component from './Component' import createElement from './ReactElement' import instantiateReactComponent from './component/util' import $ from 'jquery' const React = { nextReactRootIndex: 0, // 標識id,肯定每一個vDom的惟一性 Component, // 全部自定義組件的父類 createElement, // 建立vdom render(vDom, container) { // 入口 var componentInstance = instantiateReactComponent(vDom) //經過vDom生成Component var markup = componentInstance.mountComponent(this.nextReactRootIndex++) container.innerHTML = markup $(document).trigger('mountReady') } } // 代碼地址:src/react/index.js
因爲渲染和更新都已經封裝在不一樣的ReactComponent裏,因此,這裏也須要一個方法,根據不一樣的vDom類型生成對應的ReactComponent,下面咱們就來實現這個方法:
// component工廠,用來返回一個component實例 function instantiateReactComponent(node) { // 文本節點的狀況 if (typeof node === 'string' || typeof node === 'number') { return new ReactTextComponent(node) } // 瀏覽器默認節點的狀況 if (typeof node === 'object' && typeof node.type === 'string') { return new ReactDomComponent(node) } // 自定義的元素節點 if (typeof node === 'object' && typeof node.type === 'function') { return new ReactCompositeComponent(node) } }
而後再調用入口ReactComponent的mount方法,獲取渲染內容,再將其渲染出來就行。
以上就是實現一個react的整體思路,下節咱們重點放在不一樣ReactComponet的mount上。
下一節地址:https://segmentfault.com/a/11...
參考資料,感謝幾位前輩的分享:
https://www.cnblogs.com/sven3...
https://github.com/purplebamb...陳屹 《深刻React技術棧》