使用Vue開發(fā)已經(jīng)幾年的時間了,今天將 10 個日常工作中實踐以及從其他國外文章看到的小技巧分享出來,希望能夠讓大家可以更愉快的擼碼!
1. .sync修飾符實現(xiàn)props雙向數(shù)據(jù)綁定
Vue的數(shù)據(jù)流向是單向數(shù)據(jù)流,即:父組件通過屬性綁定將數(shù)據(jù)傳給子組件,子組件通過props接收,但子組件中無法對props的數(shù)據(jù)修改來更新父組件的數(shù)據(jù),只能通過$emit派發(fā)事件的方式,父組件接收到到事件后執(zhí)行修改。Vue2.30 以后新增了一個sync屬性,可以實現(xiàn)子組件派發(fā)事件時執(zhí)行修改父組件數(shù)據(jù),無需再父組件接收事件進(jìn)行更改。
示例:在父組件中控制子組件的顯示隱藏
- 普通實現(xiàn)方式:
// 父組件
<template>
<div>
<button @click="handleClick">click me</button>
<child
:visible="visible"
@on-success="handleSuccess"
@on-cancel="handleCancel"
></child>
</div>
</template>
<script>
export default {
data() {
return {
visible: false
}
}
methods: {
handleClick() {
this.visible = true
},
handleSuccess() {
this.visible = false
},
handleCancel() {
this.visible = false
}
}
}
</script>
// 子組件
<template>
<div class="box" v-show="visible">
<input type="text" />
<div>
<button @click="cancel">取消</button>
<button @click="submit">確定</button>
</div>
</div>
</template>
<script>
export default {
props: {
visible: {
type: Boolean,
default: false,
},
},
methods: {
submit() {
this.$emit("on-success");
},
cancel() {
this.$emit("on-cancel");
},
},
};
</script>
- .sync`修飾符實現(xiàn)方式
// 父組件
<template>
<div>
<button @click="handleClick">click me</button>
// 添加sync修飾符,相當(dāng)于<child
:visible="visible"
@update:visible="visible=$event"
></child>
<child :visible.sync="visible"></child>
</div>
</template>
<script>
export default {
data() {
return {
visible: false
}
}
methods: {
handleClick() {
this.visible = true
}
}
}
</script>
// 子組件
<template>
<div class="box" v-show="visible">
<input type="text" />
<div>
<button @click="cancel">取消</button>
<button @click="submit">確定</button>
</div>
</div>
</template>
<script>
export default {
props: {
visible: {
type: Boolean,
default: false,
},
},
methods: {
submit() {
this.$emit("update:visible", false);
},
cancel() {
this.$emit("update:visible", false);
},
},
};
</script>
2. 監(jiān)聽生命周期Hook
2.1. 組件外部(父組件)監(jiān)聽(子)其他組件的生命周期函數(shù)
在有些業(yè)務(wù)場景下,在父組件中我們需要監(jiān)聽子組件,或者第三方組件的生命周期函數(shù),然后來進(jìn)行一些業(yè)務(wù)邏輯處理,但是組件內(nèi)部有沒有提供change事件時,此時我們可以使用hook來監(jiān)聽所有的生命周期函數(shù)。方式: @hook:鉤子函數(shù)
<template>
<!--通過@hook:updated監(jiān)聽組件的updated生命鉤子函數(shù)-->
<!--組件的所有生命周期鉤子都可以通過@hook:鉤子函數(shù)名 來監(jiān)聽觸發(fā)-->
<custom-select @hook:updated="handleSelectUpdated" />
</template>
<script>
import CustomSelect from "../components/custom-select";
export default {
components: {
CustomSelect,
},
methods: {
handleSelectUpdated() {
console.log("custom-select組件的updated鉤子函數(shù)被觸發(fā)");
},
},
};
</script>
2.2. 監(jiān)聽組件內(nèi)部的生命周期函數(shù)
在組件內(nèi)部,如果想要監(jiān)聽組件的生命周期鉤子,可以使用$on,$once
示例:使用 echart 時,監(jiān)聽窗口改變事件,組件銷毀時取消監(jiān)聽,通常是在mounted生命周期中設(shè)置監(jiān)聽,beforeDestroy鉤子中銷毀監(jiān)聽。這樣就是要寫在不同的地方,可以使用this.$once('hook:beforeDestroy'),()=> {}這種方式監(jiān)聽beforeDestroy鉤子,在這個鉤子處罰時銷毀。
一次性監(jiān)聽使用$once,一直監(jiān)聽使用$on。
export default {
mounted() {
this.chart = echarts.init(this.$el);
// 監(jiān)聽窗口發(fā)生變化,resize組件
window.addEventListener("resize", this.handleResizeChart);
// 通過hook監(jiān)聽組件銷毀鉤子函數(shù),并取消監(jiān)聽事件
this.$once("hook:beforeDestroy", () => {
window.removeEventListener("resize", this.handleResizeChart);
});
},
methods: {
handleResizeChart() {
// do something
},
},
};
3. 深度作用選擇器
我們在寫Vue組件的時候為了避免當(dāng)前組件的樣式對子組件產(chǎn)生影響,通常我們會在當(dāng)前組件的style標(biāo)簽上加上scoped,這樣在這個組件中寫的樣式只會作用于當(dāng)前組件,不會對子組件產(chǎn)生影響。
<style scoped>
.example {
color: red;
}
</style>
這樣轉(zhuǎn)換后的結(jié)果:
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
但是有時候,我們引入的第三方組件,我們希望在當(dāng)前組件中修改第三方組件的樣式,對子組件也產(chǎn)生作用,同時跟第三方組件無關(guān)的樣式繼續(xù)scoped。
那么我們可以使用以下兩種方式:
- 混用本地和全局樣式
即:可以在一個組件中同時使用有 scoped 和非 scoped 樣式。
<style>
/* 全局樣式 */
</style>
<style scoped>
/* 本地樣式 */
</style>
- 使用操作符:
>>>、/deep/、::v-deep
即:如果你希望scoped樣式中的一個選擇器能夠作用得“更深”,例如影響子組件,就可以使用操作符。
<style scoped>
.a >>> .b { /* ... */ }
/*或*/
.a /deep/ .b { /* ... */ }
/*或*/
.a ::v-deep .b { /* ... */ }
/* .b選擇器的樣式不僅可以作用當(dāng)前組件,也可以作用于子組件 */
</style>
編譯后:
.a[data-v-f3f3eg9] .b {
/* ... */
}
4. 組件初始化時觸發(fā)Watcher
默認(rèn)情況下,Watcher在組件初始化的時候是不會運行的,所以如果在watch中監(jiān)聽的數(shù)據(jù)默認(rèn)是不會進(jìn)行初始化的。類似于這樣:
watch: {
title: (newTitle, oldTitle) => {
// 組件初始化時不會打印
console.log("Title changed from " + oldTitle + " to " + newTitle);
};
}
但是,如果我們期望在初始化的時候運行watch,則可以通過添加immediate屬性。
watch: {
title: {
immediate: true,
handler(newTitle, oldTitle) {
// 組件初始化時會被打印
console.log("Title changed from " + oldTitle + " to " + newTitle)
}
}
}
5. 自定義驗證Props
我們都知道在子組件接收props時可以對傳入的屬性進(jìn)行校驗,可以校驗為字符串、數(shù)字、數(shù)組、對象、函數(shù)。但我們也可以進(jìn)行自定義校驗。
示例:驗證傳入的字符串狀態(tài)必須為success和error。
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'success',
'error',
].indexOf(value) !== -1
}
}
}
6. 動態(tài)指令參數(shù)
Vue在綁定事件的時候支持將指令參數(shù)動態(tài)傳遞給組件,假設(shè)有一個按鈕組件,并且在某些情況下想監(jiān)聽單擊事件,而在其他情況下想監(jiān)聽雙擊事件。此時就可以使用動態(tài)指令參數(shù)。
<template>
...
<aButton @[someEvent]="handleSomeEvent()" />
...
</template>
<script>
...
data(){
return{
...
someEvent: someCondition ? "click" : "dblclick"
}
},
methods:{
handleSomeEvent(){
// handle some event
}
}
...
</script>
7. 組件路由復(fù)用
在開發(fā)當(dāng)中,有時候我們不同的路由復(fù)用同一個組件,默認(rèn)情況下,我們切換組件,Vue出于性能考慮可能不會重復(fù)渲染。
但是我們可以通過給router-view綁定一個key屬性來進(jìn)行切換的時候路由重復(fù)渲染。
<template>
<router-view :key="$route.fullPath"></router-view>
</template>
8. 批量屬性繼承——使用$props將父組件的所有的props傳遞到子組件
在開發(fā)中當(dāng)前組件從父組件接收傳遞下來的數(shù)據(jù)使用props接收,如果再將這些props數(shù)據(jù)傳遞到子組件,通常情況下,我們同樣是使用屬性綁定的方式一個一個的屬性去綁定。但是如果props的數(shù)據(jù)很多,那么一個個的綁定方式就很不優(yōu)雅。
此時我們可以使用$props來傳遞。
- Bad
<template>
<!-- 將從父組件接收到的props數(shù)據(jù)傳遞到子組件 -->
<childComponent
:value1='value1'
:value2='value2'
:value3='value3'
:value4='value4'
:value5='value5'
/>
</template>
<script>
export default {
// 從父組件接收到的props數(shù)據(jù)
props: ['value1','value2','value3','value4','value5'],
data() {
return {....}
.....
}
}
</scrript>
// childComponent.vue
<script>
export default {
props: ['value1','value2','value3','value4','value5'],
data() {
return {....}
.....
},
mounted() {
// 子組件可以接收到數(shù)據(jù)
console.log(this.value1)
console.log(this.value2)
console.log(this.value3)
console.log(this.value4)
console.log(this.value5)
}
}
</scrript>
- Good
<template>
<!-- 將從父組件接收到的props數(shù)據(jù)傳遞到子組件
使用v-bind="$props" 批量傳遞
-->
<childComponent
v-bind="$props"
/>
</template>
<script>
export default {
// 從父組件接收到的props數(shù)據(jù)
props: ['value1','value2','value3','value4','value5'],
data() {
return {....}
.....
}
}
// childComponent.vue
<script>
export default {
props: ['value1','value2','value3','value4','value5'],
data() {
return {....}
.....
},
mounted() {
// 子組件可以接收到數(shù)據(jù)
console.log(this.value1)
console.log(this.value2)
console.log(this.value3)
console.log(this.value4)
console.log(this.value5)
}
}
</scrript>
屬性繼承在開發(fā)表單組件時,是不得不解決的問題,使用$props就可以很好的解決批量屬性傳遞問題。
下面以開發(fā)一個XInput為例:
<template>
<label>姓名</label>
<!-- 使用XInput組件 -->
<XInput
:value="value"
:placeholder="placeholder"
:maxlength="maxlength"
:minlength="minlength"
:name="name"
:form="form"
:value="value"
:disabled="disabled"
:readonly="readonly"
:autofocus="autofocus"
@input="handleInputChange"
/>
</template>
- Bad
// XInput.vue
<template>
<div>
<input
@input="$emit('input', $event.target.value)"
:value="value"
:placeholder="placeholder"
:maxlength="maxlength"
:minlength="minlength"
:name="name"
:form="form"
:value="value"
:disabled="disabled"
:readonly="readonly"
:autofocus="autofocus"
/>
</div>
</template>
<script>
export default {
props: [
"label",
"placeholder",
"maxlength",
"minlength",
"name",
"form",
"value",
"disabled",
"readonly",
"autofocus",
],
};
</script>
- Good
<template>
<div>
<input v-bind="$props" />
</div>
</template>
<script>
export default {
props: [
"label",
"placeholder",
"maxlength",
"minlength",
"name",
"form",
"value",
"disabled",
"readonly",
"autofocus",
],
};
</script>
9. 把所有父級組件的事件監(jiān)聽傳遞到子組件 - $listeners
如果子組件不在父組件的根目錄下,則可以將所有事件偵聽器從父組件傳遞到子組件。即在子組件可以獲取到所有子組件的事件。
// Parnet.vue
<template>
<div>父組件</div>
<!-- 組件 -->
<Child @on-test1="handleTest" 1 />
</template>
// Child.vue
<template>
<div>子組件</div>
<!-- 組件 -->
<!-- 使用v-on='$listeners'將所有父組件非原生事件傳遞到子組件 -->
<sub-child
@on-test2="handleTest2"
@on-test3.native="handleTest3"
v-on="$listeners"
/>
</template>
// SubChild.vue
<template>
<div>孫子組件</div>
</template>
<script>
export default {
...
mounted() {
console.log(this.$listeners)
/*
{
on-test1: ? invoker()
on-test2: ? invoker()
}
*/
// 調(diào)要祖父組件的事件
this.$listeners.on-test1()
// 調(diào)要父組件的事件
this.$listeners.on-test2()
}
}
</script>
注意:如果使用native修改的事件則獲取不到。即無法獲取到原生事件。
10. 基礎(chǔ)組件自動注冊
在項目開發(fā)中我們通常對于通用組件都是用到的地方挨個import引入,這種方式雖然沒有問題,但是作為一個有追求的程序狗怎么能做這種重復(fù)性的勞動呢。
你可以嘗試下面這種基礎(chǔ)組件自動全局注冊的方式,通用組件只需要定義在components/base/文件夾下,就可以實現(xiàn)自動全局注冊。需要使用的地方可以直接使用,無需單獨引入。
// utils/globals.js
/*
這個方法負(fù)責(zé)基礎(chǔ)組件的全局注冊;
這些組件可以在項目的任何地方使用而無需引入;
所有的通用組件文件要定義在/components/base/文件夾下;
組件命名采用:Base<componentName>.vue 的方式
*/
export const registerBaseComponents = vm => {
// 引入通用組件
const requireComponent = require.context(
// 讀取文件的路徑
'./components/base',
// 是否遍歷文件的子目錄
false,
// 匹配文件的正則
/Base[\w-]+\.vue$\
)
requireComponent.keys().forEach(fileName => {
// 獲取每個組件文件配置
const componentConfig = requireComponent(fileName)
// 轉(zhuǎn)換組件命名為駝峰命名
const componentName = upperFirst(
camelCase(fileName.replace(/^\.\//,'').replace(/\.\w+$/,''))
)
// 全局注冊組件
vm.component(componentName,componentConfig.default || componentConfig)
})
}
然后,在入口文件main.js中引入并初始化。
import Vue from 'vue'
import { registerBaseComponents } from '@/utils/globals'
registerBaseComponents(Vue)
.....
11、自定義 v-model
Vue 的特性之一是單向數(shù)據(jù)流,父組件傳遞給子組件的數(shù)據(jù),無法做到完全同步,子組件如果想更新父組件的數(shù)據(jù)需要派發(fā)事件給父組件。但vue也提供了一種方式給我們,自定義v-model,讓我們可以實現(xiàn)雙向數(shù)據(jù)綁定的效果。
- 方式一:
// Parent.vue
<Button @click="handleChange"></Button>
.....
<Child v-model="visible" />
...
data() {
return {
visible: false
}
},
methods: {
handleChange() {
this.visible = true
}
}
// Child.vue
<template>
<Drawer
v-model="isVisible">
......
</Drawer>
</template>
<script>
export default {
model: {
prop: 'value',
event: 'change'
},
props: {
// value為接收到父組件的數(shù)據(jù)
value: Boolean
},
computed: {
isVisible: {
get() {
return this.value
},
set(val) {
// 更新父組件數(shù)據(jù)
this.$emit('change', val)
}
}
},
}
</script>
- 方式二:
// Parent.vue
<Button @click="handleChange"></Button>
.....
<Child v-model="visible" />
...
data() {
return {
visible: false
}
},
methods: {
handleChange() {
this.visible = true
}
}
// Child.vue
<template>
<Drawer
v-model="isVisible">
......
</Drawer>
</template>
<script>
export default {
props: {
// value為接收到父組件的數(shù)據(jù)
value: Boolean
},
computed: {
isVisible: {
get() {
return this.value
},
set(val) {
// 默認(rèn)派發(fā)input事件
this.$emit('input', val)
}
}
},
}
</script>
至此,我們的 11 個小技巧就分享完了,如果你覺得有用,請你動動小手點個贊讓我知道[筆芯]