vue函數式組件在項目中的使用


本篇主要介紹非單文件組件( Vue.component創建的組件其中包括函數式組件)在項目中的使用,會用到以下api:Vue.component()、Vue.extend()、$createElement、patch()。

單文件組件:文件擴展名為 .vue 的 single-file components (單文件組件)。

從事vue開發的小伙伴,平時組件化的過程中大多都采用 單文件組件+模塊化系統的方式吧。例如:

import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  },
  // ...
}

如果你看過官方文檔,了解過vue的組件化,會發現除了.vue結尾的單文件組件,vue還提供創建組件的另一種思路:Vue.component、Vue.extend,其中函數式組件也正是利用這種方式來創建的。

函數式組件:沒有管理任何狀態,也沒有監聽任何傳遞給它的狀態,也沒有生命周期方法。實際上,它只是一個接受一些 prop 的函數。在這樣的場景下,我們可以將組件標記為 functional,這意味它無狀態 (沒有響應式數據),也沒有實例 (沒有 this上下文),組件需要的一切都是通過 context參數傳遞:

  • props:提供所有 prop 的對象
  • children:VNode 子節點的數組
  • slots:一個函數,返回了包含所有插槽的對象
  • scopedSlots:(2.6.0+) 一個暴露傳入的作用域插槽的對象。也以函數形式暴露普通插槽。
  • data:傳遞給組件的整個數據對象,作為 createElement的第二個參數傳入組件
  • parent:對父組件的引用
  • listeners:(2.3.0+) 一個包含了所有父組件為當前組件注冊的事件監聽器的對象。這是 data.on 的一個別名。
  • injections:(2.3.0+) 如果使用了 inject選項,則該對象包含了應當被注入的 property。

 官網的函數式組件示例:

Vue.component('my-component', {
  functional: true,
  // Props 是可選的
  props: {
    // ...
  },
  // 為了彌補缺少的實例
  // 提供第二個參數作為上下文
  render: function (createElement, context) {
    // ...
  }
})

將以上示例適當修改,並引入到我們項目中:

child.js

import Vue from 'vue';

export default Vue.component('my-component',{ // 該組件抽成js文件,
    functional: true,
    // Props 是可選的
    props: {
      // ...
    },
    // 為了彌補缺少的實例
    // 提供第二個參數作為上下文
    render: function (createElement, context) {
      return createElement('h1', '我是函數式子組件')
    }
  })

這里我將該組件抽成單獨的js文件,便於復用和維護。在父組件引入該組件:

parent.vue:

<template>
  <div>
    <h1>我是父組件</h1>
    <child />
   <my-component />
</div> </template> <script> import child from './chid.js' export default { name: "parent", components: { child }, data() { return { }; }, mounted(){ }, methods:{ } }; </script>

效果:

 你會發現 <child />和<my-component />都能引入到父組件中,前者好理解,import引入后component中注冊,后者為啥能直接用呢?是因為Vue.component()注冊的是全局組件

我們再增加一個子組件(跟上面的組件同名):

import Vue from 'vue';
//這是函數式組件2
export default Vue.component('my-component',{ // 同名
    functional: true,
    // Props 是可選的
    props: {
      // ...
    },
    // 為了彌補缺少的實例
    // 提供第二個參數作為上下文
    render: function (createElement, context) {
      return createElement('h1', '我是函數式子組件2')
    }
  })

現在看看運行效果:

結果會發現,"函數式組件2"被覆蓋了!由於Vue.component()同名的組件會覆蓋,也因為全局組件不好辨別當前的組件名是否已經注冊,所以建議使用Vue.extend()來新建函數式組件(不想創建全局組件的情況下)。

Vue.extend使用基礎 Vue 構造器,創建一個“子類”。參數是一個包含組件選項的對象。

Vue.extend相當於一個擴展實例構造器,用於創建一個具有初始化選項的Vue子類,在實例化時可以進行擴展選項,最后使用$mount方法綁定在元素上。$mounte會替換被掛載節點下的內容!

Vue.extend和Vue.component之間的關系:

<template>
  <div>
    <h1>我是父組件</h1>
    <div id="parent"></div>
    <com />
  </div>
</template>

<script>
import child from "./child.js";
import Vue from 'vue';
Vue.component('com', child)  

...

Vue.extend可以當做Vue.component的組件選項。

因此如果希望創建的組件為全局組件, 可用Vue.component(),否則用Vue.extend(),之后手動掛載。

 

下面用Vue.extend()創建組件:

child.js:

import Vue from 'vue';

export default Vue.extend({
  // Props 是可選的
  props: {
    
  },
  template: `<div>我是extend函數式子組件</div>`
  
})

這里使用的template寫法,vue底層執行的時候會將template解析成AST,然后將AST轉化為render函數,render的過程vue幫我們處理就好了,所以不習慣寫render函數的同學可以用template語法(用模板語法則該組件將不再是函數式組件,並擁有實例this)。

parent.vue:

<template>
  <div>
    <h1>我是父組件</h1>
    <div id="parent"></div>
    
  </div>
</template>

<script>
import child from './child.js'
export default {
  name: "parent",
  components: {
    
  },
  data() {
    return {
     
    };
  },
  created(){
    
  },
  mounted(){
    new child(
      {
        props: {
          val:{
            default:6
          }
        },
        methods:{
          func1(){
           console.log("我是方法")
          }
        }
      }
    ).$mount("#parent") // 用$mount()將child產生的實例掛載到id為parent的dom下
  },
  methods:{
    
  }
};
</script>

child.js:

import Vue from 'vue';

export default Vue.extend({

  template: `<div>我是extend<span></span>子組件{{val}}</div>`,

  mounted(){
    this.func1()
  }
  
})

 

效果:

以上不管是Vue.component()還是Vue.extend()最終都是創建Vue的組件實例,它既不是虛擬dom也不是真實的dom節點。

 業務中,有些ui庫要求我們傳入vNode或者真實的dom,例如element UI中的$confirm彈框中的message屬性,既可以傳普通的字符串,也可以傳vNode。下面來手寫一段vNode:

...
const h = this.$createElement; // 對$createElement不熟悉的可以查看vue文檔 const vNode = h('p', null, [ h('span', null, '內容可以是 '), h('i', { style: 'color: teal' }, 'VNode') ]); console.log(vNode);

打印:

上面的vNode結構非常簡單,h函數的children參數可以手寫,但如果vNode結構很復雜的話,手寫就顯得很凌亂。因此在h函數的第一個參數,我們可以傳一個component組件。

<template>
  <div>
    <h1>我是父組件</h1>
    <div id="parent"></div>
    
  </div>
</template>

<script>
import child from './child.js'
export default {
  name: "parent",
  components: {
    
  },
  data() {
    return {
     
    };
  },
  created(){
    
  },
  mounted(){
    const h = this.$createElement;
    let vNode = h(child,{
      props: {
        val:8,
        func1: ()=>{console.log('哈哈哈哈')}
      }
    })
    console.log(vNode)
  },
  methods:{
    
  }
};
</script>

 

 接下來在elementUI中使用:

<template>
  <div>
    <h1>我是父組件</h1>
    <div id="parent"></div>
  </div>
</template>

<script>
import child from "./child.js";
export default {
  name: "parent",
  components: {},
  data() {
    return {};
  },
  created() {},
  mounted() {
    const h = this.$createElement;
    let vNode = h(child, {
      props: {
        val: 8,
        func1: () => {
          console.log("哈哈哈哈");
        },
      },
    });
    this.$confirm("提示", {
      title: "提示",
      message: vNode,
      showCancelButton: true,
      confirmButtonText: "確定",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      
    });
    console.log(vNode);
  },
  methods: {},
};
</script>

 

 繼續思考,上面ui組件會幫我們將vNode虛擬節點渲染到頁面中,如果我想將vNode渲染到我們頁面中的某個節點,該怎么實現呢?

其實vue的實例原型上有一個方法:__patch__,而patch函數就是vue中diff算法的核心函數,可以利用它來幫我們完成vNode虛擬節點的"上樹" !

<template>
  <div>
    <h1>我是父組件</h1>
    <div id="parent"></div>
  </div>
</template>

<script>
import child from "./child.js";
export default {
  name: "parent",
  components: {},
  data() {
    return {};
  },
  created() {},
  mounted() {
    const h = this.$createElement;
    let vNode = h(child, {
      props: {
        val: 8,
        func1: () => {
          console.log("哈哈哈哈");
        },
      },
    });
    const dom = document.getElementById("parent")
    this.__patch__(dom,vNode) //dom是老節點(id為parent),vNode是我們即將渲染的新節點,通過diff算法重新渲染parent節點
  },
  methods: {},
};
</script>

 

最后封裝一個render函數版的原汁原味的函數式組件,render函數並沒有使用createElement,而是使用jsx,書寫更方便,上代碼!

functionalComponent.js:
import Vue from 'vue';
 const functionalComponent = Vue.extend(
  {
    functional: true,
    props: {
      render: Function
    },
    render(h, ctx) {
      return ctx.props.render.call(ctx, h);
    }
  }
 )
 
 export default functionalComponent

parent.js:

<template>
  <div>
    <h1>我是父組件</h1>
    <div id="parent"></div>
  </div>
</template>

<script>
import functionalComponent from "./functionalComponent";

export default {
  name: "parent",
  components: {},
  data() {
    return {};
  },
  created() {},
  mounted() {
    const _h = this.$createElement;

    let vNode = _h(functionalComponent, {
      props: {
        render: (createElement, ctx) => {
          return <div>123</div>; // jsx
        },
      },
    });
    const dom = document.getElementById("parent");
    this.__patch__(dom, vNode);
  },
  methods: {},
};
</script>

 

總結,以上就是我對vue中創建非單文件組件以及函數式組件的理解,如果有什么意見和建議,歡迎留言~

 

腳踏實地行,海闊天空飛~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM