Vue項目開發中一些常用實用的小技巧:
場景 1:開發vue項目的時候我們寫了一堆基礎UI元件,然後每次我們需要使用這些元件的時候,都得先import,然後聲明components注冊這些元件,這樣的話如果我們封裝了50個元件,那麼就意味我們要寫50個相同的元件引入語句,50個注冊語句,大量的重複代碼:
main.ts
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store/index';
import AppHeader from '@/components/app-header/app-header.vue';
import AppButton from '@/components/app-button/app-button.vue';
import AppModal from '@/components/app-modal/app-modal.vue';
import AppInput from '@/components/app-input/app-input.vue';
Vue.component('app-header', AppHeader);
Vue.component('app-button', AppButton);
Vue.component('app-modal', AppModal);
Vue.component('app-input', AppInput);
【優化解析】:如果遇到從一個檔案夾引入很多子產品的情況,我們可以借助一下神器webpack的一個api→ require.context() 方法來建立自己的(子產品)上下文,它會周遊檔案夾中的指定檔案,然後自動導入,進而實作自動動态require元件。
文法:
/**
* directory {String} -需要檢索的檔案目錄
* useSubdirectories {Boolean} -是否周遊檔案的子目錄
* regExp {RegExp} - 比對檔案的正則
**/
require.context(directory, useSubdirectories = false, regExp = /^.//);
【使用】
(1)我們在components檔案夾添加一個叫global.ts的檔案,在這個檔案裡借助webpack動态将需要的基礎元件統統打包進來。
import Vue from 'vue'
export function capitalizeFirstLetter(val: string) {
return val.charAt(0).toUpperCase() + val.slice(1)
}
//找到components檔案夾下以.vue命名的檔案
export const files = require.context('@/components', true, /\.vue$/);
this.getComponents();
// 針對3中不同的元件設計模式提供不同的元件注冊政策
export function getComponents() {
this.files.keys().forEach(path => {
const type = this.getType(path, this.files(path));
switch (type) {
case 'component':
this.installComonent(Vue, this.files(path));
break;
case 'componentGroup':
this.instalGroupComponent(Vue, this.files(path));
break;
case 'serveApi':
this.installServeApi(Vue, this.files(path));
break;
}
});
}
getType(path, file) {
if (path.lastIndexOf('.ts') === -1) {
return 'component';
} else {
if (file.default.install) {
return 'serveApi';
} else {
return 'componentGroup';
}
}
}
instalGroupComponent(Vue, file) {
const components = file.default;
Object.keys(components).forEach(name => {
Vue.component('Nb' + name, components[name]);
});
}
installComonent(Vue, file) {
const options = file.default;
const name = options.name;
Vue.component('Nb' + name, options);
}
installServeApi(Vue, file) {
Vue.use(file.default);
}
// 最簡單的使用
this.files.keys().forEach(fileName => {
const componentConfig = this.files(fileName)
const componentName = capitalizeFirstLetter(
fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
//因為得到的filename格式是: './baseButton.vue', 是以這裡我們去掉頭和尾,隻保留真正的檔案名
)
Vue.component(componentName, componentConfig.default || componentConfig)
})
(2)最後我們在main.ts中import 'components/global.js',然後我們就可以随時随地使用這些基礎元件,無需手動引入了。
場景 2:元件在建立的時候我們會通過接口擷取一次清單,同時監聽input框,每當發生變化的時候重新擷取一次清單。
【解析】可以使用@Watch() 中的immediate屬性 立即執行
@Watch
接受第一個參數為要監聽的屬性名 第二個屬性為可選對象。
@Watch
所裝飾的函數即監聽到屬性變化之後的操作.
有時候我們可能需要監聽整個對象,例如我們針對一個對象中的某個屬性進行監聽,但是這個對象中有很多屬性,任何一個屬性的改變我們都需要重新發起請求,那麼我們可以利用watch的deep屬性進行深度監聽,也就是監聽複雜的資料類型。
immediate:true表示建立元件時立馬執行一次。
import {Vue, Component, Watch} from 'vue-property-decorator';
可以直接利用 watch 的immediate和handler屬性簡寫
@Watch('nameInput', { immediate: true, deep: true })
getData(newVal: Person, oldVal: Person){
// todo...
}
場景 3:在使用
Vue
進行開發時,我們經常看到有些元件有些重複的 js 邏輯,這部分邏輯我們可以使用mixins(混合)來實作,有兩種
mixins
的方法。
(1)是
vue-class-component
提供的
//定義要混合的類 mixins.ts
import Vue from 'vue';
import Component from 'vue-class-component';
@Component // 一定要用Component修飾
export default class myMixins extends Vue {
value: string = "Hello"
}
___________________________________________________________
// 引入
import Component {mixins} from 'vue-class-component';
import myMixins from 'mixins.ts';
@Component
export class myComponent extends mixins(myMixins) {
// 直接extends myMinxins 也可以正常運作
created(){
console.log(this.value) // => Hello
}
}
(2)是在
@Component
中混入
// 定義 mixins.ts
import { Vue, Component } from 'vue-property-decorator';
declare module 'vue/types/vue' {
interface Vue {
value: string;
}
}
@Component
export default class myMixins extends Vue {
value: string = 'Hello'
}
_________________________________________________________________
//引入mixin
import { Vue, Component, Prop } from 'vue-property-decorator';
import myMixins from '@static/js/mixins';
@Component({
mixins: [myMixins]
})
export default class myComponent extends Vue{
created(){
console.log(this.value) // => Hello
}
}
場景 3: 多個過濾器全局使用
// 多個過濾器全局注冊
//定義filter.ts //src/common/filters.ts
let dateFormat = value => value.replace(/(\d{4})(\d{2})(\d{2})/g, '$1-$2-$3')
export { dateFormat }
//使用 /src/main.ts
import * as custom from './common/filters/custom'
Object.keys(custom).forEach(key => Vue.filter(key, custom[key]))
場景 4: 在使用Vue開發項目中我們經常用scoped屬性表示它的樣式隻作用于目前子產品,是樣式私有化。
scoped渲染的規則/原理:
給HTML的DOM節點添加一個 不重複的data屬性 來表示 唯一性;
在對應的 CSS選擇器 末尾添加一個目前元件的 data屬性選擇器來私有化樣式。
【問題】:使用了scoped屬性後,我們元件内部的樣式無法在外部被控制,(怎麼都改不了樣式)
【解決方案】:deep屬性
.result-tab /deep/ {
.nav-bar {
a {
flex: initial;
height: 50px;
font-size: 36px;
margin-left: 40px;
line-height: 100px;
height: auto;
}
}
.result-content {
padding: 0 40px;
}
}
場景 5:Modal彈框大體結構固定,隻是中間内容有所變化,這種情況下就可以用到插槽。
通過slot可以向元件内部指定位置插入内容,通過slot可以實作父子傳參
【使用】:
子元件:
<template>
<div class="h-100">
<div class="custom-modal" tabindex="-1">
<div class="custom-modal-dialog custom-modal-dialog-margin upload-modal">
<div class="custom-modal-content">
<div class="modal-body d-flex flex-column px-3 py-4">
<div class="modal-title mb-2 relative w-100">
<h5>{{ modalTitle | translate }}</h5>
<span class="clickable modal-close" @click="closeModal($event)">
<i class="gr gr-close gr-1x"></i>
</span>
</div>
<div class="modal-content">
// 作用域插槽,子元件将該user屬性傳遞給父元件使用
<slot name="modal-content" :user="user"></slot>
</div>
</div>
</div>
</div>
</div>
<div class="custom-modal-backdrop"></div>
</div>
</template>
<script >
import { Component, Vue, Prop, Emit } from 'vue-property-decorator';
import ErrorToast from '@/utils/toast';
@Component({
components: {}
})
export default class AppModal extends Vue {
@Prop() modalTitle!: string;
// 作用域插槽,子元件将該屬性傳遞給父元件使用
user: any = {
name: 'jenny',
jobTitle: 'CTO'
}
closeModal() {
this.$ionic.modalController.dismiss();
}
}
</script>
// 父元件
<template>
<div class="h-100">
<app-modal :modalTitle="'common.tip'">
// 父元件使用子元件傳過來的user屬性, slotProps可以為任意名
<div slot="modal-content" slot-scope="slotProps">
{{ slotProps.user.name }}
{{ slotProps.user.jobTitle }}
<div class="mb-2">
<p>{{ 'my.item.logout.msg' | translate}}</p>
<ion-button
class="float-right border-radius-4"
@click="logout"
>{{ 'common.confirm' | translate }}</ion-button>
</div>
</div>
</app-modal>
</div>
</template>
場景 6:比如一個需求是從/post-page/a,跳轉到/post-page/b。然後我們驚人的發現,頁面跳轉後資料竟然沒更新?!
【原因】vue-router”智能地”發現這是同一個元件,然後它就決定要複用這個元件,是以你在created函數裡寫的方法壓根就沒執行。通常的解決方案是監聽$route的變化來初始化資料。
【優化】可以給router-view添加一個unique的key,這樣即使是公用元件,隻要url變化了,就一定會重新建立這個元件。(雖然損失了一丢丢性能,但避免了無限的bug)。同時,注意我将key直接設定為路由的完整路徑,一舉兩得。
<router-view :key="$route.fullpath"></router-view>
場景 7:實作動态換膚(比如 預設情況css會傳遞一個預設主題色給js, 當将使用者選擇某個主題色時再傳給css)
js将變量值傳給scss, 可以使用
css var()
實作
:root {
--main-bg-color: coral;
}
#div1 {
background-color: var(--main-bg-color);
}
#div2 {
background-color: var(--main-bg-color);
}
scss變量傳給js(可以通過 css-modules
:export
來實作)
// variable.scss
$theme: blue;
:export {
theme: $theme;
}
// test.ts
import variable from '@/styles/variable.scss'
console.log(variable.theme) // blue
場景 8:平時寫業務的時候,免不了會對第三方元件進行二次封裝,比如我們基于el-input元件進行封裝,這樣的話需要傳入很多個@Prop 或者傳多個$Emit
【優化】我們可以使用
v-bind="$attrs"
:傳遞所有屬性;
v-on="$listeners"
傳遞所有方法
場景 9:當需要在子元件中修改父元件值的時候可以用(.sync)
<base-item :data.sync="itemData"></base-item>
等同于以下:
<base-item :data="itemData" @update:data="val => itemData = val"></base-item>
this.$emit('update:data', newVal)
場景 10: 避免一些全局操作:
(1)在元件内選擇節點的時候一定要切記避免使用 document.querySelector()等一系列的全局選擇器。
應該使用this.$el或者this.refs.xxx.$el的方式來選擇 DOM。
這樣就能将你的操作局限在目前的元件内,能避免很多問題。
(2)經常會不可避免的需要注冊一些全局性的事件,
比如監聽頁面視窗的變化 window.addEventListener('resize', this.__resizeHandler),
但再聲明了之後一定要在 beforeDestroy或者destroyed生命周期登出它。
window.removeEventListener('resize', this.__resizeHandler)避免造成不必要的消耗。
(3)避免過多的全局狀态,不是所有的狀态都需要存在 vuex 中的,應該根據業務進行合理的進行取舍。
如果不可避免有很多的值需要存在 vuex 中,建議使用動态注冊的方式。
隻是部分業務需要的狀态處理,建議使用 Event Bus或者使用 簡單的 store 模式。
(4)css 也應該盡量避免寫太多的全局性的樣式。除了一些全局公用的樣式外,是以針對業務的或者元件的
樣式都應該使用命名空間的方式或者直接使用 vue-loader 提供的 scoped寫法,避免一些全局沖突。
場景 11:元件中注冊一些全局事件的時候,都需要在
mounted
中聲明,在
destroyed
中銷毀,但由于這個是寫在兩個生命周期内的,很容易忘記,如果你在元件摧毀階段忘記移除的話,會造成記憶體的洩漏,而且都不太容易發現。
【解決】使用Hook
mounted() {
window.addEventListener('resize', this.handleResize())
}
beforeDestory() {
window.removeEventListener('resize', this.handleResize())
}
修改後:
mounted() {
window.addEventListener('resize', this.handleResize());
this.$on('hook:destroyed', () => {
window.removeEventListener('resize', this.handleResize())
})
}