淺談Vue中Vnode與Dom的關係, vnode與dom相互融合

在項目中, 咱們會遇到將Vnode或者component渲染到指定的dom中. 這時候, 咱們可能須要去研究Vue中的源碼, 會有如何建立Vnode? 如何渲染Vnode?等相關的疑問. 今天咱們不研究源碼, 咱們從實際實踐中來看看實現的方式.javascript

首先, 在vue中咱們一般是經過建立一個Vue實例對象或者建立Vue後經過$mount來掛載到一個Dom元素上,完成Vue項目的渲染.html

import Vue from "vue";
import App from "../app.vue";

new Vue({
  el: "#app",
  render: h => h(App)
})
複製代碼

或者vue

import Vue from "vue";
import App from "../app.vue";

new Vue({
  render: h => h(App)
}).$mount("#app")
複製代碼

基於這個咱們會想, 是否是再建立一個Vue實例來掛載到另一個指定的Dom中呢?java

<div id="app"></div>
<div id="app2"></div>
複製代碼
new Vue({
   render: h => h(App)
}).$mount("#app")

new Vue({
  render: h => h(App)
}).$mount("#app2")
複製代碼

效果:node

淺談Vue中Vnode與Dom的關係

其實這種想法是可行的, 可是不合理, 咱們不可能每遇到這種狀況就這樣處理吧. 按照習慣, 咱們建立一個Vue就相似建立了一個應用程序/app. 那有沒有其餘辦法呢?app

辦法固然是有的, 這一次, 咱們結合虛擬dom和dom的關係來作這一次思考. 虛擬dom其實就是一個標籤的語法樹, 經過這棵樹經過patch函數建立相關的標籤, 渲染到root Dom. 語法樹上的數據有所變化, 則會更新數據相關的dom標籤. 也就是說數據對應標籤, 數據變化, 標籤內容也會變化, 他們是對應的. 基於這一個想法, 咱們來完成一個簡單的實例.dom

實例說明:函數

目的: 經過dom來建立本身的組件, 而後將組件融入到Vue組件中, 實現互相通訊.ui

步驟:this

  1. 咱們先建立基本的Hello Vue組件.
export default {
  data() {
    return {
      dom: null
    }
  },
  render(create) {
    return create('div', {}, [this.$slots.click, this.$slots.body])
  }
}
複製代碼

在App.vue中加入Hello代碼, hello中的slot設定了一個按鈕, 按鈕擁有點擊事件, 事件是改變slot body中的內容.

<template>
   <section>
       <h3>{{message}}</h3>
       <hello>
          <button slot="click" @click="random">點擊我</button>
          <div slot="body">我是內容: {{content}}</div>
       </hello>
   </section>
</template>

<script>
  import Hello from './hello';
   export default {
      components: {Hello},
      data() {
         return {
           content: '',
           message: "Hello Vuejs"
         }
      },
      methods: {
         random() {
            this.content = new Date();
         }
      }
   }
</script>
複製代碼

效果:

hello組件

只要點擊按鈕就會改變內容.

這樣一個基本的組件就完成了, 可是他仍是純粹的一個vue功能的組件. 咱們須要加入實際DOM的功能. 那咱們繼續跟進. 在Hello組件中加入一個建立的DOM功能. 這個dom展現在右上角.

function DomDemo(){
  this.root = document.createElement("div");
  this.body = document.createElement('div');
  this.root.appendChild(this.body); 

  this.root.className = "domdemo";

  this.setContent = function(html) {
     this.body.innerHTML = html;
  }
  document.body.appendChild(this.root);
}


export default {
  mounted() {
    this.dom = new DomDemo();
    console.log(this.dom)
  },
  data() {
    return {
      dom: null
    }
  },
  render(create) {
    return create('div', {}, [this.$slots.click, this.$slots.body])
  }
}
複製代碼

固然他們尚未直接的關係, 這裏咱們須要將hello組件中的slot body 的內容放入到dom中. 而後點擊按鈕, dom中的內容也會改變, 這也就實現了響應式. vue組件和dom組件實現了交互. 那如何處理呢? 咱們修改一下Hello組件代碼.

function DomDemo(){
  this.root = document.createElement("div");
  this.body = document.createElement('div');
  this.root.appendChild(this.body); 

  this.root.className = "domdemo";

  this.vnodeToElment = function(nodes) {
    if(!nodes) return [];
    const elements = [];
    nodes.forEach(node => {
      elements.push(node.elm);
    });
    return elements;
  }

  this.setContent = function(nodes) {
     this.body.innerHTML = '';
     this.vnodeToElment(nodes).forEach(item => {
       this.body.appendChild(item);
     })
  }
  document.body.appendChild(this.root);
}


export default {
   
  mounted() {
    this.dom = new DomDemo();
    this.dom.setContent(this.$slots.body);
  },
  data() {
    return {
      dom: null
    }
  },
  render(create) {
    return create('div', {}, [this.$slots.click, this.$slots.body])
  }
}
複製代碼

這樣咱們的內容就到了dom中, 點擊vue組件中的按鈕, dom中的內容也會改變了: 首先將vue虛擬dom渲染到界面中, 再移動元素到dom中, 這樣就完成了整個效果. 其實本質仍是引用的問題, 內存地址的關係.

這個例子有什麼做用呢? 其實用處仍是蠻大的. 好比咱們開發的時候, 須要引入dom相關的組件, 好比popover等, 均可以使用這種方式.

總結

從Vue實例對象掛載到DOM, 再到實例對象中的VNODE掛載到指定的dom中, 來實現咱們業務須要的功能, 也方便掛載dom相關的組件. 咱們須要知道Vue的渲染過程, 在不去看源碼的時候, 多思考, 多設想在渲染過程當中會作什麼樣的處理. 以及咱們應該如何應對, 巧妙的融合起來. 這裏咱們也須要把基礎打牢固, 瞭解引用的本質.

相關文章
相關標籤/搜索