部落格地址: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>
}
}
使用函式式元件的原因:
- 最主要最關鍵的原因是函式式元件不需要例項化,無狀態,沒有生命週期,所以渲染效能要好於普通元件
- 函式式元件結構比較簡單,程式碼結構更清晰
函式式元件與普通元件的區別
- 函式式元件需要在元件上宣告functional
- 函式式元件不需要例項化,所以沒有 this,this通過render函式的第二個引數來代替
- 函式式元件沒有生命週期鉤子函式,不能使用計算屬性、watch 等等
- 函式式元件不能通過 $emit 對外暴露事件,呼叫事件只能通過context.listeners.click的方式呼叫外部傳入的事件
- 因為函式式元件是沒有例項化的,所以在外部通過ref去引用元件時,實際引用的是 HTMLElement
- 函式式元件的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 不能 watch 陣列 和 物件變化的解決方案
部落格地址:https://ainyi.com/95