10個(gè)Vue開發(fā)技巧助力成為更好的工程師

優(yōu)雅更新props
更新?prop?在業(yè)務(wù)中是很常見的需求,但在子組件中不允許直接修改?prop,因?yàn)檫@種做法不符合單向數(shù)據(jù)流的原則,在開發(fā)模式下還會(huì)報(bào)出警告。因此大多數(shù)人會(huì)通過?$emit?觸發(fā)自定義事件,在父組件中接收該事件的傳值來更新?prop。
child.vue:
export?defalut?{
????props:?{
????????title:?String??
????},
????methods:?{
????????changeTitle(){
????????????this.$emit('change-title',?'hello')
????????}
????}
}
parent.vue:
<child?:title="title"?@change-title="changeTitle">child>
export?default?{
????data(){
????????return?{
????????????title:?'title'
????????}??
????},
????methods:?{
????????changeTitle(title){
????????????this.title?=?title
????????}
????}
}
這種做法沒有問題,我也常用這種手段來更新?prop。但如果你只是想單純的更新?prop,沒有其他的操作。那么?sync?修飾符能夠讓這一切都變得特別簡單。
parent.vue:
<child?:title.sync="title">child>
child.vue:
export?defalut?{
????props:?{
????????title:?String??
????},
????methods:?{
????????changeTitle(){
????????????this.$emit('update:title',?'hello')
????????}
????}
}
只需要在綁定屬性上添加?.sync,在子組件內(nèi)部就可以觸發(fā)?update:屬性名?來更新?prop。可以看到這種手段確實(shí)簡潔且優(yōu)雅,這讓父組件的代碼中減少一個(gè)“沒必要的函數(shù)”。
參考文檔
provide/inject
這對選項(xiàng)需要一起使用,以允許一個(gè)祖先組件向其所有子孫后代注入一個(gè)依賴,不論組件層次有多深,并在其上下游關(guān)系成立的時(shí)間里始終生效。
簡單來說,一個(gè)組件將自己的屬性通過?provide?暴露出去,其下面的子孫組件?inject?即可接收到暴露的屬性。
App.vue:
export?default?{
????provide()?{
????????return?{
????????????app:?this
????????}
????}?
}
child.vue:
export?default?{
????inject:?['app'],
????created()?{
????????console.log(this.app)?//?App.vue實(shí)例
????}
}
在 2.5.0+ 版本可以通過設(shè)置默認(rèn)值使其變成可選項(xiàng):
export?default?{
????inject:?{
????????app:?{
????????????default:?()?=>?({})
????????}
????},
????created()?{
????????console.log(this.app)?
????}
}
如果你想為?inject?的屬性變更名稱,可以使用?from?來表示其來源:
export?default?{
????inject:?{
????????myApp:?{
????????????//?from的值和provide的屬性名保持一致
????????????from:?'app',
????????????default:?()?=>?({})
????????}
????},
????created()?{
????????console.log(this.myApp)?
????}
}
需要注意的是?provide?和?inject?主要在開發(fā)高階插件/組件庫時(shí)使用。并不推薦用于普通應(yīng)用程序代碼中。但是某些時(shí)候,或許它能幫助到我們。
參考文檔
小型狀態(tài)管理器
大型項(xiàng)目中的數(shù)據(jù)狀態(tài)會(huì)比較復(fù)雜,一般都會(huì)使用?vuex?來管理。但在一些小型項(xiàng)目或狀態(tài)簡單的項(xiàng)目中,為了管理幾個(gè)狀態(tài)而引入一個(gè)庫,顯得有些笨重。
在 2.6.0+ 版本中,新增的?Vue.observable?可以幫助我們解決這個(gè)尷尬的問題,它能讓一個(gè)對象變成響應(yīng)式數(shù)據(jù):
//?store.js
import?Vue?from?'vue'
export?const?state?=?Vue.observable({?
??count:?0?
})
使用:
<div?@click="setCount">{{?count?}}div>
import?{state}?from?'../store.js'
export?default?{
????computed:?{
????????count()?{
????????????return?state.count
????????}
????},
????methods:?{
????????setCount()?{
????????????state.count++
????????}
????}
}
當(dāng)然你也可以自定義?mutation?來復(fù)用更改狀態(tài)的方法:
import?Vue?from?'vue'
export?const?state?=?Vue.observable({?
??count:?0?
})
export?const?mutations?=?{
??SET_COUNT(payload)?{
????if?(payload?>?0)?{
????????state.count?=?payload
????}?
??}
}
使用:
import?{state,?mutations}?from?'../store.js'
export?default?{
????computed:?{
????????count()?{
????????????return?state.count
????????}
????},
????methods:?{
????????setCount()?{
????????????mutations.SET_COUNT(100)
????????}
????}
}
參考文檔
卸載watch觀察
通常定義數(shù)據(jù)觀察,會(huì)使用選項(xiàng)的方式在?watch?中配置:
export?default?{
????data()?{
????????return?{
????????????count:?1??????
????????}
????},
????watch:?{
????????count(newVal)?{
????????????console.log('count 新值:'+newVal)
????????}
????}
}
除此之外,數(shù)據(jù)觀察還有另一種函數(shù)式定義的方式:
export?default?{
????data()?{
????????return?{
????????????count:?1??????
????????}
????},
????created()?{
????????this.$watch('count',?function(){
????????????console.log('count 新值:'+newVal)
????????})
????}
}
它和前者的作用一樣,但這種方式使定義數(shù)據(jù)觀察更靈活,而且?$watch?會(huì)返回一個(gè)取消觀察函數(shù),用來停止觸發(fā)回調(diào):
let?unwatchFn?=?this.$watch('count',?function(){
????console.log('count 新值:'+newVal)
})
this.count?=?2?// log: count 新值:2
unwatchFn()
this.count?=?3?//?什么都沒有發(fā)生...
$watch?第三個(gè)參數(shù)接收一個(gè)配置選項(xiàng):
this.$watch('count',?function(){
????console.log('count 新值:'+newVal)
},?{
????immediate:?true?//?立即執(zhí)行watch
})
參考文檔
巧用template
相信?v-if?在開發(fā)中是用得最多的指令,那么你一定遇到過這樣的場景,多個(gè)元素需要切換,而且切換條件都一樣,一般都會(huì)使用一個(gè)元素包裹起來,在這個(gè)元素上做切換。
<div?v-if="status==='ok'">
????<h1>Titleh1>
????<p>Paragraph?1p>
????<p>Paragraph?2p>
div>
如果像上面的 div 只是為了切換條件而存在,還導(dǎo)致元素層級(jí)嵌套多一層,那么它沒有“存在的意義”。
我們都知道在聲明頁面模板時(shí),所有元素需要放在??元素內(nèi)。除此之外,它還能在模板內(nèi)使用,?元素作為不可見的包裹元素,只是在運(yùn)行時(shí)做處理,最終的渲染結(jié)果并不包含它。
<template>
????<div>
????????<template?v-if="status==='ok'">
??????????<h1>Titleh1>
??????????<p>Paragraph?1p>
??????????<p>Paragraph?2p>
????????template>
????div>
template>
同樣的,我們也可以在??上使用?v-for?指令,這種方式還能解決?v-for?和?v-if?同時(shí)使用報(bào)出的警告問題。
<template?v-for="item?in?10">
????<div?v-if="item?%?2?==?0"?:key="item">{{item}}div>
template>
template使用v-if,
template使用v-for
過濾器復(fù)用
過濾器被用于一些常見的文本格式化,被添加在表達(dá)式的尾部,由“管道”符號(hào)指示。
<div>{{?text?|?capitalize?}}div>
export?default?{
????data()?{
????????return?{
????????????text:?'hello'
????????}??
????},
????filters:?{
????????capitalize:?function?(value)?{
????????????if?(!value)?return?''
????????????value?=?value.toString()
????????????return?value.charAt(0).toUpperCase()?+?value.slice(1)
?????????}
????}
}
試想一個(gè)場景,不僅模板內(nèi)用到這個(gè)函數(shù),在?method?里也需要同樣功能的函數(shù)。但過濾器無法通過?this?直接引用,難道要在?methods?再定義一個(gè)同樣的函數(shù)嗎?
要知道,選項(xiàng)配置都會(huì)被存儲(chǔ)在實(shí)例的?$options?中,所以只需要獲取?this.$options.filters就可以拿到實(shí)例中的過濾器。
export?default?{
????methods:?{
????????getDetail()?{
????????????this.$api.getDetail({
????????????????id:?this.id
????????????}).then(res?=>?{
????????????????let?capitalize?=?this.$options.filters.capitalize
????????????????this.title?=?capitalize(res.data.title)
????????????})
????????}
????}
}
除了能獲取到實(shí)例的過濾器外,還能獲取到全局的過濾器,因?yàn)?span>?this.$options.filters?會(huì)順著?__proto__?向上查找,全局過濾器就存在原型中。
自定義指令獲取實(shí)例
有的情況下,當(dāng)需要對普通 DOM 元素進(jìn)行底層操作,這時(shí)候就會(huì)用到自定義指令。像是項(xiàng)目中常用的權(quán)限指令,它能精確到某個(gè)模塊節(jié)點(diǎn)。大概思路為獲取權(quán)限列表,如果當(dāng)前綁定權(quán)限不在列表中,則刪除該節(jié)點(diǎn)元素。
Vue.directive('role',?{
????inserted:?function?(el,?binding,?vnode)?{
??????let?role?=?binding.value
??????if(role){
????????const?applist?=?sessionStorage.getItem("applist")
????????const?hasPermission?=?role.some(item?=>?applist.includes(item))?
????????//?是否擁有權(quán)限
????????if(!hasPermission){
??????????el.remove()?//沒有權(quán)限則刪除模塊節(jié)點(diǎn)
????????}
??????}
????}
})
自定義指令鉤子函數(shù)共接收3個(gè)參數(shù),包括?el?(綁定指令的真實(shí)dom)、binding?(指令相關(guān)信息)、vnode?(節(jié)點(diǎn)的虛擬dom)。
假設(shè)現(xiàn)在業(yè)務(wù)發(fā)生變化,applist?存儲(chǔ)在?vuex?里, 但指令內(nèi)想要使用實(shí)例上的屬性,或者是原型上的?$store。我們是沒有辦法獲取到的,因?yàn)殂^子函數(shù)內(nèi)并沒有直接提供實(shí)例訪問。vnode?作為當(dāng)前的虛擬dom,它里面可是綁定到實(shí)例上下文的,這時(shí)候訪問?vnode.context?就可以輕松解決問題。
Vue.directive('role',?{
????inserted:?function?(el,?binding,?vnode)?{
??????let?role?=?binding.value
??????if(role){
????????//?vnode.context?為當(dāng)前實(shí)例
????????const?applist?=?vnode.context.$store.state.applist
????????const?hasPermission?=?role.some(item?=>?applist.includes(item))?
????????if(!hasPermission){
??????????el.remove()
????????}
??????}
????}
})
優(yōu)雅注冊插件
插件通常用來為?Vue?添加全局功能。像常用的?vue-router、vuex?在使用時(shí)都是通過?Vue.use來注冊的。Vue.use?內(nèi)部會(huì)自動(dòng)尋找?install?方法進(jìn)行調(diào)用,接受的第一個(gè)參數(shù)是?Vue?構(gòu)造函數(shù)。
一般在使用組件庫時(shí),為了減小包體積,都是采用按需加載的方式。如果在入口文件內(nèi)逐個(gè)引入組件會(huì)讓?main.js?越來越龐大,基于模塊化開發(fā)的思想,最好是單獨(dú)封裝到一個(gè)配置文件中。配合上?Vue.use,在入口文件使用能讓人一目了然。
vant.config.js:
import?{
??Toast,
??Button
}?from?'vant'
const?components?=?{
??Toast,
??Button
}
const?componentsHandler?=?{
??install(Vue){
????Object.keys(components).forEach(key?=>?Vue.use(components[key]))
??}
}
export?default?componentsHandler
main.js:
import?Vue?from?'vue'
import?vantCompoents?from?'@/config/vant.config'
Vue.config.productionTip?=?false
Vue.use(vantCompoents)
new?Vue({
??render:?h?=>?h(App)
}).$mount('#app')
參考文檔
自動(dòng)化引入模塊
在開發(fā)中大型項(xiàng)目時(shí),會(huì)將一個(gè)大功能拆分成一個(gè)個(gè)小功能,除了能便于模塊的復(fù)用,也讓模塊條理清晰,后期項(xiàng)目更好維護(hù)。
像 api 文件一般按功能劃分模塊,在組合時(shí)可以使用?require.context?一次引入文件夾所有的模塊文件,而不需要逐個(gè)模塊文件去引入。每當(dāng)新增模塊文件時(shí),就只需要關(guān)注邏輯的編寫和模塊暴露,require.context?會(huì)幫助我們自動(dòng)引入。
需要注意?require.context?并不是天生的,而是由?webpack?提供。在構(gòu)建時(shí),webpack?在代碼中解析它。
let?importAll?=?require.context('./modules',?false,?/\.js$/)
class?Api?extends?Request{
????constructor(){
????????super()
????????//importAll.keys()為模塊路徑數(shù)組
????????importAll.keys().map(path?=>{
????????????//兼容處理:.default獲取ES6規(guī)范暴露的內(nèi)容;?后者獲取commonJS規(guī)范暴露的內(nèi)容
????????????let?api?=?importAll(path).default?||?importAll(path)
????????????Object.keys(api).forEach(key?=>?this[key]?=?api[key])
????????})
????}
}
export?default?new?Api()
require.context?參數(shù):
文件夾路徑 是否遞歸查找子文件夾下的模塊 模塊匹配規(guī)則,一般匹配文件后綴名
只要是需要批量引入的場景,都可以使用這種方法。包括一些公用的全局組件,只需往文件夾內(nèi)新增組件即可使用,不需要再去注冊。如果還沒用上的小伙伴,一定要了解下,簡單實(shí)用又能提高效率。
參考文檔
路由懶加載(動(dòng)態(tài)chunkName)
路由懶加載作為性能優(yōu)化的一種手段,它能讓路由組件延遲加載。通常我們還會(huì)為延遲加載的路由添加“魔法注釋”(webpackChunkName)來自定義包名,在打包時(shí),該路由組件會(huì)被單獨(dú)打包出來。
let?router?=?new?Router({
??routes:?[
????{
??????path:'/login',
??????name:'login',
??????component:?import(/*?webpackChunkName:?"login"?*/?`@/views/login.vue`)
????},
????{
??????path:'/index',
??????name:'index',
??????component:?import(/*?webpackChunkName:?"index"?*/?`@/views/index.vue`)
????},
????{
??????path:'/detail',
??????name:'detail',
??????component:?import(/*?webpackChunkName:?"detail"?*/?`@/views/detail.vue`)
????}
??]
})
上面這種寫法沒問題,但仔細(xì)一看它們結(jié)構(gòu)都是相似的,作為一名出色的開發(fā)者,我們可以使用?map?循環(huán)來解決這種重復(fù)性的工作。
const?routeOptions?=?[
??{
????path:'/login',
????name:'login',
??},
??{
????path:'/index',
????name:'index',
??},
??{
????path:'/detail',
????name:'detail',
??},
]
const?routes?=?routeOptions.map(route?=>?{
??if?(!route.component)?{
????route?=?{
??????...route,
??????component:?()?=>?import(`@/views/${route.name}.vue`)
????}
??}
??return?route
})
let?router?=?new?Router({
??routes
})
在書寫更少代碼的同時(shí),我們也把“魔法注釋”給犧牲掉了。總所周知,代碼中沒辦法編寫動(dòng)態(tài)注釋。這個(gè)問題很尷尬,難道就沒有兩全其美的辦法了嗎?
強(qiáng)大的?webpack?來救場了,從 webpack 2.6.0 開始,占位符 [index] 和 [request] 被支持為遞增的數(shù)字或?qū)嶋H解析的文件名。我們可以這樣使用“魔法注釋”:
const?routes?=?routeOptions.map(route?=>?{
??if?(!route.component)?{
????route?=?{
??????...route,
??????component:?()?=>?import(/*?webpackChunkName:?"[request]"?*/?`@/views/${route.name}.vue`)
????}
??}
??return?route
})
- END -專注分享當(dāng)下最實(shí)用的前端技術(shù)。關(guān)注前端達(dá)人,與達(dá)人一起學(xué)習(xí)進(jìn)步!
長按關(guān)注"前端達(dá)人"

