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

上集回顧

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

圖片描述

package.json

{
  "name": "vdom",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "compile": "babel index.js --out-file compiled.js"
  },
  "author": "",
  "license": "",
  "devDependencies": {
    "babel-cli": "^6.23.0",
    "babel-plugin-transform-react-jsx": "^6.23.0"
  }
}

這裏主要主要兩點:node

  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安裝下依賴。react

.babelrc

{
  "plugins": [
    ["transform-react-jsx", {
      "pragma": "h"  // default pragma is React.createElement
    }]
  ]
}

在babel的配置文件中,咱們指定transform-react-jsx這個插件將轉化後的函數名設置爲h。默認的函數名是React.createElement,咱們不依賴react,因此顯然換個本身的名字更合適。這裏不清楚h是幹什麼的沒關係,等會看到代碼你就知道了。npm

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>VDOM</title>
    <style>
        body { margin: 0; font-size: 24; font-family: sans-serif }
        .list { text-decoration: none }
        .list .main { color: red }
    </style>
  </head>
  <body>
    <script src="compiled.js"></script>
    <div id="app"></div>
    
    <script>
      var app = document.getElementById('app')
      render(app)
    </script>
  </body>
</html>

這個HTML仍是很直觀的,相似React,咱們有一個根節點id是app。而後咱們render函數最終生成的DOM會插入到app這個根節點裏。注意咱們引用的compile.js文件是babel根據等會要寫的index.js文件自動生成的。json

index.js

首先,咱們用JSX來編寫「模板」:segmentfault

function view() {
  return <ul id="filmList" className="list">
    <li className="main">Detective Chinatown Vol 2</li>
    <li>Ferdinand</li>
    <li>Paddington 2</li>
  </ul>
}

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

function view() {
  return h(
    "ul",
    { id: "filmList", className: "list" },
    h(
      "li",
      { className: "main" },
      "Detective Chinatown Vol 2"
    ),
    h(
      "li",
      null,
      "Ferdinand"
    ),
    h(
      "li",
      null,
      "Paddington 2"
    )
  );
}

能夠看出h函數接收的參數,第一個參數是node的類型,好比ul,li,第二個參數是node的屬性,以後的參數是node的children,假如child又是一個node的話,就會繼續調用h函數。瀏覽器

清楚了Babel會將咱們的JSX編譯成什麼樣子後,接下來咱們就能夠繼續在index.js中來寫h函數了。babel

function flatten(arr) {
  return [].concat(...arr)
}

function h(type, props, ...children) {
  return {
    type,
    props: props || {},
    children: flatten(children)
  }
}

咱們的h函數主要的工做就是返回咱們真正須要的hyperscript對象,只有三個參數,第一個參數是節點類型,第二個參數是屬性對象,第三個是子節點的數組。app

這裏主要用了ES6的rest, spread參數,不清楚代碼中兩個...分別是什麼意思的能夠先去看個人介紹ES6文章30分鐘掌握ES6/ES2015核心內容(上)。簡單來講,rest就是上面的...children,它將函數多餘的參數放到一個數組裏,因此children此時變成了一個數組。而spread則是rest的逆運算,也就是上面的...arr,它將一個數組轉爲用逗號分隔的參數序列。

flatten(children)這個操做是由於children這個數組裏的元素有可能也是個數組,那樣就成了一個二維數組,因此咱們須要將數組拍平成一維數組。[].concat(...arr)是ES6寫法,傳統的寫法是[].concat.apply([], arr)

咱們如今能夠先來看一下h函數最終返回的對象長什麼樣子。

function render() {
  console.log(view())
}

咱們在render函數中打印出執行完view()的結果,再npm run compile後,用瀏覽器打開咱們的index.html,看控制檯輸出的結果。
圖片描述

能夠,很完美!這個對象就是咱們的VDOM了!

下面咱們就能夠根據VDOM, 來渲染真實DOM了。先改寫render函數:

function render(el) {
  el.appendChild(createElement(view(0)))
}

createElement函數生成DOM,而後再插入到咱們在index.html中寫的根節點app。注意render函數式在index.html中被調用的。

function createElement(node) {
  if (typeof(node) === 'string') {
    return document.createTextNode(node)
  }

  let { type, props, children } = node
  const el = document.createElement(type)
  setProps(el, props)
  children.map(createElement)
    .forEach(el.appendChild.bind(el))

  return el
}

function setProp(target, name, value) {
  if (name === 'className') {
    return target.setAttribute('class', value)
  }

  target.setAttribute(name, value)
}

function setProps(target, props) {
  Object.keys(props).forEach(key => {
    setProp(target, key, props[key])
  })
}

咱們來仔細看下createElement函數。假如說node,即VDOM的類型是文本,咱們直接返回一個建立好的文本節點。不然的話,咱們取出node中類型,屬性和子節點, 先根據類型建立相應的目標節點,而後再調用setProps函數依次設置好目標節點的屬性,最後遍歷子節點,遞歸調用createElement方法,將返回的子節點插入到剛剛建立的目標節點裏。最後返回這個目標節點。

還須要注意的一點是,jsx中class的寫成了className,因此我須要特殊處理一下。

大功告成,complie後瀏覽器打開index.html看看結果吧。

圖片描述

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

相關文章
相關標籤/搜索