【React進階系列】從零開始手把手教你實現一個Virtual DOM(二)

發表於2018-04-26

假如你的專案使用了React,你知道怎麼做效能優化嗎?
你知道為什麼React讓你寫shouldComponentUpdate或者React.PureComponent嗎?
你知道為什麼React讓你寫Immutable Data Structures嗎?
你知道為什麼React讓你在渲染列表時,一定要給每個子項加一個key嗎?
你知道為什麼React讓你在條件渲染時,不寫if而寫&&操作符或三元操作符嗎?

一切的答案都在Virtual DOM上!
只要你跟著我完成了這個手寫Virtual DOM的系列,上面的所有問題你都將得到解答,從此進入react高手的陣營!


上集回顧

從零開始手把手教你實現一個Virtual DOM(一)
上一集我們介紹了什麼是VDOM,為什麼要用VDOM,以及我們要怎樣來實現一個VDOM。我們再來看一下這張藍圖,今天我們要實現的是這張圖的左半部分。1707261403-5ae076b77b2a7_articlex

package.json

這裡主要主要兩點:

  1. devDependencies中依賴babel-cli和babel-plugin-transform-react-jsx這兩個庫,前者提供Babel的命令列功能,後者主要幫我們把jsx轉化成js。
  2. scripts中我們指定了一條命令:complile,每次當我們在當前目錄下的命令列中敲npm run compile時,babal就會將我們的index.js轉化後新建一個compile.js檔案。

完成後,在命令列中輸入npm install安裝下依賴。

.babelrc

在babel的配置檔案中,我們指定transform-react-jsx這個外掛將轉化後的函式名設定為h。預設的函式名是React.createElement,我們不依賴react,所以顯然換個自己的名字更合適。這裡不清楚h是幹什麼的不要緊,等會看到程式碼你就知道了。

index.html

這個HTML還是很直觀的,類似React,我們有一個根節點id是app。然後我們render函式最終生成的DOM會插入到app這個根節點裡。注意我們引用的compile.js檔案是babel根據等會要寫的index.js檔案自動生成的。

index.js

首先,我們用JSX來編寫“模板”:

接下來,我們要將JSX編譯成js, 也就是hyperscript。我們先用Babel編譯一下,看這段JSX轉成js會是什麼樣子,開啟命令列,輸入npm run compile,得到的compile.js:

可以看出h函式接收的引數,第一個引數是node的型別,比如ul,li,第二個引數是node的屬性,之後的引數是node的children,假如child又是一個node的話,就會繼續呼叫h函式。

清楚了Babel會將我們的JSX編譯成什麼樣子後,接下來我們就可以繼續在index.js中來寫h函式了。

我們的h函式主要的工作就是返回我們真正需要的hyperscript物件,只有三個引數,第一個引數是節點型別,第二個引數是屬性物件,第三個是子節點的陣列。

這裡主要用了ES6的rest, spread引數,不清楚程式碼中兩個...分別是什麼意思的可以先去看我的介紹ES6文章30分鐘掌握ES6/ES2015核心內容(上)。簡單來說,rest就是上面的...children,它將函式多餘的引數放到一個陣列裡,所以children此時變成了一個陣列。而spread則是rest的逆運算,也就是上面的...arr,它將一個陣列轉為用逗號分隔的引數序列。

flatten(children)這個操作是因為children這個陣列裡的元素有可能也是個陣列,那樣就成了一個二維陣列,所以我們需要將陣列拍平成一維陣列。[].concat(...arr)是ES6寫法,傳統的寫法是[].concat.apply([], arr)

我們現在可以先來看一下h函式最終返回的物件長什麼樣子。

我們在render函式中列印出執行完view()的結果,再npm run compile後,用瀏覽器開啟我們的index.html,看控制檯輸出的結果。
3000695485-5ae136f8746ec_articlex

可以,很完美!這個物件就是我們的VDOM了!

下面我們就可以根據VDOM, 來渲染真實DOM了。先改寫render函式:

createElement函式生成DOM,然後再插入到我們在index.html中寫的根節點app。注意render函式式在index.html中被呼叫的。

我們來仔細看下createElement函式。假如說node,即VDOM的型別是文字,我們直接返回一個建立好的文字節點。否則的話,我們取出node中型別,屬性和子節點, 先根據型別建立相應的目標節點,然後再呼叫setProps函式依次設定好目標節點的屬性,最後遍歷子節點,遞迴呼叫createElement方法,將返回的子節點插入到剛剛建立的目標節點裡。最後返回這個目標節點。

還需要注意的一點是,jsx中class的寫成了className,所以我需要特殊處理一下。

大功告成,complie後瀏覽器開啟index.html看看結果吧。133159883-5ae155e2c467f_articlex

今天我們成功的完成了藍圖的左半部分,將JSX轉化成hyperscript,再轉化成VDOM,最後根據VDOM生成DOM,渲染到頁面。明天,我們迎接挑戰,開始處理資料變動引起的重新渲染,我們要如何DIFF新舊VDOM,生成補丁,修改DOM。

相關文章