在Vue
中,不同的選項有不同的合併策略,比如 data
,props
,methods
是同名屬性覆蓋合併,其他直接合併,而生命周期鈎子函數則是將同名的函數放到一個數組中,在調用的時候依次調用
在Vue
中,提供了一個api
, Vue.config.optionMergeStrategies
,可以通過這個api去自定義選項的合併策略。
在代碼中打印
console.log(Vue.config.optionMergeStrategies)
通過合併策略自定義生命周期函數
背景
最近客戶給領導反饋,我們的系統用一段時間,瀏覽器就變得有點卡,不知道為什麼。問題出來了,本來想甩鍋到後端,但是瀏覽器問題,沒法甩鍋啊,那就排查吧。
後來發現頁面有許多定時器,ajax
輪詢還有動畫,打開一個瀏覽器頁簽沒法問題,打開多了,瀏覽器就變得卡了,這時候我就想如果能在用戶切換頁簽時候將這些都停掉,不久解決了。百度裏面上下檢索,找到了一個事件visibilitychange
,可以用來判斷瀏覽器頁簽是否显示。
有方法了,就寫唄
export default {
created() {
window.addEventListener('visibilitychange', this.$_hanldeVisiblityChange)
// 此處用了hookEvent,可以參考小編前一篇文章
this.$on('hook:beforeDestroy', () => {
window.removeEventListener(
'visibilitychange',
this.$_hanldeVisiblityChange
)
})
},
methods: {
$_hanldeVisiblityChange() {
if (document.visibilityState === 'hidden') {
// 停掉那一堆東西
}
if (document.visibilityState === 'visible') {
// 開啟那一堆東西
}
}
}
}
通過上面的代碼,可以看到在每一個需要監聽處理的文件都要寫一堆事件監聽,判斷頁面是否显示的代碼,一處兩處還可以,文件多了就頭疼了,這時候小編突發奇想,定義一個頁面显示隱藏的生命周期鈎子,把這些判斷都封裝起來
自定義生命周期鈎子函數
定義生命周期函數 pageHidden
與 pageVisible
import Vue from 'vue'
// 通知所有組件頁面狀態發生了變化
const notifyVisibilityChange = (lifeCycleName, vm) => {
// 生命周期函數會存在$options中,通過$options[lifeCycleName]獲取生命周期
const lifeCycles = vm.$options[lifeCycleName]
// 因為使用了created的合併策略,所以是一個數組
if (lifeCycles && lifeCycles.length) {
// 遍歷 lifeCycleName對應的生命周期函數列表,依次執行
lifeCycles.forEach(lifecycle => {
lifecycle.call(vm)
})
}
// 遍歷所有的子組件,然後依次遞歸執行
if (vm.$children && vm.$children.length) {
vm.$children.forEach(child => {
notifyVisibilityChange(lifeCycleName, child)
})
}
}
// 添加生命周期函數
export function init() {
const optionMergeStrategies = Vue.config.optionMergeStrategies
// 定義了兩個生命周期函數 pageVisible, pageHidden
// 為什麼要賦值為 optionMergeStrategies.created呢
// 這個相當於指定 pageVisible, pageHidden 的合併策略與 created的相同(其他生命周期函數都一樣)
optionMergeStrategies.pageVisible = optionMergeStrategies.beforeCreate
optionMergeStrategies.pageHidden = optionMergeStrategies.created
}
// 將事件變化綁定到根節點上面
// rootVm vue根節點實例
export function bind(rootVm) {
window.addEventListener('visibilitychange', () => {
// 判斷調用哪個生命周期函數
let lifeCycleName = undefined
if (document.visibilityState === 'hidden') {
lifeCycleName = 'pageHidden'
} else if (document.visibilityState === 'visible') {
lifeCycleName = 'pageVisible'
}
if (lifeCycleName) {
// 通過所有組件生命周期發生變化了
notifyVisibilityChange(lifeCycleName, rootVm)
}
})
}
應用
- 在
main.js
主入口文件引入
import { init, bind } from './utils/custom-life-cycle'
// 初始化生命周期函數, 必須在Vue實例化之前確定合併策略
init()
const vm = new Vue({
router,
render: h => h(App)
}).$mount('#app')
// 將rootVm 綁定到生命周期函數監聽裏面
bind(vm)
2. 在需要的地方監聽生命周期函數
export default {
pageVisible() {
console.log('頁面显示出來了')
},
pageHidden() {
console.log('頁面隱藏了')
}
}
provide
與inject
,不止父子傳值,祖宗傳值也可以
Vue
相關的面試經常會被面試官問道,Vue
父子之間傳值的方式有哪些,通常我們會回答,props
傳值,$emit
事件傳值,vuex
傳值,還有eventbus
傳值等等,今天再加一種provide
與inject
傳值,離offer
又近了一步。(對了,下一節還有一種)
使用過React
的同學都知道,在React
中有一個上下文Context
,組件可以通過Context
向任意後代傳值,而Vue
的provide
與inject
的作用於Context
的作用基本一樣
先舉一個例子
使用過elemment-ui
的同學一定對下面的代碼感到熟悉
<template>
<el-form :model="formData" size="small">
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" />
</el-form-item>
<el-form-item label="年齡" prop="age">
<el-input-number v-model="formData.age" />
</el-form-item>
<el-button>提交</el-button>
</el-form>
</template>
<script>
export default {
data() {
return {
formData: {
name: '',
age: 0
}
}
}
}
</script>
看了上面的代碼,貌似沒啥特殊的,天天寫啊。在el-form
上面我們指定了一個屬性size="small"
,然後有沒有發現表單裏面的所有表單元素以及按鈕的 size
都變成了small
,這個是怎麼做到的?接下來我們自己手寫一個表單模擬一下
自己手寫一個表單
自定義表單custom-form.vue
<template>
<form class="custom-form">
<slot></slot>
</form>
</template>
<script>
export default {
props: {
// 控製表單元素的大小
size: {
type: String,
default: 'default',
// size 只能是下面的四個值
validator(value) {
return ['default', 'large', 'small', 'mini'].includes(value)
}
},
// 控製表單元素的禁用狀態
disabled: {
type: Boolean,
default: false
}
},
// 通過provide將當前表單實例傳遞到所有後代組件中
provide() {
return {
customForm: this
}
}
}
</script>
在上面代碼中,我們通過provide
將當前組件的實例傳遞到後代組件中,provide
是一個函數,函數返回的是一個對象
自定義表單項custom-form-item.vue
沒有什麼特殊的,只是加了一個label
,element-ui
更複雜一些
<template>
<div class="custom-form-item">
<label class="custom-form-item__label">{{ label }}</label>
<div class="custom-form-item__content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
default: ''
}
}
}
</script>
自定義輸入框 custom-input.vue
<template>
<div
class="custom-input"
:class="[
`custom-input--${getSize}`,
getDisabled && `custom-input--disabled`
]"
>
<input class="custom-input__input" :value="value" @input="$_handleChange" />
</div>
</template>
<script>
/* eslint-disable vue/require-default-prop */
export default {
props: {
// 這裏用了自定義v-model
value: {
type: String,
default: ''
},
size: {
type: String
},
disabled: {
type: Boolean
}
},
// 通過inject 將form組件注入的實例添加進來
inject: ['customForm'],
computed: {
// 通過計算組件獲取組件的size, 如果當前組件傳入,則使用當前組件的,否則是否form組件的
getSize() {
return this.size || this.customForm.size
},
// 組件是否禁用
getDisabled() {
const { disabled } = this
if (disabled !== undefined) {
return disabled
}
return this.customForm.disabled
}
},
methods: {
// 自定義v-model
$_handleChange(e) {
this.$emit('input', e.target.value)
}
}
}
</script>
在
form
中,我們通過
provide
返回了一個對象,在
input
中,我們可以通過
inject
獲取
form
中返回對象中的項,如上代碼
inject:['customForm']
所示,然後就可以在組件內通過
this.customForm
調用
form
實例上面的屬性與方法了
在項目中使用
<template>
<custom-form size="small">
<custom-form-item label="姓名">
<custom-input v-model="formData.name" />
</custom-form-item>
</custom-form>
</template>
<script>
import CustomForm from '../components/custom-form'
import CustomFormItem from '../components/custom-form-item'
import CustomInput from '../components/custom-input'
export default {
components: {
CustomForm,
CustomFormItem,
CustomInput
},
data() {
return {
formData: {
name: '',
age: 0
}
}
}
}
</script>
執行上面代碼,運行結果為:
<form class="custom-form">
<div class="custom-form-item">
<label class="custom-form-item__label">姓名</label>
<div class="custom-form-item__content">
<!--size=small已經添加到指定的位置了-->
<div class="custom-input custom-input--small">
<input class="custom-input__input">
</div>
</div>
</div>
</form>
通過上面的代碼可以看到,input
組件已經設置組件樣式為custom-input--small
了
inject
格式說明
除了上面代碼中所使用的inject:['customForm']
寫法之外,inject
還可以是一個對象。且可以指定默認值
修改上例,如果custom-input
外部沒有custom-form
,則不會注入customForm
,此時為customForm
指定默認值
{
inject: {
customForm: {
// 對於非原始值,和props一樣,需要提供一個工廠方法
default: () => ({
size: 'default'
})
}
}
}
使用限制
1.provide
和inject
的綁定不是可響應式的。但是,如果你傳入的是一個可監聽的對象,如上面的customForm: this
,那麼其對象的屬性還是可響應的。
2.Vue
官網建議provide
和 inject
主要在開發高階插件/組件庫時使用。不推薦用於普通應用程序代碼中。因為provide
和inject
在代碼中是不可追溯的(ctrl + f可以搜),建議可以使用Vuex
代替。 但是,也不是說不能用,在局部功能有時候用了作用還是比較大的。
插槽,我要鑽到你的懷裡
插槽,相信每一位Vue
都有使用過,但是如何更好的去理解插槽,如何去自定義插槽,今天小編為你帶來更形象的說明。
默認插槽
<template>
<!--這是一個一居室-->
<div class="one-bedroom">
<!--添加一個默認插槽,用戶可以在外部隨意定義這個一居室的內容-->
<slot></slot>
</div>
</template>
<template>
<!--這裏一居室-->
<one-bedroom>
<!--將傢具放到房間裏面,組件內部就是上面提供的默認插槽的空間-->
<span>先放一個小床,反正沒有女朋友</span>
<span>再放一個電腦桌,在家還要加班寫bug</span>
</one-bedroom>
</template>
<script>
import OneBedroom from '../components/one-bedroom'
export default {
components: {
OneBedroom
}
}
</script>
具名插槽
<template>
<div class="two-bedroom">
<!--這是主卧-->
<div class="master-bedroom">
<!---主卧使用默認插槽-->
<slot></slot>
</div>
<!--這是次卧-->
<div class="secondary-bedroom">
<!--次卧使用具名插槽-->
<slot name="secondard"></slot>
</div>
</div>
</template>
<template>
<two-bedroom>
<!--主卧使用默認插槽-->
<div>
<span>放一個大床,要結婚了,嘿嘿嘿</span>
<span>放一個衣櫃,老婆的衣服太多了</span>
<span>算了,還是放一個電腦桌吧,還要寫bug</span>
</div>
<!--次卧,通過v-slot:secondard 可以指定使用哪一個具名插槽, v-slot:secondard 也可以簡寫為 #secondard-->
<template v-slot:secondard>
<div>
<span>父母要住,放一個硬一點的床,軟床對腰不好</span>
<span>放一個衣櫃</span>
</div>
</template>
</two-bedroom>
</template>
<script>
import TwoBedroom from '../components/slot/two-bedroom'
export default {
components: {
TwoBedroom
}
}
</script>
作用域插槽
<template>
<div class="two-bedroom">
<!--其他內容省略-->
<div class="toilet">
<!--通過v-bind 可以向外傳遞參數, 告訴外面衛生間可以放洗衣機-->
<slot name="toilet" v-bind="{ washer: true }"></slot>
</div>
</div>
</template>
<template>
<two-bedroom>
<!--其他省略-->
<!--衛生間插槽,通過v-slot="scope"可以獲取組件內部通過v-bind傳的值-->
<template v-slot:toilet="scope">
<!--判斷是否可以放洗衣機-->
<span v-if="scope.washer">這裏放洗衣機</span>
</template>
</two-bedroom>
</template>
插槽默認值
<template>
<div class="second-hand-house">
<div class="master-bedroom">
<!--插槽可以指定默認值,如果外部調用組件時沒有修改插槽內容,則使用默認插槽-->
<slot>
<span>這裡有一張水床,玩的夠嗨</span>
<span>還有一個衣櫃,有點舊了</span>
</slot>
</div>
<!--這是次卧-->
<div class="secondary-bedroom">
<!--次卧使用具名插槽-->
<slot name="secondard">
<span>這裡有一張嬰兒床</span>
</slot>
</div>
</div>
</template>
<second-hand-house>
<!--主卧使用默認插槽,只裝修主卧-->
<div>
<span>放一個大床,要結婚了,嘿嘿嘿</span>
<span>放一個衣櫃,老婆的衣服太多了</span>
<span>算了,還是放一個電腦桌吧,還要寫bug</span>
</div>
</second-hand-house>
dispatch
和broadcast
,這是一種有歷史的組件通信方式
dispatch
與
broadcast
是一種有歷史的組件通信方式,為什麼是有歷史的,因為他們是
Vue1.0
提供的一種方式,在
Vue2.0
中廢棄了。但是廢棄了不代表我們不能自己手動實現,像許多UI庫內部都有實現。本文以
element-ui
實現為基礎進行介紹。同時看完本節,你會對組件的
$parent
,
$children
,
$options
有所了解。
方法介紹
$dispatch
: $dispatch
會向上觸發一個事件,同時傳遞要觸發的祖先組件的名稱與參數,當事件向上傳遞到對應的組件上時會觸發組件上的事件偵聽器,同時傳播會停止。
$broadcast
: $broadcast
會向所有的後代組件傳播一個事件,同時傳遞要觸發的後代組件的名稱與參數,當事件傳遞到對應的後代組件時,會觸發組件上的事件偵聽器,同時傳播會停止(因為向下傳遞是樹形的,所以只會停止其中一個恭弘=叶 恭弘子分支的傳遞)。
$dispatch
實現與應用
1. 代碼實現
// 向上傳播事件
// @param {*} eventName 事件名稱
// @param {*} componentName 接收事件的組件名稱
// @param {...any} params 傳遞的參數,可以有多個
function dispatch(eventName, componentName, ...params) {
// 如果沒有$parent, 則取$root
let parent = this.$parent || this.$root
while (parent) {
// 組件的name存儲在組件的$options.componentName 上面
const name = parent.$options.name
// 如果接收事件的組件是當前組件
if (name === componentName) {
// 通過當前組件上面的$emit觸發事件,同事傳遞事件名稱與參數
parent.$emit.apply(parent, [eventName, ...params])
break
} else {
// 否則繼續向上判斷
parent = parent.$parent
}
}
}
// 導出一個對象,然後在需要用到的地方通過混入添加
export default {
methods: {
$dispatch: dispatch
}
}
2. 代碼應用
-
在子組件中通過$dispatch
向上觸發事件
import emitter from '../mixins/emitter'
export default {
name: 'Chart',
// 通過混入將$dispatch加入進來
mixins: [emitter],
mounted() {
// 在組件渲染完之後,將組件通過$dispatch將自己註冊到Board組件上
this.$dispatch('register', 'Board', this)
}
}
-
在Board
組件上通過$on
監聽要註冊的事件
$broadcast
實現與應用
1. 代碼實現
//向下傳播事件
// @param {*} eventName 事件名稱
// @param {*} componentName 要觸發組件的名稱
// @param {...any} params 傳遞的參數
function broadcast(eventName, componentName, ...params) {
this.$children.forEach(child => {
const name = child.$options.name
if (name === componentName) {
child.$emit.apply(child, [eventName, ...params])
} else {
broadcast.apply(child, [eventName, componentName, ...params])
}
})
}
// 導出一個對象,然後在需要用到的地方通過混入添加
export default {
methods: {
$broadcast: broadcast
}
}
2. 代碼應用
在父組件中通過$broadcast
向下觸發事件
import emitter from '../mixins/emitter'
export default {
name: 'Board',
// 通過混入將$dispatch加入進來
mixins: [emitter],
methods:{
//在需要的時候,刷新組件
$_refreshChildren(params) {
this.$broadcast('refresh', 'Chart', params)
}
}
}
在後代組件中通過$on
監聽刷新事件
export default {
name: 'Chart',
created() {
this.$on('refresh',(params) => {
// 刷新事件
})
}
}
總結
通過上面的例子,同學們應該都能對$dispatch
和$broadcast
有所了解,但是為什麼Vue2.0
要放棄這兩個方法呢?官方給出的解釋是:”因為基於組件樹結構的事件流方式實在是讓人難以理解,並且在組件結構擴展的過程中會變得越來越脆弱。這種事件方式確實不太好,我們也不希望在以後讓開發者們太痛苦。並且 $dispatch
和 $broadcast
也沒有解決兄弟組件間的通信問題。“
確實如官網所說,這種事件流的方式確實不容易讓人理解,而且後期維護成本比較高。但是在小編看來,不管黑貓白貓,能抓老鼠的都是好貓,在許多特定的業務場景中,因為業務的複雜性,很有可能使用到這樣的通信方式。但是使用歸使用,但是不能濫用,小編一直就在項目中有使用。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※台北網頁設計公司全省服務真心推薦
※想知道最厲害的網頁設計公司"嚨底家"!
※新北清潔公司,居家、辦公、裝潢細清專業服務
※推薦評價好的iphone維修中心