動態導入本地圖片
假設我們有這么一個功能,后臺返回圖片的名稱,前端需要自己拼接路徑獲取本地圖片,假定這些資源是存在我們前端的 assets/images
,如果你采取傳統的字符串拼接的方式:
//template
<img :src="'@/assets/images' + imgUrl" alt="">
發現圖片無法顯示,打開控制臺審查元素,發現路徑并沒有正確解析,這個跟 webpack
編譯打包有關系,在編譯過程中目錄結構改變導致的。
我們只需要一個 require
方法就可以完美解決這個問題:
//template
<img :src="require('@/assets/images' + imgUrl)" alt="">
刷新瞧瞧,是不是可以了~
開發和生產的路由配置
配置路由的時候,開發環境下不需要使用 lazy-loading
加載 , 僅在生產環境使用即可,因為開發模式使用 lazy-loading
會導致 webpack
熱更新比較慢。
可以創建2個 js
,分別為 _import_development.js
和 _import_production.js
用來加載我們的組件
ps
: 我的頁面是在 views
文件夾下
// _import_development.js
module.exports = file => require('@/views' + file + '.vue').default;
// _import_production.js
module.exports = file => () => import('@/views/' + file + '.vue')
之后我們可以在我們的路由文件中通過當前運行環境( process.env.NODE_ENV
)來加載不同的導入文件js
,大概就變成下面的樣子了。
// router.js
const _import = require('./_import_' + process.env.NODE_ENV);
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Index',
component: _import('/index')
}
],
mode: 'history'
})
帶參數的自定義指令
我們平時書寫自定義指令大部分都是以下的這種方式:
Vue.directive('background', {
inserted: function (el) {
// 修改背景色
el.style.backgroundColor = 'red'
}
})
這樣可能在某些場景下顯得不夠靈活,其實我們是可以給指令傳遞參數的,我們可以將上面的代碼改成下面這樣:
Vue.directive('background', {
inserted: function (el,binding) {
// 修改背景色
el.style.backgroundColor = binding.value
}
})
其中第二個參數 binding
是一個對象,包含下面這些屬性:
-
name
:指令名,不包括v-
前綴。 -
value
:指令的綁定值,例如:v-background="'red'"
中,綁定值為red
。 -
oldValue
:指令綁定的前一個值,僅在update
和componentUpdated
鉤子中可用。 -
expression
:字符串形式的指令表達式。例如v-my-directive="1 + 1"
中,表達式為"1 + 1"
。 -
arg
:傳給指令的參數,可選。例如v-my-directive:foo
中,參數為"foo"
。 -
modifiers
:一個包含修飾符的對象。例如:v-my-directive.foo.bar
中,修飾符對象為{ foo: true, bar: true }
使用時就可以
//固定值
<div class="" v-background="'red'">Hello World</div>
//動態傳值
<div class="" v-background="color">Hello World</div>
其中 color
綁定的是 data
里面的 color
值。
如果你覺得這么還不夠靈活,我想動態修改參數可以嗎?當然沒問題,請看下面:
Vue.directive('style', {
inserted: function (el,binding) {
el.style[binding.arg] = binding.value;
}
})
這么一來,你想修改啥直接寫就是啦。
// 修改背景色
<div class="" v-style:[`background`]="'red'">Hello World</div>
如果我想批量修改,so easy
傳個對象就好了,如下
Vue.directive('style', {
inserted: function (el,binding) {
for( let key in binding.value){
el.style[key] = binding.value[key]
}
}
})
// 批量修改
<div class="" v-style="{ color : 'white' , background : 'red'}">Hello World</div>
帶參數過濾器
過濾器通常在** 雙花括號插值**和 v-bind 表達式 中使用,經常是為了來格式化一些文本之類的。
它跟自定義指令一樣,也是可以帶參數的,不過過濾器比起指令要簡單的多。
假設我們需要將后端傳過來的時間戳格式化一下,一般的這么寫就可以了:
// ps: 這里引入了一個 moment 包
Vue.filter('formatDate',function (val) {
return moment.unix(val).format('YYYY-MM-DD HH:mm:ss')
})
后來為了讓用戶可以自定義顯示格式,后端增加了一個formate
字段,我們不得不修改我們的過濾器,這時候就需要給過濾器加參數,來解決這個問題
Vue.filter('formatDate',function (val,format) {
return moment.unix(val).format(format)
})
調用的時候只需要這么傳入即可:
<div class="">{{ timestamp | formatDate('YYYY/MM/DD HH:mm:ss') }}</div>
過濾器第一個參數仍然是原始的值,YYYY/MM/DD HH:mm:ss
作為第二個參數傳到了 format
中,這樣的拓展性是不是更好了呢~
$attrs解決數據多級傳遞
$attrs
官方解釋是包含了父作用域中不作為 prop
被識別 (且獲取) 的特性綁定 (class
和 style
除外)。當一個組件沒有聲明任何 prop
時,這里會包含所有父作用域的綁定 (class
和 style
除外),并且可以通過 v-bind="$attrs"
傳入內部組件——在創建高級別的組件時非常有用。
理解起來一頭霧水,其主要意思是父組件往子組件傳沒有在props
里聲明過的值時,子組件可以通過$attrs
接受,且只包含父組件沒有在props
里聲明的值。
通常我們如果需要從父組件接收傳遞很多個值,那么我們就需要在 props
里聲明需要接受的值,如果孫子組件也需要,那么就又要重復在props
中聲明,顯得非常繁瑣。
例如我們現在有 A
,B
,C
三個組件,是父子孫的關系,如果 B
、C
組件都要從 A
組件中繼承一系列的屬性:
// com-a
<template>
<com-b :title='title' :desc='desc' :date='date'></com-b>
</template>
// com-b
<template>
<com-c v-bind='$attrs'></com-c>
</template>
這時候我們在 B
組件中通過 $attrs
就可以獲取父組件傳遞的 title
、desc
和date
,此外我們只要給 C
組件綁定 v-bind='$attrs'
, 同理,C
組件內部也就可以通過 $attrs
獲取到 A
里面的值了~
ps:
B
、C
組件的 DOM
上會綁定 A
傳過來的屬性,Vue
內部默認是這么處理的,要去掉的話給 B
、C
組件加上 inheritAttrs : false
屬性即可。
$listeners
的用法也比較類似,不贅述了~
跨組件通信的另一種方式
想到跨組件通信,可能會想到 eventBus
, vuex
之類的方法,實際上我們可以借助 vue
本身的依賴注入這種方案優雅實現
我們首先需要在 main.js
中,定義一個 eventHub
, 這是我們的關鍵點
// main.js
new Vue({
el: '#app',
router,
store,
components: {
App
},
data: {
eventHub: new Vue()
},
template: '<App/>'
})
之后,在我們在需要監控的組件的生命周期中綁定一下:
// com-a
mounted () {
this.$root.eventHub.$on('update',(data)=>{
console.log(data);
})
},
beforeDestroy () {
this.$root.eventHub.$off('update')
}
其他組件要觸發改事件只需要一句話:
emitEevent () {
this.$root.eventHub.$emit('update',{ msg : 'hello world' })
}
值得注意的是,一定要在 beforeDestroy
生命周期中通過 $off
取消監聽,不然會重復監聽導致觸發多次,如果只需要觸發一次事件的話,$once
綁定會更加不錯。
函數式組件
Vue
里的函數式組件和 React
中的無狀態組件有些類似,如果說一個組件沒有管理任何狀態,也沒有監聽任何傳遞給它的狀態,也沒有生命周期方法,那么這時候我們可以考慮使用函數式組件。
函數式組件跟普通組件相比,因為沒有狀態管理,聲明周期,只是函數,所以渲染開銷低很多,以此可以優化我們的代碼。
通常函數式組件的聲明方式有2種(局部組件為例):
一種是模版渲染方式加上 functional
關鍵字創建
<template functional>
/***/
</template>
另一種是通過 render
渲染函數,并加上 functional
屬性來標識創建,這種方式比模版更接近編譯器,更加底層,渲染會更加迅速。
export default {
functional: true,
// Props 是可選的
props: {
// ...
},
render: function (createElement, context) {
// ...
}
}
關于 render
函數,由于篇幅太長,這邊不在贅述,想要了解更多細節和配置參數,可以參考官網的解釋
由于函數式組件沒有實例,為了彌補這個問題,組件需要的一切都是通過 context
參數傳遞,它是一個包括如下字段的對象:
-
props
:提供所有prop
的對象 -
children
:VNode
子節點的數組 -
slots
: 一個函數,返回了包含所有插槽的對象 -
scopedSlots
: 一個暴露傳入的作用域插槽的對象。也以函數形式暴露普通插槽。 -
data
:傳遞給組件的整個數據對象,作為createElement
的第二個參數傳入組件 -
parent
:對父組件的引用 -
listeners
: 一個包含了所有父組件為當前組件注冊的事件監聽器的對象 -
injections
: 如果使用了inject
選項,則該對象包含了應當被注入的屬性。
看起來雨里霧里,其實沒那么高深!
我們來個例子更直觀,我們現在需要渲染一個列表,沒有具體的交互,僅做展示使用,為了優化代碼,我們決定使用函數式組件來渲染。
列表數據格式如下:
// list-data
[{
id : 1 ,
title : '學習Vue'
},{
id : 2 ,
title : '學習React'
},{
id : 3 ,
title : '學習Angular'
}]
如果我們通過模版方式來做的的話,我們可以定義如下:
// todoList.vue
<template functional>
<ul>
<li v-for="item in props.todoList">{{ item.title }}</li>
</ul>
</template>
如果我們通過 render
函數來創建的話(.js
文件,不是 .vue
文件), 那么應該是這樣:
export default {
functional: true,
props: {
todoList: {
type: Array,
default: () => []
}
},
render: function (createElement, context) {
let oLi = context.props.todoList.map(item => {
return createElement('li', {
domProps: {
innerHTML: item.title
}
})
})
return createElement('ul', {}, oLi)
}
}
我們通過 import
在父組件導入一下,然后看看結果
效果一樣,雖然 template
方式看起來簡單的多,但是很多時候我還是比較傾向于 render
方式,因為它在修改或者條件判斷的時候會比較方便,也省去了不少 v-if
,v-show
這些指令,看起來更加優雅。
自動化導入component
如果你定義了一系列的牛X的公共組件,然而你每次需要頻繁的去 import xxx from '../xxx'
,還要
components : {
ComponentA
ComponentB
...
}
下面有種一勞永逸的方法,讓你解放雙手~
需要借助 webpack
里面的 require.context
方法,簡單了解一下,通過它獲取一個特定的上下文,然后從中讀取指定目錄下的文件和文件內容。
該方法有三個參數 directory
,useSubdirectories
和useSubdirectories
-
directory {String}
-讀取文件的路徑 -
useSubdirectories {Boolean}
-是否遍歷文件的子目錄 -
regExp {RegExp}
-匹配文件的正則
如果我要遍歷 components
目錄下的所有公共組件,我就可以這么做:
首先在components
目錄下創建一個 baseComponent.js
文件,
// baseComponent.js
import Vue from 'vue'
const autoRequireComponent = require.context('./', false, /.vue$/)
autoRequireComponent.keys().forEach(file => {
//獲取組件配置信息
const componentConfig = autoRequireComponent(file).default
//獲取組件的名稱 , 將 ./UButton.vue 名稱替換成 UButton
const componentName = file.replace(/^\.\//, '').replace(/\.\w+$/, '')
//注冊組件
Vue.component(componentName, componentConfig)
})
其中 autoRequireComponent.keys()
返回的就是一個文件名稱的數組,類似于 ['UButton','UInput']
,然后遍歷并通過 Vue.component
方法注冊到全局
最后在 main.js
中 import
一下就搞定了,現在你可以在任意組件中調用你的公共組件了!
該方法在批量處理一些文件的時候會有奇效!
以上就是我日常開發中遇到或者總結到的一些東西,如果你有更好更優雅的方式,記得分享哈~