天天看點

分享一些Vue中實用的小技巧

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())
    })
}