使用Vue
開發已經幾年的時間了,今天將 10 個日常工作中實踐以及從其他國外文章看到的小技巧分享出來,希望能夠讓大家可以更愉快的擼碼!
1. .sync
修飾符實現props
雙向數據綁定
Vue
的數據流向是單向數據流,即:父組件通過屬性綁定將數據傳給子組件,子組件通過props
接收,但子組件中無法對props
的數據修改來更新父組件的數據,只能通過$emit
派發事件的方式,父組件接收到到事件后執行修改。Vue2.30 以后新增了一個sync
屬性,可以實現子組件派發事件時執行修改父組件數據,無需再父組件接收事件進行更改。
示例:在父組件中控制子組件的顯示隱藏
- 普通實現方式:
// 父組件
<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`修飾符實現方式
// 父組件
<template>
<div>
<button @click="handleClick">click me</button>
// 添加sync修飾符,相當于<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. 監聽生命周期Hook
2.1. 組件外部(父組件)監聽(子)其他組件的生命周期函數
在有些業務場景下,在父組件中我們需要監聽子組件,或者第三方組件的生命周期函數,然后來進行一些業務邏輯處理,但是組件內部有沒有提供change
事件時,此時我們可以使用hook
來監聽所有的生命周期函數。方式: @hook:鉤子函數
<template>
<!--通過@hook:updated監聽組件的updated生命鉤子函數-->
<!--組件的所有生命周期鉤子都可以通過@hook:鉤子函數名 來監聽觸發-->
<custom-select @hook:updated="handleSelectUpdated" />
</template>
<script>
import CustomSelect from "../components/custom-select";
export default {
components: {
CustomSelect,
},
methods: {
handleSelectUpdated() {
console.log("custom-select組件的updated鉤子函數被觸發");
},
},
};
</script>
2.2. 監聽組件內部的生命周期函數
在組件內部,如果想要監聽組件的生命周期鉤子,可以使用$on
,$once
示例:使用 echart 時,監聽窗口改變事件,組件銷毀時取消監聽,通常是在mounted
生命周期中設置監聽,beforeDestroy
鉤子中銷毀監聽。這樣就是要寫在不同的地方,可以使用this.$once('hook:beforeDestroy'),()=> {}
這種方式監聽beforeDestroy
鉤子,在這個鉤子處罰時銷毀。
一次性監聽使用$once
,一直監聽使用$on
。
export default {
mounted() {
this.chart = echarts.init(this.$el);
// 監聽窗口發生變化,resize組件
window.addEventListener("resize", this.handleResizeChart);
// 通過hook監聽組件銷毀鉤子函數,并取消監聽事件
this.$once("hook:beforeDestroy", () => {
window.removeEventListener("resize", this.handleResizeChart);
});
},
methods: {
handleResizeChart() {
// do something
},
},
};
3. 深度作用選擇器
我們在寫Vue
組件的時候為了避免當前組件的樣式對子組件產生影響,通常我們會在當前組件的style
標簽上加上scoped
,這樣在這個組件中寫的樣式只會作用于當前組件,不會對子組件產生影響。
<style scoped>
.example {
color: red;
}
</style>
這樣轉換后的結果:
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
但是有時候,我們引入的第三方組件,我們希望在當前組件中修改第三方組件的樣式,對子組件也產生作用,同時跟第三方組件無關的樣式繼續scoped
。
那么我們可以使用以下兩種方式:
- 混用本地和全局樣式
即:可以在一個組件中同時使用有 scoped 和非 scoped 樣式。
<style>
/* 全局樣式 */
</style>
<style scoped>
/* 本地樣式 */
</style>
- 使用操作符:
>>>
、/deep/
、::v-deep
即:如果你希望scoped
樣式中的一個選擇器能夠作用得“更深”,例如影響子組件,就可以使用操作符。
<style scoped>
.a >>> .b { /* ... */ }
/*或*/
.a /deep/ .b { /* ... */ }
/*或*/
.a ::v-deep .b { /* ... */ }
/* .b選擇器的樣式不僅可以作用當前組件,也可以作用于子組件 */
</style>
編譯后:
.a[data-v-f3f3eg9] .b {
/* ... */
}
4. 組件初始化時觸發Watcher
默認情況下,Watcher
在組件初始化的時候是不會運行的,所以如果在watch
中監聽的數據默認是不會進行初始化的。類似于這樣:
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
時可以對傳入的屬性進行校驗,可以校驗為字符串、數字、數組、對象、函數。但我們也可以進行自定義校驗。
示例:驗證傳入的字符串狀態必須為success
和error
。
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'success',
'error',
].indexOf(value) !== -1
}
}
}
6. 動態指令參數
Vue
在綁定事件的時候支持將指令參數動態傳遞給組件,假設有一個按鈕組件,并且在某些情況下想監聽單擊事件,而在其他情況下想監聽雙擊事件。此時就可以使用動態指令參數。
<template>
...
<aButton @[someEvent]="handleSomeEvent()" />
...
</template>
<script>
...
data(){
return{
...
someEvent: someCondition ? "click" : "dblclick"
}
},
methods:{
handleSomeEvent(){
// handle some event
}
}
...
</script>
7. 組件路由復用
在開發當中,有時候我們不同的路由復用同一個組件,默認情況下,我們切換組件,Vue
出于性能考慮可能不會重復渲染。
但是我們可以通過給router-view
綁定一個key
屬性來進行切換的時候路由重復渲染。
<template>
<router-view :key="$route.fullPath"></router-view>
</template>
8. 批量屬性繼承——使用$props
將父組件的所有的props
傳遞到子組件
在開發中當前組件從父組件接收傳遞下來的數據使用props
接收,如果再將這些props
數據傳遞到子組件,通常情況下,我們同樣是使用屬性綁定的方式一個一個的屬性去綁定。但是如果props
的數據很多,那么一個個的綁定方式就很不優雅。
此時我們可以使用$props
來傳遞。
- Bad
<template>
<!-- 將從父組件接收到的props數據傳遞到子組件 -->
<childComponent
:value1='value1'
:value2='value2'
:value3='value3'
:value4='value4'
:value5='value5'
/>
</template>
<script>
export default {
// 從父組件接收到的props數據
props: ['value1','value2','value3','value4','value5'],
data() {
return {....}
.....
}
}
</scrript>
// childComponent.vue
<script>
export default {
props: ['value1','value2','value3','value4','value5'],
data() {
return {....}
.....
},
mounted() {
// 子組件可以接收到數據
console.log(this.value1)
console.log(this.value2)
console.log(this.value3)
console.log(this.value4)
console.log(this.value5)
}
}
</scrript>
- Good
<template>
<!-- 將從父組件接收到的props數據傳遞到子組件
使用v-bind="$props" 批量傳遞
-->
<childComponent
v-bind="$props"
/>
</template>
<script>
export default {
// 從父組件接收到的props數據
props: ['value1','value2','value3','value4','value5'],
data() {
return {....}
.....
}
}
// childComponent.vue
<script>
export default {
props: ['value1','value2','value3','value4','value5'],
data() {
return {....}
.....
},
mounted() {
// 子組件可以接收到數據
console.log(this.value1)
console.log(this.value2)
console.log(this.value3)
console.log(this.value4)
console.log(this.value5)
}
}
</scrript>
屬性繼承在開發表單組件時,是不得不解決的問題,使用$props
就可以很好的解決批量屬性傳遞問題。
下面以開發一個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. 把所有父級組件的事件監聽傳遞到子組件 - $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()
}
*/
// 調要祖父組件的事件
this.$listeners.on-test1()
// 調要父組件的事件
this.$listeners.on-test2()
}
}
</script>
注意
:如果使用native
修改的事件則獲取不到。即無法獲取到原生事件。
10. 基礎組件自動注冊
在項目開發中我們通常對于通用組件都是用到的地方挨個import
引入,這種方式雖然沒有問題,但是作為一個有追求的程序狗怎么能做這種重復性的勞動呢。
你可以嘗試下面這種基礎組件自動全局注冊的方式,通用組件只需要定義在components/base/
文件夾下,就可以實現自動全局注冊。需要使用的地方可以直接使用,無需單獨引入。
// utils/globals.js
/*
這個方法負責基礎組件的全局注冊;
這些組件可以在項目的任何地方使用而無需引入;
所有的通用組件文件要定義在/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)
// 轉換組件命名為駝峰命名
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 的特性之一是單向數據流,父組件傳遞給子組件的數據,無法做到完全同步,子組件如果想更新父組件的數據需要派發事件給父組件。但vue
也提供了一種方式給我們,自定義v-model
,讓我們可以實現雙向數據綁定的效果。
- 方式一:
// 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為接收到父組件的數據
value: Boolean
},
computed: {
isVisible: {
get() {
return this.value
},
set(val) {
// 更新父組件數據
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為接收到父組件的數據
value: Boolean
},
computed: {
isVisible: {
get() {
return this.value
},
set(val) {
// 默認派發input事件
this.$emit('input', val)
}
}
},
}
</script>
至此,我們的 11 個小技巧就分享完了,如果你覺得有用,請你動動小手點個贊讓我知道[筆芯]