Vue 開發技巧總結

​博客地址:https://ainyi.com/95

本人玩了 Vue 兩年多,在此總結一下開發時的一些技巧和方法

自定義組件 v-model

v-model 是 Vue 提供的一個語法糖,它本質上是由 value 屬性 + input 事件組成的(都是原生的默認屬性)
自定義組件中,可以通過傳遞 value 屬性並監聽 input 事件來實現數據的雙向綁定

自定義組件

<template>
  <div>
    <input :value="value" @input="$_handleInput" />
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: ''
    }
  },
  data() {
    return {}
  },
  methods: {
    $_handleInput(e) {
      this.$emit('input', e.target.value)
    }
  }
}
</script>

父組件調用

<template>
  <div class="home">
    <krry-input v-model="say"></krry-input>
  </div>
</template>

<script>
export default {
  name: 'Home',
  components: {
    KrryInput: () => import('@/components/KrryInput')
  },
  data() {
    return {
      say: 'haha'
    }
  }
}
</script>

函數式組件

簡單說一下函數式組件
函數式組件就是函數是組件。使用過 React 的同學,應該不會對函數式組件感到陌生
函數式組件,我們可以理解爲沒有內部狀態沒有生命週期鉤子函數沒有 this(不需要實例化的組件)
由於它像函數一樣輕巧,沒有實例引用,所以渲染性能提高了不少

在日常開發中,經常會開發一些純展示性的業務組件,比如一些詳情頁面,列表界面等,它們有一個共同的特點是隻需要將外部傳入的數據進行展現,不需要有內部狀態,不需要在生命週期鉤子函數裏面做處理,這時候你就可以考慮使用函數式組件

export default {
  // 通過配置 functional 屬性指定組件爲函數式組件
  functional: true,
  // 組件接收的外部屬性,也可無需顯式聲明 props
  props: {
    avatar: {
      type: String
    }
  },
  /**
   * 渲染函數
   * @param {*} h
   * @param {*} context 函數式組件沒有 this, props, slots 等,都在 context 上面掛着
   */
  render(h, context) {
    const { props } = context
    if (props.avatar) {
      return <img src={props.avatar}></img>
    }
    return <img src="default-avatar.png"></img>
  }
}

使用函數式組件的原因:

  1. 最主要最關鍵的原因是函數式組件不需要實例化,無狀態,沒有生命週期,所以渲染性能要好於普通組件
  2. 函數式組件結構比較簡單,代碼結構更清晰

函數式組件與普通組件的區別

  1. 函數式組件需要在組件上聲明functional
  2. 函數式組件不需要實例化,所以沒有 this,this通過render函數的第二個參數來代替
  3. 函數式組件沒有生命週期鉤子函數,不能使用計算屬性、watch 等等
  4. 函數式組件不能通過 $emit 對外暴露事件,調用事件只能通過context.listeners.click的方式調用外部傳入的事件
  5. 因爲函數式組件是沒有實例化的,所以在外部通過ref去引用組件時,實際引用的是 HTMLElement
  6. 函數式組件的props可以不用顯式聲明,所以沒有在props裏面聲明的屬性都會被自動隱式解析爲 prop,而普通組件所有未聲明的屬性都被解析到 $attrs 裏面,並自動掛載到組件根元素上面(可以通過 inheritAttrs 屬性禁止)

模板語法聲明函數式組件

在 Vue2.5 之前,使用函數式組件只能通過 JSX 的方式,在之後可以通過模板語法來聲明函數式組件

<!-- 在 template 上面添加 functional 屬性 -->
<template functional>
  <img :src="props.avatar" />
</template>
<!-- 上面第 6 點,可不用顯示聲明 props -->

事件參數 $event

$event 是事件對象的一個特殊變量。它在某些場景下爲複雜的功能提供了更多的可選參數

<template>
  <img src="text.jpg" @click="handleClick($event)" />
</template>

<script>
export default {
  methods: {
    handleClick (e) {
      console.log(e)
    }
  }
}
</script>

EventBus

聲明一個全局 Vue 實例變量 EventBus,把所有的通信數據、事件監聽都存儲到這個變量上
類似於 Vuex,但這種方式一般適用於小的項目
原理就是利用 on、emit 並實例化一個全局 vue 實現數據共享
可以實現平級、嵌套組件傳值;但是對應的事件名 eventTarget 必須是全局唯一的

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

// 傳值組件
this.$eventBus.$emit('eventTarget','這是eventTarget傳過來的值')

// 接收組件
this.$eventBus.$on('eventTarget', v => {
  console.log('eventTarget', v)
})

也可以新建一個 bus.js 文件

import Vue from 'vue'
export default new Vue()

在要通信的組件導入此文件,進行監聽或發送

import Bus from '@/bus'

// 組件1
Bus.$emit('operateMusic', id)

// 組件2
Bus.$on('operateMusic', id => {})

Mixin 混入

一般在 src 定義一個 mixins 文件夾,裏面存放每個 mixin,用 index.js 文件彙總導出

index.js

import serviceMixinsModule from './service-mixins'
import tableListMixinsModule from './tableList-mixins'

export const serviceMixins = serviceMixinsModule
export const tableListMixins = tableListMixinsModule

// 組件中使用
// import { serviceMixins, tableListMixins } from '@/mixins'
// export default {
//   mixins: [serviceMixins, tableListMixins],
// }

主要說說衝突問題

當組件和混入對象含有同名選項時,這些選項將以恰當的方式進行“合併”
比如,數據對象在內部會進行遞歸合併,並在發生衝突時以組件數據優先

let mixin = {
  data: function () {
    return {
      message: 'hello',
      foo: 'abc'
    }
  }
}

new Vue({
  mixins: [mixin],
  data: function () {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created: function () {
    console.log(this.$data)
    // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})

同名鉤子函數將合併爲一個數組,因此都將被調用。另外,混入對象的鉤子將在組件自身鉤子之前調用

let mixin = {
  created: function () {
    console.log('混入對象的鉤子被調用')
  }
}

new Vue({
  mixins: [mixin],
  created: function () {
    console.log('組件鉤子被調用')
  }
})

// => "混入對象的鉤子被調用"
// => "組件鉤子被調用"

值爲對象的選項,例如 methods、components 和 directives,將被合併爲同一個對象。兩個對象鍵名衝突時,取組件對象的鍵值對

let mixin = {
  methods: {
    foo: function () {
      console.log('foo')
    },
    conflicting: function () {
      console.log('from mixin')
    }
  }
}

let vm = new Vue({
  mixins: [mixin],
  methods: {
    bar: function () {
      console.log('bar')
    },
    conflicting: function () {
      console.log('from self')
    }
  }
})

vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"

注意:Vue.extend() 也使用同樣的策略進行合併

路由參數解耦

相信這是大多數人處理組件中路由參數的方式:

export default {
  computed: {
    paramsId() {
      return this.$route.params.id
    }
  }
}

在組件內部使用 $route 會對某個URL產生強耦合,這限制了組件的靈活性
正確的解決方案是向路由器添加 props

const router = new VueRouter({
  routes: [{
    path: '/:id',
    component: Component,
    props: true
  }]
})

這樣,組件可以直接從 props 獲取 params

export default {
  props: ['id'],
  computed: {
    paramsId() {
      return this.id
    }
  }
}

此外,你還可以傳入函數以返回自定義 props

const router = new VueRouter({
  routes: [{
    path: '/:id',
    component: Component,
    props: router => ({ id: route.query.id })
  }]
})

hook 妙用

如果在頁面掛載時增加一個定時器,但銷燬時需要清除定時器
一般想法是在 beforeDestroy 中使用 clearInterval(this.timer) 來清除

export default {
  data () {
    return {
      timer: null
    }
  },
  mounted () {
    this.timer = setInterval(() => {
      console.log(Date.now())
    }, 1000)
  },
  beforeDestroy () {
    clearInterval(this.timer)
  }
}

還有種更方便的方法,使用 $once 監聽 hook 函數

export default {
  mounted () {
    let timer = null
    timer = setInterval(() => {
      console.log(Date.now())
    }, 1000)
    this.$once('hook:beforeDestroy', () => {
      clearInterval(timer)
    })
  }
}

監聽子組件生命週期 Hook

通常,可以像這樣監聽子組件的生命週期(例如 mounted)

<!-- Child -->
<script>
export default {
  mounted () {
    this.$emit('onMounted')
  }
}
</script>

<!-- Parent -->
<template>
  <Child @onMounted="handleOnMounted" />
</template>

還有另一種簡單的解決方案,可以改用 @hook:mounted 在父組件直接監聽

<!-- Parent -->
<template>
  <Child @hook:mounted="handleOnMounted" />
</template>

掛載全局變量

Vue.prototype.$lang = Lang

// 可以在任何一個組件使用 this.$lang

Watcher 技巧

watch 的深度監聽deep: true 和 立即觸發immediate: true 就不多說了
需要注意的是深度監聽deep: true 只能監聽原有屬性的變化,不能監聽新增、刪除的屬性

還有一個有趣的特性,隨時監聽,隨時取消,$watch

const unwatch = this.$watch('say', curVal => {
  console.log('數據發生了變化', curVal)
}, {
  deep: true,
  immediate: true // 是否第一次觸發
})
setTimeout(() => {
  unwatch()
}, 3000)

this.$watch 的返回值 unwatch 是個方法,執行後就可以取消監聽

傳送門

Vue 自定義指令

Vue3 爲何使用 Proxy 實現數據監聽

Vue JSX、自定義 v-model

Vue.nextTick 的應用解析

Vue 路由模塊化配置

關於 Vue 不能 watch 數組 和 對象變化的解決方案

Vue.mixin

Vuex 相關理解

Vue 知識總結

​博客地址:https://ainyi.com/95

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章