1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        「不容錯過」34條 Vue 高質量實戰(zhàn)技巧

        共 21492字,需瀏覽 43分鐘

         ·

        2020-10-08 20:18

        這是我學習整理的關于 Vue.js 系列文章的第一篇,另外還有兩篇分別是關于優(yōu)化和原理的。希望讀完這3篇文章,你能對 Vue 有個更深入的認識。

        7種組件通信方式隨你選

        組件通信是 Vue 的核心知識,掌握這幾個知識點,面試開發(fā)一點問題都沒有。

        props/@on+$emit

        用于實現(xiàn)父子組件間通信。通過 props 可以把父組件的消息傳遞給子組件:

        //?parent.vue????
        <child?:title="title">child>
        //?child.vue
        props:?{
        ????title:?{
        ????????type:?String,
        ????????default:?'',
        ????}
        }

        這樣一來 this.title 就直接拿到從父組件中傳過來的 title 的值了。注意,你不應該在子組件內部直接改變 prop,這里就不多贅述,可以直接看官網介紹。

        而通過 @on+$emit 組合可以實現(xiàn)子組件給父組件傳遞信息:

        //?parent.vue
        <child?@changeTitle="changeTitle">child>
        //?child.vue
        this.$emit('changeTitle',?'bubuzou.com')

        listeners

        Vue_2.4 中新增的 $attrs/$listeners 可以進行跨級的組件通信。$attrs 包含了父級作用域中不作為 prop 的屬性綁定(classstyle 除外),好像聽起來有些不好理解?沒事,看下代碼就知道是什么意思了:

        //?父組件?index.vue
        <list?class="list-box"?title="標題"?desc="描述"?:list="list">list>
        //?子組件?list.vue
        props:?{
        ????list:?[],
        },
        mounted()?{
        ????console.log(this.$attrs)??//?{title:?"標題",?desc:?"描述"}
        }

        在上面的父組件 index.vue 中我們給子組件 list.vue 傳遞了4個參數(shù),但是在子組件內部 props 里只定義了一個 list,那么此時 this.$attrs 的值是什么呢?首先要去除 props 中已經綁定了的,然后再去除 classstyle,最后剩下 titledesc 結果和打印的是一致的?;谏厦娲a的基礎上,我們在給 list.vue 中加一個子組件:

        // 子組件 list.vue

        //?孫子組件?detail.vue
        //?不定義props,直接打印?$attrs
        mounted()?{
        ????console.log(this.$attrs)??//?{title:?"標題",?desc:?"描述"}
        }

        在子組件中我們定義了一個 v-bind="$attrs" 可以把父級傳過來的參數(shù),去除 props、classstyle 之后剩下的繼續(xù)往下級傳遞,這樣就實現(xiàn)了跨級的組件通信。

        $attrs 是可以進行跨級的參數(shù)傳遞,實現(xiàn)父到子的通信;同樣的,通過 $listeners 用類似的操作方式可以進行跨級的事件傳遞,實現(xiàn)子到父的通信。$listeners 包含了父作用域中不含 .native 修飾的 v-on 事件監(jiān)聽器,通過 v-on="$listeners" 傳遞到子組件內部。

        //?父組件?index.vue
        <list?@change="change"?@update.native="update">list>

        //?子組件?list.vue
        <detail?v-on="$listeners">detail>
        //?孫子組件?detail.vue
        mounted()?{
        ????this.$listeners.change()
        ????this.$listeners.update()?//?TypeError:?this.$listeners.update?is?not?a?function
        }

        provide/inject組合拳

        provide/inject 組合以允許一個祖先組件向其所有子孫后代注入一個依賴,可以注入屬性和方法,從而實現(xiàn)跨級父子組件通信。在開發(fā)高階組件和組件庫的時候尤其好用。

        //?父組件?index.vue
        data()?{
        ????return?{
        ????????title:?'bubuzou.com',
        ????}
        }
        provide()?{
        ????return?{
        ????????detail:?{
        ????????????title:?this.title,
        ????????????change:?(val)?=>?{
        ????????????????console.log(?val?)
        ????????????}
        ????????}
        ????}
        }

        //?孫子組件?detail.vue
        inject:?['detail'],
        mounted()?{
        ????console.log(this.detail.title)??//?bubuzou.com
        ????this.detail.title?=?'hello?world'??//?雖然值被改變了,但是父組件中?title?并不會重新渲染
        ????this.detail.change('改變后的值')??//?執(zhí)行這句后將打?。焊淖兒蟮闹?
        }
        ?

        provideinject 的綁定對于原始類型來說并不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監(jiān)聽的對象,那么其對象的 property 還是可響應的。這也就是為什么在孫子組件中改變了 title,但是父組件不會重新渲染的原因。

        ?

        EventBus

        以上三種方式都是只能從父到子方向或者子到父方向進行組件的通信,而我就比較牛逼了?,我還能進行兄弟組件之間的通信,甚至任意2個組件間通信。利用 Vue 實例實現(xiàn)一個 EventBus 進行信息的發(fā)布和訂閱,可以實現(xiàn)在任意2個組件之間通信。有兩種寫法都可以初始化一個 eventBus 對象:

        1. 通過導出一個 Vue 實例,然后再需要的地方引入:

          //?eventBus.js
          import?Vue?from?'vue'
          export?const?EventBus?=?new?Vue()

          使用 EventBus 訂閱和發(fā)布消息:

          import?{EventBus}?from?'../utils/eventBus.js'

          //?訂閱處
          EventBus.$on('update',?val?=>?{})

          //?發(fā)布處
          EventBus.$emit('update',?'更新信息')
        2. main.js 中初始化一個全局的事件總線:

          //?main.js
          Vue.prototype.$eventBus?=?new?Vue()

          使用:

          //?需要訂閱的地方
          this.$eventBus.$on('update',?val?=>?{})

          //?需要發(fā)布信息的地方
          this.$eventBus.$emit('update',?'更新信息')

        如果想要移除事件監(jiān)聽,可以這樣來:

        this.$eventBus.$off('update',?{})

        上面介紹了兩種寫法,推薦使用第二種全局定義的方式,可以避免在多處導入 EventBus 對象。這種組件通信方式只要訂閱和發(fā)布的順序得當,且事件名稱保持唯一性,理論上可以在任何 2 個組件之間進行通信,相當?shù)膹姶蟆5欠椒m好,可不要濫用,建議只用于簡單、少量業(yè)務的項目中,如果在一個大型繁雜的項目中無休止的使用該方法,將會導致項目難以維護。

        Vuex進行全局的數(shù)據(jù)管理

        Vuex 是一個專門服務于 Vue.js 應用的狀態(tài)管理工具。適用于中大型應用。Vuex 中有一些專有概念需要先了解下:

        • State:用于數(shù)據(jù)的存儲,是 store 中的唯一數(shù)據(jù)源;
        • Getter:類似于計算屬性,就是對 State 中的數(shù)據(jù)進行二次的處理,比如篩選和對多個數(shù)據(jù)進行求值等;
        • Mutation:類似事件,是改變 Store 中數(shù)據(jù)的唯一途徑,只能進行同步操作;
        • Action:類似 Mutation,通過提交 Mutation 來改變數(shù)據(jù),而不直接操作 State,可以進行異步操作;
        • Module:當業(yè)務復雜的時候,可以把 store 分成多個模塊,便于維護;

        對于這幾個概念有各種對應的 map 輔助函數(shù)用來簡化操作,比如 mapState,如下三種寫法其實是一個意思,都是為了從 state 中獲取數(shù)據(jù),并且通過計算屬性返回給組件使用。

        computed:?{
        ????count()?{
        ????????return?this.$store.state.count
        ????},
        ????...mapState({
        ????????count:?state?=>?state.count
        ????}),
        ????...mapState(['count']),
        },

        又比如 mapMutations, 以下兩種函數(shù)的定義方式要實現(xiàn)的功能是一樣的,都是要提交一個 mutation 去改變 state 中的數(shù)據(jù):

        methods:?{
        ????increment()?{
        ????????this.$store.commit('increment')
        ????},
        ????...mapMutations(['increment']),
        }

        接下來就用一個極簡的例子來展示 Vuex 中任意2個組件間的狀態(tài)管理。1、 新建 store.js

        import?Vue?from?'vue'
        import?Vuex?from?'vuex'
        Vue.use(Vuex)
        ????
        export?default?new?Vuex.Store({
        ????state:?{
        ????????count:?0,
        ????},
        ????mutations:?{
        ????????increment(state)?{
        ????????????state.count++
        ????????},
        ????????decrement(state)?{
        ????????????state.count--
        ????????}
        ????},
        })

        2、 創(chuàng)建一個帶 storeVue 實例

        import?Vue?from?'vue'
        import?App?from?'./App.vue'
        import?router?from?'./router'
        import?store?from?'./utils/store'
        ????
        new?Vue({
        ????router,
        ????store,
        ????render:?h?=>?h(App)
        }).$mount('#app')

        3、 任意組件 A 實現(xiàn)點擊遞增

        <template>
        ????<p?@click="increment">click to increment:{{count}}p>
        template>
        <script>
        import?{mapState,?mapMutations}?from?'vuex'
        export?default?{
        ????computed:?{
        ????????...mapState(['count'])
        ????},
        ????methods:?{
        ????????...mapMutations(['increment'])
        ????},
        }
        script>

        4、 任意組件 B 實現(xiàn)點擊遞減

        <template>
        ????<p?@click="decrement">click to decrement:{{count}}p>
        template>
        <script>
        import?{mapState,?mapMutations}?from?'vuex'
        export?default?{
        ????computed:?{
        ????????...mapState(['count'])
        ????},
        ????methods:?{
        ????????...mapMutations(['decrement'])
        ????},
        }
        script>

        以上只是用最簡單的 vuex 配置去實現(xiàn)組件通信,當然真實項目中的配置肯定會更復雜,比如需要對 State 數(shù)據(jù)進行二次篩選會用到 Getter,然后如果需要異步的提交那么需要使用 Action,再比如如果模塊很多,可以將 store 分模塊進行狀態(tài)管理。對于 Vuex 更多復雜的操作還是建議去看Vuex 官方文檔,然后多寫例子。

        Vue.observable實現(xiàn)mini vuex

        這是一個 Vue2.6 中新增的 API,用來讓一個對象可以響應。我們可以利用這個特點來實現(xiàn)一個小型的狀態(tài)管理器。

        //?store.js
        import?Vue?from?'vue'
        ?
        export?const?state?=?Vue.observable({
        ????count:?0,
        })

        export?const?mutations?=?{
        ????increment()?{
        ????????state.count++
        ????}
        ????decrement()?{
        ????????state.count--
        ????}
        }
        //?parent.vue
        <template>
        ????<p>{{?count?}}p>
        template>
        <script>
        import?{?state?}?from?'../store'
        export?default?{
        ????computed:?{
        ????????count()?{
        ????????????return?state.count
        ????????}
        ????}
        }
        script>
        //?child.vue
        import??{?mutations?}?from?'../store'
        export?default?{
        ????methods:?{
        ????????handleClick()?{
        ????????????mutations.increment()
        ????????}
        ????}
        }

        children/root

        通過給子組件定義 ref 屬性可以使用 $refs 來直接操作子組件的方法和屬性。

        <child?ref="list">child>

        比如子組件有一個 getList 方法,可以通過如下方式進行調用,實現(xiàn)父到子的通信:

        this.$refs.list.getList()

        除了 $refs 外,其他3個都是自 Vue 實例創(chuàng)建后就會自動包含的屬性,使用和上面的類似。

        6類可以掌握的修飾符

        表單修飾符

        表單類的修飾符都是和 v-model 搭配使用的,比如:v-model.lazyv-model-trim 以及 v-model.number 等。

        • .lazy:對表單輸入的結果進行延遲響應,通常和 v-model 搭配使用。正常情況下在 input 里輸入內容會在 p 標簽里實時的展示出來,但是加上 .lazy 后則需要在輸入框失去焦點的時候才觸發(fā)響應。
          <input?type="text"?v-model.lazy="name"?/>
          <p>{{?name?}}p>
        • .trim:過濾輸入內容的首尾空格,這個和直接拿到字符串然后通過 str.trim() 去除字符串首尾空格是一個意思。
        • .number:如果輸入的第一個字符是數(shù)字,那就只能輸入數(shù)字,否則他輸入的就是普通字符串。

        事件修飾符

        Vue 的事件修飾符是專門為 v-on 設計的,可以這樣使用:@click.stop="handleClick",還能串聯(lián)使用:@click.stop.prevent="handleClick"。

        <div?@click="doDiv">
        ????click?div
        ????<p?@click="doP">click?pp>
        div>
        • .stop:阻止事件冒泡,和原生 event.stopPropagation() 是一樣的效果。如上代碼,當點擊 p 標簽的時候,div 上的點擊事件也會觸發(fā),加上 .stop 后事件就不會往父級傳遞,那父級的事件就不會觸發(fā)了。
        • .prevent:阻止默認事件,和原生的 event.preventDefault() 是一樣的效果。比如一個帶有 href 的鏈接上添加了點擊事件,那么事件觸發(fā)的時候也會觸發(fā)鏈接的跳轉,但是加上 .prevent 后就不會觸發(fā)鏈接跳轉了。
        • .capture:默認的事件流是:捕獲階段-目標階段-冒泡階段,即事件從最具體目標元素開始觸發(fā),然后往上冒泡。而加上 .capture 后則是反過來,外層元素先觸發(fā)事件,然后往深層傳遞。
        • .self:只觸發(fā)自身的事件,不會傳遞到父級,和 .stop 的作用有點類似。
        • .once:只會觸發(fā)一次該事件。
        • .passive:當頁面滾動的時候就會一直觸發(fā) onScroll 事件,這個其實是存在性能問題的,尤其是在移動端,當給他加上 .passive 后觸發(fā)的就不會那么頻繁了。
        • .native:現(xiàn)在在組件上使用 v-on 只會監(jiān)聽自定義事件 (組件用 $emit 觸發(fā)的事件)。如果要監(jiān)聽根元素的原生事件,可以使用 .native 修飾符,比如如下的 el-input,如果不加 .native 當回車的時候就不會觸發(fā) search 函數(shù)。
          <el-input?type="text"?v-model="name"?@keyup.enter.native="search">el-input>
        ?

        串聯(lián)使用事件修飾符的時候,需要注意其順序,同樣2個修飾符進行串聯(lián)使用,順序不同,結果大不一樣。@click.prevent.self 會阻止所有的點擊事件,而 @click.self.prevent 只會阻止對自身元素的點擊。

        ?

        鼠標按鈕修飾符

        • .left:鼠標左鍵點擊;
        • .right:鼠標右鍵點擊;
        • .middle:鼠標中鍵點擊;

        鍵盤按鍵修飾符

        Vue 提供了一些常用的按鍵碼:

        • .enter
        • .tab
        • .delete (捕獲“刪除”和“退格”鍵)
        • .esc
        • .space
        • .up
        • .down
        • .left
        • .right

        另外,你也可以直接將 KeyboardEvent.key 暴露的任意有效按鍵名轉換為 kebab-case 來作為修飾符,比如可以通過如下的代碼來查看具體按鍵的鍵名是什么:

        <input?@keyup="onKeyUp">
        onKeyUp(event)?{
        ????console.log(event.key)??//?比如鍵盤的方向鍵向下就是?ArrowDown
        }

        .exact修飾符

        .exact 修飾符允許你控制由精確的系統(tǒng)修飾符組合觸發(fā)的事件。


        <button?v-on:click.ctrl="onClick">Abutton>


        <button?v-on:click.ctrl.exact="onCtrlClick">Abutton>


        <button?v-on:click.exact="onClick">Abutton>

        .sync修飾符

        .sync 修飾符常被用于子組件更新父組件數(shù)據(jù)。直接看下面的代碼:

        //?parent.vue
        <child?:title.sync="title">child>
        //?child.vue
        this.$emit('update:title',?'hello')

        子組件可以直接通過 update:title 的形式進行更新父組件中聲明了 .syncprop。上面父組件中的寫法其實是下面這種寫法的簡寫:

        <child?:title="title"?@update:title="title?=?$event">child>
        ?

        注意帶有 .sync 修飾符的 v-bind 不能和表達式一起使用

        ?

        如果需要設置多個 prop,比如:

        <child?:name.sync="name"?:age.sync="age"?:sex.sync="sex">child>

        可以通過 v-bind.sync 簡寫成這樣:

        <child?v-bind.sync="person">child>
        person:?{
        ????name:?'bubuzou',
        ????age:?21,
        ????sex:?'male',
        }

        Vue 內部會自行進行解析把 person 對象里的每個屬性都作為獨立的 prop 傳遞進去,各自添加用于更新的 v-on 監(jiān)聽器。而從子組件進行更新的時候還是保持不變,比如:

        this.$emit('update:name',?'hello')

        6種方式編寫可復用模塊

        今天需求評審了一個需求,需要實現(xiàn)一個詳情頁,這個詳情頁普通用戶和管理員都能進去,但是展示的數(shù)據(jù)有稍有不同,但絕大部分是一樣的;最主要的區(qū)別是詳情對于普通用戶是純展示,而對于管理員要求能夠編輯,然后管理員還有一些別的按鈕權限等。需求看到這里,如果在排期的時候把用戶的詳情分給開發(fā)A做,而把管理員的詳情分給B去做,那這樣做的結果就是開發(fā)A寫了一個詳情頁,開發(fā)B寫了一個詳情頁,這在開發(fā)階段、提測后的修改 bug 階段以及后期迭代階段,都需要同時維護這 2 個文件,浪費了時間浪費了人力,所以你可以從中意識到編寫可復用模塊的重要性。

        Vue 作者尤大為了讓開發(fā)者更好的編寫可復用模塊,提供了很多的手段,比如:組件、自定義指令、渲染函數(shù)、插件以及過濾器等。

        組件

        組件是 Vue 中最精髓的地方,也是我們平時編寫可復用模塊最常用的手段,但是由于這塊內容篇幅很多,所以不在這里展開,后續(xù)會寫相關的內容進行詳述。

        使用混入mixins

        什么是混入呢?從代碼結構上來看,混入其實就是半個組件,一個 Vue 組件可以包括 template、scriptstyle 三部分,而混入其實就是 script 里面的內容。一個混入對象包含任意組件選項,比如 data、methods、computed、watch 、生命周期鉤子函數(shù)、甚至是 mixins 自己等,混入被設計出來就是旨在提高代碼的靈活性、可復用性。

        什么時候應該使用混入呢?當可復用邏輯只是 JS 代碼層面的,而無 template 的時候就可以考慮用混入了。比如需要記錄用戶在頁面的停留的時間,那我們就可以把這段邏輯抽出來放在 mixins 里:

        //?mixins.js
        export?const?statMixin?=?{
        ????methods:?{
        ????????enterPage()?{},
        ????????leavePage()?{},
        ????},
        ????mounted()?{
        ????????this.enterPage()
        ????},
        ????beforeDestroyed()?{
        ????????this.leavePage()
        ????}
        }

        然后在需要統(tǒng)計頁面停留時間的地方加上:


        import?{?statMixin?}?from?'../common/mixins'
        export?default?{
        ????mixins:?[statMixin]
        }

        使用混入的時候要注意和組件選項的合并規(guī)則,可以分為如下三類:

        • data 將進行遞歸合并,對于鍵名沖突的以組件數(shù)據(jù)為準:

          //?mixinA?的?data
          data()?{
          ????obj:?{
          ????????name:?'bubuzou',
          ????},
          }

          //?component?A
          export?default?{
          ????mixins:?[mixinA],
          ????data(){
          ????????obj:?{
          ????????????name:?'hello',
          ????????????age:?21
          ????????},
          ????},
          ????mounted()?{
          ????????console.log(?this.obj?)??//?{?name:?'bubuzou',?'age':?21?}????
          ????}
          }
        • 對于生命周期鉤子函數(shù)將會合并成一個數(shù)組,混入對象的鉤子將先被執(zhí)行:

          //?mixin?A
          const?mixinA?=?{
          ????created()?{
          ????????console.log(?'第一個執(zhí)行'?)
          ????}
          }

          //?mixin?B
          const?mixinB?=?{
          ????mixins:?[mixinA]
          ????created()?{
          ????????console.log(?'第二個執(zhí)行'?)
          ????}
          }

          //?component?A
          export?default?{
          ????mixins:?[mixinB]
          ????created()?{
          ????????console.log(?'最后一個執(zhí)行'?)
          ????}
          }
        • 值為對象的選項,例如 methods、componentsdirectives,將被合并為同一個對象。兩個對象鍵名沖突時,取組件對象的鍵值對。

        自定義指令

        除了 Vue 內置的一些指令比如 v-modelv-if 等,Vue 還允許我們自定義指令。在 Vue2.0 中,代碼復用和抽象的主要形式是組件。然而,有的情況下,你仍然需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令。比如我們可以通過自定義一個指令來控制按鈕的權限。我們期望設計一個如下形式的指令來控制按鈕權限:

        <button?v-auth="['user']">提交button>

        通過在按鈕的指令里傳入一組權限,如果該按鈕只有 admin 權限才可以提交,而我們傳入一個別的權限,比如 user,那這個按鈕就不應該顯示了。接下來我們去注冊一個全局的指令:

        //?auth.js
        const?AUTH_LIST?=?['admin']

        function?checkAuth(auths)?{
        ????return?AUTH_LIST.some(item?=>?auths.includes(item))
        }

        function?install(Vue,?options?=?{})?{
        ????Vue.directive('auth',?{
        ????????inserted(el,?binding)?{
        ????????????if?(!checkAuth(binding.value))?{
        ????????????????el.parentNode?&&?el.parentNode.removeChild(el)
        ????????????}
        ????????}
        ????})
        }

        export?default?{?install?}

        然后我們需要在 main.js 里通過安裝插件的方式來啟用這個指令:

        import?Auth?from?'./utils/auth'
        Vue.use(Auth)

        使用渲染函數(shù)

        這里將使用渲染函數(shù)實現(xiàn)上面介紹過的的權限按鈕。使用方式如下,把需要控制權限的按鈕包在權限組件 authority 里面,如果有該權限就顯示,沒有就不顯示。

        <authority?:auth="['admin']">
        ????<button>提交button>
        authority>

        然后我們用渲染函數(shù)去實現(xiàn)一個 authority 組件:

        
        
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            美女操穴 | 黄色软件下载安装 | 亚洲综合在线视频 | 成人A片在线视频 | A一级毛片录像带录像片 | 黑人日亚洲美女 | 国产美女一级视频 | 就去艹| 欧美日韩国产不卡视频 | 久色在线 |