在平常項目開發過程當中,每每離不開封裝組件的過程。好比a頁面有一個功能恰巧b頁面和c頁面也使用。這時候咱們考慮到代碼複用,會考慮將這個功能作成組件。這篇文章咱們就來說一講開發vue組件時候的一些小技巧和一些黑(騷)科(操)技(做)。javascript
咱們會經過大量例子來一個個講解
v-once
低開銷靜態組件首先咱們用腳手架搭建一個項目,用來寫咱們的組件,咱們用到的UI框架是Iview。功能組件的編寫,離不開組件之間的通訊
,這個相關的文章不少,本文就不詳細闡述了,這裏推薦一篇掘金的關於組件通訊的文章,寫的十分全面感興趣的小夥伴能夠去看看。html
slot插槽vue
slot插槽在咱們日常寫組件的時候出場率應該很高。將 <slot>
元素做爲承載分發內容的出口,咱們能夠在插槽內包含任何模板代碼,這使得咱們的組件更加的靈活。咱們來說一個比較經典的例子對話框組件,先看代碼。java
<template>
<div>
<div>頭部</div>
<div>
<slot>默認內容</slot>
</div>
<div>
<slot name="footer"> <Button>確認</Button> </slot>
</div>
</div>
</template>
<script>
export default { name: "model" };
</script>
複製代碼
model標籤中除slot爲footer
的代碼,其餘代碼都將做爲對話框的主體內容顯示,這就是做用域插槽
的用法。而slot爲footer
的代碼將在底部操做按鈕部分顯示,對應<slot name="footer">
這就是具名插槽
的用法。ios
屬性透傳git
屬性透傳的出現的場景很少,咱們以UI組件的二次封裝爲例。假如你的項目中頻繁使用一個UI組件,並且這個UI組件每次都要設置不少相似的屬性,那麼咱們就能夠進行二次封裝,調用起來更加的方便,甚至能夠在原有組件上擴展功能。咱們來看一段二次封裝Iview的Table組件的代碼。github
<script>
import { Table } from "iview";
export default {
name: "customizeTable",
props: { ...Table.props, test: { type: [String], default: "擴展屬性" } },
data() {
return { defaultProps: { border: true, stripe: true } };
},
render(h) {
var props = Object.assign({ ...this._props }, this.defaultProps);
return h(Table, { props: props });
}
};
</script>複製代碼
//方法二web
<template>
<div>
<Table v-bind="{...mergeProps}"></Table>
</div>
</template>
<script>
import { Table } from "iview";
export default {
name: "customizeTable",
props: {
...Table.props,
test: {
type: [String],
default: "擴展屬性"
}
},
computed: {
mergeProps: function() {
return Object.assign({ ...this._props }, this.defaultProps);
}
},
data() {
return {
defaultProps: {
border: true,
stripe: true
}
};
}
};
</script>複製代碼
首先咱們經過Table組件的props屬性獲取的Table全部默認參數,而後經過this._props
實現屬性透傳,同時咱們還能夠在props中擴展你須要的屬性如test
。方法一中綁定屬性咱們使用了render方法,這個咱們在函數式組件中再細說。方法二中咱們經過v-bind實現綁定對象中全部的屬性。api
咱們先引用一下官網對於函數式組件的介紹數組
函數式組件,沒有管理任何狀態,也沒有監放任何傳遞給它的狀態,也沒有生命週期方法。實際上,它只是一個接受一些 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
選項,則該對象包含了應當被注入的屬性。在添加 functional: true
以後,須要更新咱們的錨點標題組件的渲染函數,爲其增長 context
參數,並將 this.$slots.default
更新爲 context.children
,而後將 this.level
更新爲 context.props.level
。
爲何要用函數式組件?
因爲函數式組件沒有生命週期、無狀態、沒有實例,那就意味着它渲染速度更快,開銷更低,同時由於函數式組件只是函數,咱們能夠在函數中實現一些咱們須要的邏輯。
何時使用函數式組件?
在我看來函數式組件更像是一箇中間件的角色,因此它比較常見的應用場景就是做爲包裝組件。舉例來講咱們有一個組件可能渲染多個組件中的一個,須要根據傳遞的參數來決定渲染那個組件,咱們來看一段代碼。
export default {
functional: true,
render: function (createElement, context) {
return createElement(context.props.domType, context.data, context.children);
}
}複製代碼
<template>
<div>
<functionnalScript domType="model" style="width:100%"></functionnalScript>
</div>
</template>
<script>
import customizeTable from "../components/customizeTable.vue";
import model from "../components/model.vue";
import functionnalScript from "../components/functionnal.js";
export default {
name: "pageA",
components: {
customizeTable,
model,
functionnalScript: functionnalScript
}
};
</script>複製代碼
<template functional>
<div>
<button v-bind="data.attrs" v-on="listeners">
<slot />
</button>
</div>
</template>複製代碼
正常調用的時候父頁面咱們有customizeTable組件和model組件咱們能夠經過在一個屬性domType
指定functionnalScript顯示對應的組件,並且能夠在多處複用。
還記得咱們前面是否是講了屬性透傳
,其實經過函數式組件也能夠實現屬性透傳,咱們來看模板的函數式組件functional組件就實現了徹底透傳任何 attribute
、事件監聽器
、子節點
等。
組件的模板通常都是在template選項內定義,而內聯模板在使用組件時,給組件標籤使用inline-template特性,組件就會把它的內容看成模板,而不是把它當內容分發,這讓模板更靈活。可是這也致使了一些問題,咱們先看代碼。
<script>
export default {
name: "inlineChildren",
data() {
return { tip: "我是子組件的數據" };
}
};
</script>
複製代碼
<template>
<div>
<inlineChildren inline-template>
<div>
<h3>{{tip}}</h3>
</div>
</inlineChildren>
</div>
</template>
<script>
import inlineChildren from "../components/inlineChildren.vue";
export default {
name: "pageA",
components: { inlineChildren },
data() {
return { tip: "我是父組件的數據" };
}
};
</script>複製代碼
inline-template的存在讓inlineChildren標籤內部內容再也不做爲slot(分發內容),而是做爲inlineChildren組件的template
,而且這部份內容所在的上下文,是子組件的,並非父組件的。因此除非特殊場景(我是沒遇到過~~),其它適合正如官網所說的,inline-template
會讓模板的做用域變得更加難以理解。因此做爲最佳實踐,請在組件內優先選擇 template
選項或 .vue
文件裏的一個 <template>
元素來定義模板。
什麼是遞歸組件?
在組件本身的模板中調用自身就是咱們所謂的遞歸組件。(注意須要經過組件的name
調用)
使用場景有哪些?
遞歸組件在咱們平常開發中的發揮空間仍是很大的,好比咱們的菜單,評論等有層級的功能均可以使用到它。咱們來看一個菜單的例子:
<template>
<div>
<div v-for="item in menuList">
<div class="hasChildren">{{item.name}}
<Icon v-if="item.children" type="ios-arrow-down" />
</div>
<side-menu v-if="item.children" :menuList="item.children"></side-menu>
</div>
</div>
</template>
<script>
export default {
name: "SideMenu",
props: {
menuList: {
type: Array,
default() {
return [];
}
}
}
};
</script>
<style>
.hasChildren {
background: #999;
color: #fff;
height: 40px;
line-height: 40px;
}
</style>複製代碼
<template>
<div>
<sideMenu :menuList="menuList"></sideMenu>
</div>
</template>
<script>
import sideMenu from "../components//side-menu.vue";
export default {
name: "pageA",
components: { sideMenu },
data() {
return {
menuList: [
{ name: "side1", children: [{ name: "side1-1" }, { name: "side1-2" }] },
{
name: "side2",
children: [
{ name: "side2-1" },
{ name: "side2-2", children: [{ name: "side2-2-1" }] }
]
}
]
};
}
};
</script>複製代碼
咱們能夠看到在sideMenu組件內部調用了它自己,這就是遞歸組件的基本使用方式。在更加複雜一點的狀況就是有2個組件A和B,他們之間互有調用關係。這時會出現一個問題,究竟是先有雞仍是先有蛋。
模塊系統發現它須要 A,可是首先 A 依賴 B,可是 B 又依賴 A,可是 A 又依賴 B,如此往復。這變成了一個循環,不知道如何不通過其中一個組件而徹底解析出另外一個組件,解決方法有2個。
//方法一 在A組件
beforeCreate: function () {
this.$options.components.TreeFolderContents = require('./B.vue').default
}
//方法二 異步加載B組件
components: {
B: () => import('./B.vue')
}複製代碼
v-once低開銷靜態組件
若是你的組件中出現了大量靜態內容,那麼它們可能致使你的組件渲染速度變慢,這時候你能夠在根元素上添加 v-once
attribute 以確保這些內容只計算一次而後緩存起來。
<template>
<div v-once> {{hugeData}} </div>
</template>
<script>
export default {
name: "vOnce",
data() {
return { hugeData: "huge Data..." };
},
mounted() {
setTimeout(() => {
this.hugeData = "change Data...";
}, 100);
}
};
</script>複製代碼
你會發現hugeData
一直是huge Data...沒有變,由於v-once
致使這個模板渲染後數據發生變化沒法正常更新,因此考慮到其餘開發者對於這個屬性並不必定了解,若是不是特別須要儘可能不要使用。
正常的組件調用的過程:
<testModule></testModule>
import TestModule from "./TestModule "
export default { components: { testModule }};複製代碼
若是你須要在多處使用這個組件,那麼意味着每次調用你都須要重複上面的步驟,那麼有沒有更加方便的調用方式呢?console.log
、alert
不陌生吧,有時候咱們也但願能經過這種方式調用一個組件,不在須要把組件寫入到節點中。例如一個提示框組件,可能須要在各類時候喚醒它,而且提示對應內容,咱們經過代碼來實現這個tip組件。
//extend.js
import Vue from 'vue'import Tip from './tip.vue';
//使用基礎 Vue 構造器,建立一個包含tip組件的「子類」
Tip.newInstance = (props) => {
const tip = Vue.extend(Tip);
const messageInstance = new tip({ propsData: props });
const div = document.createElement('div');
document.body.appendChild(div); messageInstance.$mount(div)
}
export default Tip.newInstance複製代碼
//main.js
import tip from 'extend.js'Vue.prototype.$tip=tip;
//調用
this.$tip({});複製代碼
這樣咱們就能實現經過調用一個函數this.$tip({})
來調用Tip組件了。你能夠看到咱們的核心思想就是建立一個包含咱們須要組件的vue實例,而後將這個實例掛載到一個新建立的div上。若是咱們可能屢次調用這個函數,那麼頁面中將會被添加入多個div,這並非咱們所但願,因此咱們還能夠再優化一下。
<template>
<div>
<div v-for="item in tips">
<tip v-bind="item"></tip>
</div>
</div>
</template>
<script>
let i = 0;
const now = new Date().getTime();
function getUid() {
return "tip" + now + "-" + i++;
}
import tip from "./tip.vue";
export default {
name: "tipList",
data() {
return { tips: [] };
},
components: { tip },
methods: {
add(options) {
let name = getUid;
let _options = Object.assign({ name: name }, options);
this.tips.push(_options);
}
}
};
</script>複製代碼
import Vue from 'vue'import Tip from './tipList.vue';
let tip, initialization;
//使用基礎 Vue 構造器,建立一個包含tip組件的「子類」
Tip.newInstance = () => {
tip = Vue.extend(Tip);
const messageInstance = new tip();
const div = document.createElement('div');
document.body.appendChild(div);
messageInstance.$mount(div)
return {
prompt(props) {
messageInstance.show(props);
}
}
}
initialization=function(){
if(!tip){
tip=Tip.newInstance()
}
return tip
}
export default initialization
//main.js
import tip from 'extend.js'
Vue.prototype.$tip=tip;
//調用
this.$tip.prompt({});複製代碼
咱們經過一個組件tipList.vue
的prompt
方法實現屢次調用的tip組件的需求,每次調用tip組件等於向tips數組中push一個對象這樣就會多渲染出一個tip組件。
但願看完以後,你也能在你的組件中使用上它們,讓你的組件能夠減小一些沒必要要的邏輯。做者可能有一些理解不對的地方但願大佬能夠再評論裏提出來,咱們一塊兒學習一塊兒進步。
其餘文章傳送門: