本篇將詳細介紹vue元件化之函式式元件,會用到以下api:

Vue.component()、Vue.extend()、$createElement、patch()。

從事vue開發的小夥伴,平時元件化的過程中大多都採用的vue檔案+模組化系統的方式吧。例如:

import ComponentA from './ComponentA.vue'

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

如果你看過官方文件,瞭解過vue的元件化,你會發現vue提供建立元件的另一種思路:函式式元件。我身邊有從事vue開發的朋友,他們有的對函式式元件並沒什麼概念,也沒有在專案中實際的使用過,下面將和大家一起復習函式式元件的建立和使用。

官網的函式式元件示例:

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.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。

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函式式子元件{{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演算法的核心函式,可以利用它來幫我們完成dom節點的"上樹" !

<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>

總結,以上就是我對vue中建立函式式元件的理解,如果還有更佳的實現方式,歡迎留言~

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