在Vue中,不一樣的選項有不一樣的合併策略,好比 data,props,methods是同名屬性覆蓋合併,其餘直接合並,而生命週期鉤子函數則是將同名的函數放到一個數組中,在調用的時候依次調用
在Vue中,提供了一個api, Vue.config.optionMergeStrategies,能夠經過這個api去自定義選項的合併策略。
在代碼中打印
經過合併策略自定義生命週期函數
背景
最近客戶給領導反饋,咱們的系統用一段時間,瀏覽器就變得有點卡,不知道爲何。問題出來了,原本想甩鍋到後端,可是瀏覽器問題,無法甩鍋啊,那就排查吧。
後來發現頁面有許多定時器,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主入口文件引入
123456789101112
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. 在須要的地方監聽生命週期函數
12345678
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的同窗必定對下面的代碼感到熟悉
1234567891011121314151617181920212223
<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更復雜一些
123456789101112131415161718
<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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
<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>
執行上面代碼,運行結果爲:
1234567891011
<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指定默認值
12345678910
{ inject: { customForm: { // 對於非原始值,和props同樣,須要提供一個工廠方法 default: () => ({ size: 'default' }) } }}
使用限制
1.provide和inject的綁定不是可響應式的。可是,若是你傳入的是一個可監聽的對象,如上面的customForm: this,那麼其對象的屬性仍是可響應的。
2.Vue官網建議provide 和 inject 主要在開發高階插件/組件庫時使用。不推薦用於普通應用程序代碼中。由於provide和inject在代碼中是不可追溯的(ctrl + f能夠搜),建議可使用Vuex代替。 可是,也不是說不能用,在局部功能有時候用了做用仍是比較大的。
插槽,我要鑽到你的懷裏
插槽,相信每一位Vue都有使用過,可是如何更好的去理解插槽,如何去自定義插槽,今天小編爲你帶來更形象的說明。
默認插槽
1234567
<template> <!--這是一個一居室--> <div class="one-bedroom"> <!--添加一個默認插槽,用戶能夠在外部隨意定義這個一居室的內容--> <slot></slot> </div></template>
12345678910111213141516
<template> <!--這裏一居室--> <one-bedroom> <!--將傢俱放到房間裏面,組件內部就是上面提供的默認插槽的空間--> <span>先放一個小牀,反正沒有女友</span> <span>再放一個電腦桌,在家還要加班寫bug</span> </one-bedroom></template><script>import OneBedroom from '../components/one-bedroom'export default { components: { OneBedroom }}</script>
具名插槽
1234567891011121314
<template> <div class="two-bedroom"> <!--這是主臥--> <div class="master-bedroom"> <!---主臥使用默認插槽--> <slot></slot> </div> <!--這是次臥--> <div class="secondary-bedroom"> <!--次臥使用具名插槽--> <slot name="secondard"></slot> </div> </div></template>
12345678910111213141516171819202122232425
<template> <two-bedroom> <!--主臥使用默認插槽--> <div> <span>放一個大牀,要結婚了,嘿嘿嘿</span> <span>放一個衣櫃,老婆的衣服太多了</span> <span>算了,仍是放一個電腦桌吧,還要寫bug</span> </div> <!--次臥,經過v-slot:secondard 能夠指定使用哪個具名插槽, v-slot:secondard 也能夠簡寫爲 #secondard--> <template v-slot:secondard> <div> < www.uuedzc.cn>父母要住,放一個硬一點的牀,軟牀對腰很差</span> <span>放一個衣櫃</span> </div> </template> </two-bedroom></template><script>import TwoBedroom from '../components/slot/two-bedroom'export default { components: { TwoBedroom }}</script>
做用域插槽
123456789
<template> <div class="two-bedroom"www.tengyao3zc.cn > <!--其餘內容省略--> <div class="toilet"> <!--經過v-bind 能夠向外傳遞參數, 告訴外面衛生間能夠放洗衣機--> <slot name="toilet" v-bind="{ washer: true }"></slot> </div> </div></template>
12345678910
<template> <two-bedroom> <!--其餘省略--> <!--衛生間插槽,經過v-slot="scope"能夠獲取組件內部經過v-bind傳的值--> <template v-slot:toilet="scope"> <!--判斷是否能夠放洗衣機--> <span v-if="scope.washer">這裏放洗衣機</span> </template> </two-bedroom>www.qiaoheibpt.com</template>
插槽默認值
123456789101112131415161718
<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>
12345678
<second-hand-house> <!--主臥使用默認插槽,只裝修主臥--> <div> <span>放一個大牀,要結婚了,嘿嘿嘿</span> <span>放一個衣櫃,老婆的衣服太多了</span> www.baihuayllpt.cn <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. 代碼實現
1234567891011121314151617181920212223242526272829
// 向上傳播事件 // @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向上觸發事件
10import emitter from '../mixins/emitter'
export default {
name: www.tianhuoyl.cn'Chart',
// 經過混入將$dispatch加入進來
mixins: [emitter],
mounted(www.jintianxuesha.com) {
// 在組件渲染完以後,將組件經過$dispatch將本身註冊到Board組件上
this.$dispatch(www.yixingylzc.cn'register', 'Board', this)
}
}
在Board組件上經過$on監聽要註冊的事件
$broadcast實現與應用
1. 代碼實現
12345678910111213141516171819202122
//向下傳播事件 // @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向下觸發事件
123456789101112
import emitter from '../mixins/emitter'export default { name: 'Board', // 經過混入將$dispatch加入進來 mixins: [emitter], methods:{ //在須要的時候,刷新組件 $_refreshChildren(params) { this.$broadcast('refresh', 'Chart', params) } }}
在後代組件中經過$on監聽刷新事件
12345678
export default { name: 'Chart', created() { this.$on('refresh',(params) => { // 刷新事件 }) }}
總結
經過上面的例子,同窗們應該都能對$dispatch和$broadcast有所瞭解,可是爲何Vue2.0要放棄這兩個方法呢?官方給出的解釋是:」由於基於組件樹結構的事件流方式實在是讓人難以理解,而且在組件結構擴展的過程當中會變得愈來愈脆弱。這種事件方式確實不太好,咱們也不但願在之後讓開發者們太痛苦。而且 $dispatch 和 $broadcast 也沒有解決兄弟組件間的通訊問題。「
確實如官網所說,這種事件流的方式確實不容易讓人理解,並且後期維護成本比較高。可是在小編看來,無論黑貓白貓,能抓老鼠的都是好貓,在許多特定的業務場景中,由於業務的複雜性,頗有可能使用到這樣的通訊方式。可是使用歸使用,可是不能濫用,小編一直就在項目中有使用。vue