在 Vue 項目開發中,很容易產生一些問題,好比代碼重複、繁雜等,其實 Vue 項目開發中有不少技巧可使用,本文將列出一些簡單且很好用的幾個技巧,幫助咱們寫出漂亮的代碼。用到的技術棧是 Vue2.0 + TypeScript + vue-property-decorator + ElementUI。將用到如下幾個技巧:javascript
$attrs
和 $listeners
進行多層級的數據和事件傳遞$attrs
和 $listeners
進行多層級的數據和事件傳遞先聊聊如何傳遞 Prop,能夠分爲靜態和動態的 Prop:css
<!-- 靜態的prop -->
<blog-post title="My journey with Vue"></blog-post>
<!-- 動態的prop -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 動態的prop傳遞能夠簡寫成 -->
<blog-post :title="post.title"></blog-post>
<!-- 須要傳遞多個prop的時候,能夠一塊兒寫在v-bind上 -->
<blog-post v-bind="{ editable, title: post.title}"></blog-post>
複製代碼
瞭解了 Props 的傳遞方式,在看看官方文檔是怎麼定義 $attrs
的, 在尤大大的文檔中這樣介紹了 $attrs
:html
$attrs
: 包含了父做用域中不做爲 prop 被識別 (且獲取) 的 attribute 綁定 class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 (class 和 style 除外),而且能夠經過v-bind="$attrs"
傳入內部組件vue
$attrs
包含了傳入到父做用域中沒有在 props 聲明的其餘 props,所以咱們能夠用 $attrs
去代替那些父組件中不須要的而子組件須要的 props, 經過 v-bind="$attrs"
統一傳遞給後代。這樣就避免了一個個聲明而後再一個個傳遞。java
<blog-post v-bind="$attrs"></blog-post>
複製代碼
上面這一行代碼就經過 v-bind="$attrs"
的方式將本做用域中不做爲 prop 的其餘屬性傳遞給了 blog-post 組件。webpack
父組件經過 $attrs
傳遞給後代組件後,後代組件若是想經過觸發事件來更新父組件狀態該如何處理?若是一級一級地往上 emit 事件,會不會弄得代碼太繁瑣複雜了?在 Vue 中能夠經過 $listeners
解決這個問題,先看看官方文檔關於 $listeners
的說明:web
包含了父做用域中的 (不含
.native
修飾器的)v-on
事件監聽器。它能夠經過v-on="$listeners"
傳入內部組件——在建立更高層次的組件時很是有用。正則表達式
文檔中說了 $listeners
包含了父做用域中的事件監聽器。意思就是 $listeners
表示了父組件中的事件監聽器集合,只要是觸發父組件的事件,而不是本身的,就能夠用一個 v-on="$listeners"
表示。markdown
<!-- 父組件(第一層組件) -->
<componentA @on-change="handleChange" v-bind="{ editable, title: post.title}" />
<!-- 中間層的組件 -->
<Child v-bind="$attrs" v-on="$listeners"/>
<!-- 數據傳遞的目標組件,事件觸發的組件 -->
<div @click="handleClick">{{ title }} </div>
<script> export default { props: { title: String } handleClick() { this.$emit('on-change', 'New Title'); } } </script>
複製代碼
上面的代碼示例中,中間層的組件內經過 v-bind="$attrs"
將其他的 Prop 傳遞給了 Child 組件,再經過 v-on="$listeners"
綁定父做用域中的事件監聽器,一旦 emit 就會傳給了父組件。ide
有不少這樣的場景,父組件須要傳遞數據給子組件,且在子組件觸發數據更新的時候,立刻反饋給父組件,父組件數據更新,單向數據流向子組件,最後子組件更新。一般狀況用 props + $emit
的方式去更新狀態,可是這種處理方式有點笨拙,且不易維護,因此能夠經過實現數據的「雙向綁定」來提升代碼的可維護性。能夠經過這如下方式去實現:
在 v-bind prop
的時候添加 .sync 修飾符,賦新值的時候用 this.$emit('update:propName', newValue)
<!-- .sync是 v-on:update這種模式的一種縮寫 -->
<Child v-on:update:title="title" />
<!-- 至關於 -->
<Child :title.sync="title" />
複製代碼
若是要更新上述代碼中的 title 值,只須要 this.$emit('update:title', '新標題')
,完成數據更新。
model 是2.2.0+ 新增的選項,一個組件上的 v-model 默認會利用名爲 value 的 Prop 和名爲 input 的事件, 而 model 選項能夠規定 Prop 名稱和事件名稱來實現 v-model,好處是在實現 v-model 的同時也避免了 Prop 和事件名的衝突。
<!-- 父組件 -->
<Model v-model="checked"/>
<!-- Model組件 -->
<div @click="handleClick">
<p>自定義組件的 v-model</p>
checked {{checked}}
</div>
<script lang="ts"> export default { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, methods: { handleClick() { this.$emit('change', !this.checked); } } 複製代碼
在上述代碼中,只須要在 model 選項中添加 prop 和 event,就能夠實現了 v-model。而在 Vue + TS 項目中 vue-property-decorator 中提供了 Model 的裝飾器,須要這麼寫:
@Model('change', { type: Boolean }) readonly checked!: boolean
handleClick() {
this.$emit('change', !this.checked);
}
複製代碼
只須要經過 .sync 和 model 就能夠實現數據的「雙向綁定」,這樣書寫代碼能夠必定程度上減小咱們的代碼,並且另代碼變得更優雅且可維護。
Mixins 能夠用於兩種場景:
首先寫一個公共的 mixin 文件, 把高複用的狀態和函數寫進去。
export default class CommonMixins extends Vue{
public paginations = {
pageSize: 20,
total: 0,
currentPage: 1,
}
handleChangePageSize (pageSize: number, cb: Function) {
this.paginations.pageSize = pageSize;
cb();
}
handleChangePageNum (currentPage: number, cb: Function) {
this.paginations.currentPage = currentPage;
cb();
}
}
複製代碼
vue-property-decorator 提供了 Mixins 的裝飾器,在業務頁面中引入 Mixin 只須要往裏 Mixins 傳入 , 能夠傳多個,表示混入多個 Mixin。
<script lang="ts">
import { Component, Mixins } from 'vue-property-decorator';
import CommonMixins from "./common-mixin";
import PermissionMixins from "./permission-mixin";
@Component({})
export default class Parent extends Mixins(CommonMixins, PermissionMixins) {
}
</script>
複製代碼
若是隻須要一個的話,也能夠直接繼承
<script lang="ts"> import { Component, Mixins } from 'vue-property-decorator'; import CommonMixins from "./common-mixin"; @Component({}) export default class Parent extends CommonMixins { } </script>
複製代碼
在遇到功能點多,代碼量大的頁面時,咱們能夠利用 Mixin 抽離一些功能點,經過文件去管理這些功能,這樣會比較方便去管理代碼。
組件在加載都是同步的,但當頁面內容不少,有些組件並不須要一開始就加載出來的好比彈窗類的組件,這些就能夠用動態組件,當用戶執行了某些操做後再加載出來,這樣能夠提升主模塊加載的性能, 在 Vue 中可使用 component 動態組件, 依 is 的值,來決定哪一個組件被渲染。
<template>
<div>
主頁面 <br/>
<button @click="handleClick1">點擊記載組件1</button><br/>
<button @click="handleClick2">點擊記載組件2</button><br/>
<component :is="child1"></component>
<component :is="child2"></component>
</div>
</template>
<script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; @Component({}) export default class AsyncComponent extends Vue { public child1:Component = null; public child2:Component = null; handleClick1() { this.child1 = require('./child1').default; } handleClick2() { this.child2 = require('./child2').default; } } </script>
複製代碼
示例代碼中,只有當點擊的時候纔會去加載組件。component 還能夠配合 v-show
去控制顯示和隱藏,這樣這個component 只會 mounted 一次,優化性能。
::v-deep
修改組件樣式有不少場景想更改 UI 組件樣式,而後怕影響別人的使用,加上 scoped
後又不能生效,可使用 ::v-deep
深度做用選擇器去修改組件做用域內的 CSS
的樣式。在 CSS
中咱們可使用 >>>
操做符,但在預處理器中的寫法就要用 /deep/
或 ::v-deep
。
<style scoped>
>>> .ivu-tabs-tabpane {
background: #f1f1f1;
}
</style>
<style lang="scss" scoped>
/deep/ .ivu-tabs-tabpane {
background: #f1f1f1;
}
</style>
<style lang="scss" scoped>
::v-deep .ivu-tabs-tabpane {
background: #f1f1f1;
}
</style>
複製代碼
::v-deep
和 /deep/
做用是同樣的,但不推薦使用 /deep/
, 在 Vue3.0
中將不支持 /deep/
這種寫法。
裝飾器增長了代碼的可讀性,清晰地表達了意圖,並且提供一種方便的手段,增長或修改類的功能,好比給類其中的方法提供防抖的功能。
import debounce from 'lodash.debounce';
export function Debounce(delay: number, config: object = {}) {
return (target: any, prop: string) => {
return {
value: debounce(target[prop], delay, config),
};
};
}
複製代碼
這樣的好處是使用起來很是方便,另外增長了代碼的可讀性。
@Debounce(300)
onIdChange(val: string) {
this.$emit('idchange', val);
}
複製代碼
require.context
去獲取項目目錄信息關於 require.context
,webpack 文檔是這麼描述的:
能夠給這個函數傳入三個參數:一個要搜索的目錄,一個標記表示是否還搜索其子目錄, 以及一個匹配文件的正則表達式。
webpack 會在構建中解析代碼中的
require.context()
。若是想引入一個文件夾下面的全部文件,或者引入能匹配一個正則表達式的全部文件,這個功能就會頗有幫助
根據這個提示,咱們能夠引用到一個文件夾下面的全部文件,由此能夠利用獲取的文件信息去作一些操做,好比在註冊組件的時候,本來咱們註冊組件的時候須要一個個引入而且一個個註冊,並且後面想加新的,又要再寫上,如今能夠經過 require.context
去優化這一段代碼。
// import WmsTable from './wms-table/table/index';
import Table from './table/index.vue';
import CustomHooks from './custom-hooks/custom-hooks-actions/index';
import SFilter from './s-filter/filter-form';
import WButton from './button/index';
import CreateForm from './createForm/create-form/CreateForm.vue';
import Action from './table/action-table-column.vue';
import DetailItem from './detail-item.vue';
Vue.component('w-filter', SFilter);
Vue.component('w-button', WButton);
Vue.component('custom-hooks', CustomHooks);
Vue.component('create-form', CreateForm);
Vue.component('w-table', Table);
Vue.component('w-table-action', Action);
Vue.component('zonetime-date-picker', ZonetimeDatePicker);
Vue.component('detail', DetailItem);
複製代碼
註冊全局組件的時候,不須要一個一個 import,和一個個去註冊,使用 require.context
能夠自動導入模塊,這樣的好處在於,當咱們新建一個組件,不用本身再去手寫註冊,而在一開始就幫咱們自動完成。
const contexts = require.context('./', true, /\.(vue|ts)$/);
export default {
install (vm) {
contexts.keys().forEach(component => {
const componentEntity = contexts(component).default;
if (componentEntity.name) {
vm.component(componentEntity.name, componentEntity);
}
});
}
};
複製代碼
本文介紹了在 Vue 實戰中常常用到的一些技巧,這些技巧的目的都是爲了提高開發效率,好比簡單地實現雙向數據綁定和數據跨級傳遞等,另外也能夠提升代碼的可維護性、可讀性,好比很實用的裝飾器和利用 Mixin 拆分代碼和管理功能點。