由於公司的前端開始轉向 VueJS,最近開始使用這個框架進行開發,遇到一些問題記錄下來,以備後用。
主要寫一些 官方手冊 上沒有寫,但是實際開發中會遇到的問題,需要一定知識基礎。
涉及技術棧
- CLI: Vue-CLI
- UI: Element
- HTML: Pug(Jade)
- CSS: Less
- JavaScript: ES6
polyfill 與 transform-runtime
首先,vue-cli 爲我們自動添加了 babel-plugin-transform-runtime 這個插件,該插件多數情況下都運作正常,可以轉換大部分 ES6 語法。
但是,存在如下兩個問題:
1、異步加載組件時,會產生 polyfill 代碼冗餘
2、不支持對全局函數與實例方法的 polyfill
兩個問題的原因均歸因於 babel-plugin-transform-runtime 採用了沙箱機制來編譯我們的代碼(即:不修改宿主環境的內置對象)。
由於異步組件最終會被編譯爲一個單獨的文件,所以即使多個組件中使用了同一個新特性(例如:Object.keys()),那麼在每個編譯後的文件中都會有一份該新特性的 polyfill 拷貝。如果項目較小可以考慮不使用異步加載,但是首屏的壓力會比較大。
不支持全局函數(如:Promise、Set、Map),Set 跟 Map 這兩種數據結構應該大家用的也不多,影響較小。但是 Promise 影響可能就比較大了。
不支持實例方法(如:’abc’.include(‘b’)、[‘1’, ‘2’, ‘3’].find((n) => n < 2) 等等),這個限制幾乎廢掉了大部分字符串和一半左右數組的新特性。
一般情況下 babel-plugin-transform-runtime 能滿足大部分的需求,當不滿足需求時,推薦使用完整的 babel-polyfill。
替換 babel-polyfill
首先,從項目中移除 babel-plugin-transform-runtime
卸載該依賴:
npm
un babel-plugin-transform-runtime -D |
修改 babel 配置文件
//
.babelrc { //... "plugins" :
[ //
- "transform-runtime" ] //... } |
然後,安裝 babel-polyfill 依賴:
npm
i babel-polyfill -D |
最後,在入口文件中導入
//
src/main.js import
'babel-polyfill' |
ES6 import 引用問題
在 ES6 中,模塊系統的導入與導出採用的是引用導出與導入(非簡單數據類型),也就是說,如果在一個模塊中定義了一個對象並導出,在其他模塊中導入使用時,導入的其實是一個變量引用(指針),如果修改了對象中的屬性,會影響到其他模塊的使用。
通常情況下,系統體量不大時,我們可以使用 JSON.parse(JSON.stringify(str)) 簡單粗暴地來生成一個全新的深度拷貝的 數據對象。不過當組件較多、數據對象複用程度較高時,很明顯會產生性能問題,這時我們可以考慮使用 Immutable.js。
鑑於這個原因,進行復雜數據類型的導出時,需要注意多個組件導入同一個數據對象時修改數據後可能產生的問題。
此外,模塊定義變量或函數時即便使用 let 而不是 const,在導入使用時都會變成只讀,不能重新賦值,效果等同於用 const 聲明。
在 Vue 中使用 Pug 與 Less
安裝依賴
Vue 中使用 vue-loader 根據 lang 屬性自動判斷所需要的 loader,所以不用額外配置 Loader,但是需要手動安裝相關依賴:
npm
i pug -D npm
i less-loader -D |
還是相當方便的,不用手動修改 webpack 的配置文件添加 loader 就可以使用了
使用 pug 還是 pug-loader?sass 兩種語法的 loader 如何設置?
— 請參考 預處理器 · vue-loader
使用
<!--
xxx.vue --> <style
lang= "less" > .action
{ color:
#ddd; ul
{ overflow:
hidden; li
{ float:
left; } } } </style> <template
lang= "pug" > .action(v- if = 'hasRight' ) ul li
編輯 li
刪除 </template> <script> export
default
{ data
() { return
{ hasRight:
true } } } </script> |
定義全局函數或變量
許多時候我們需要定義一些全局函數或變量,來處理一些頻繁的操作(這裏拿 AJAX 的異常處理舉例說明)。但是在 Vue 中,每一個單文件組件都有一個獨立的上下文(this)。通常在異常處理中,需要在視圖上有所體現,這個時候我們就需要訪問 this 對象,但是全局函數的上下文通常是 window,這時候就需要一些特殊處理了。
簡單粗暴型
最簡單的方法就是直接在 window 對象上定義一個全局方法,在組件內使用的時候用 bind、call 或 apply 來改變上下文。
定義一個全局異常處理方法:
//
errHandler.js window.errHandler
= function
() { //
不能使用箭頭函數 if
(err.code && err.code !== 200) { this .$store.commit( 'err' ,
true ) }
else
{ //
... } } |
在入口文件中導入:
//
src/main.js import
'errHandler.js' 在組件中使用: //
xxx.vue export
default
{ created
() { this .errHandler
= window.errHandler.bind( this ) }, method:
{ getXXX
() { this .$http.get( 'xxx/xx' ).then(({
body: result }) => { if
(result.code === 200) { //
... }
else
{ this .errHandler(result) } }). catch ( this .errHandler) } } } |
優雅安全型
在大型多人協作的項目中,污染 window 對象還是不太妥當的。特別是一些比較有個人特色的全局方法(可能在你寫的組件中幾乎處處用到,但是對於其他人來說可能並不需要)。這時候推薦寫一個模塊,更優雅安全,也比較自然,唯一不足之處就是每個需要使用該函數或方法的組件都需要進行導入。
使用方法與前一種大同小異,就不多作介紹了。 ̄
自定義路徑別名
可能有些人注意到了,在 vue-cli 生成的模板中在導入組件時使用了這樣的語法:
import
Index from '@/components/Index' |
這個 @ 是什麼東西?後來改配置文件的時候發現這個是 webpack 的配置選項之一:路徑別名。
我們也可以在基礎配置文件中添加自己的路徑別名,比如下面這個就把 ~ 設置爲路徑 src/components 的別名:
//
build/webpack.base.js { resolve:
{ extensions:
[ '.js' ,
'.vue' ,
'.json' ], alias:
{ 'vue$' :
'vue/dist/vue.esm.js' , '@' :
resolve( 'src' ), '~' :
resolve( 'src/components' ) } } } |
然後我們導入組件的時候就可以這樣寫:
//
import YourComponent from 'YourComponent' //
import YourComponent from './YourComponent' //
import YourComponent from '../YourComponent' //
import YourComponent from '/src/components/YourComponent' import
YourComponent from '~/YourComponent' |
既解決了路徑過長的麻煩,又解決了相對路徑的煩惱,方便很多吧!
CSS 作用域與模塊
組件內樣式
通常,組件中
標籤裏的樣式是全局的,在使用第三方 UI 庫(如:Element)時,全局樣式很可能影響 UI 庫的樣式。我們可以通過添加 scoped 屬性來使 style 中的樣式只作用於當前組件:
<style
lang= "less"
scoped= "" > @import
'other.less' ; .title
{ font-size:
1.2rem; } </style> |
在有 scoped 屬性的 style 標籤內導入其他樣式,同樣會受限於作用域,變爲組件內樣式。複用程度較高的樣式不建議這樣使用。
另,在組件內樣式中應避免使用元素選擇器,原因在於元素選擇器與屬性選擇器組合時,性能會大大降低。
— 兩種組合選擇器的測試:classes selector,elements selector
導入樣式
相對於 style 使用 scoped 屬性時的組件內樣式,有時候我們也需要添加一些全局樣式。當然我們可以用沒有 scoped 屬性的 style 來寫全局樣式。但是相比較,更推薦下面這種寫法:
/*
單獨的全局樣式文件 */ /*
style-global.less */ body
{ font-size:
10px; } .title
{ font-size:
1.4rem; font-weight:
bolder; } |
然後在入口文件中導入全局樣式:
//
src/main.js import
'style-global.less' |
獲取表單控件值
通常我們可以直接使用 v-model 將表單控件與數據進行綁定,但是有時候我們也會需要在用戶輸入的時候獲取當前值(比如:實時驗證當前輸入控件內容的有效性)。
這時我們可以使用 @input 或 @change 事件綁定我們自己的處理函數,並傳入 $event 對象以獲取當前控件的輸入值:
<input
type= "text"
@change= "change($event)" > |
change
(e) { let
curVal = e.target.value if
(/^\d+$/.test(curVal)) { this .num
= +curVal }
else
{ console.error( '%s
is not a number!' ,
curVal) } } |
當然,如果 UI 框架採用 Element 會更簡單,它的事件回調會直接傳入當前值。
v-for 的使用 tips
v-for 指令很強大,它不僅可以用來遍歷數組、對象,甚至可以遍歷一個數字或字符串。
基本語法就不講了,這裏講個小 tips:
索引值
在使用 v-for 根據對象或數組生成 DOM 時,有時候需要知道當前的索引。我們可以這樣:
<ul> <li
v- for = "(item,
key) in items"
:key= "key" >
{{ key }} - {{ item }} </li></ul> |
但是,在遍歷數字的時候需要注意,數字的 value 是從 1 開始,而 key 是從 0 開始:
<ul> <li
v- for = "(v,
k) in 3"
:key= "k" >
{{ k }}-{{ v }} <!--
output to be 0-1, 1-2, 2-3 --> </li></ul> |
2.2.0+ 的版本里,當在組件中使用 v-for 時,key 現在是必須的。
模板的唯一根節點
與 JSX 相同,組件中的模板只能有一個根節點,即下面這種寫法是 錯誤 的:
<template> <h1>Title</h1> <article>Balabala...</article> </template> |
我們需要用一個塊級元素把他包裹起來:
<template> <div> <h1>Title</h1> <article>Balabala...</article> </div> </template> |
項目路徑配置
由於 vue-cli 配置的項目提供了一個內置的靜態服務器,在開發階段基本不會有什麼問題。但是,當我們把代碼放到服務器上時,經常會遇到靜態資源引用錯誤,導致界面一片空白的問題。
這是由於 vue-cli 默認配置的 webpack 是以站點根目錄引用的文件,然而有時候我們可能需要把項目部署到子目錄中。
我們可以通過 config/index.js 來修改文件引用的相對路徑:
build.assetsSubDirectory:
'static' build.assetsPublicPath:
'/' dev.assetsSubDirectory:
'static' dev.assetsPublicPath:
'/' |
我們可以看到導出對象中 build 與 dev 均有 assetsSubDirectory、assetsPublicPath 這兩個屬性。
其中 assetsSubDirectory 指靜態資源文件夾,也就是打包後的 js、css、圖片等文件所放置的文件夾,這個默認一般不會有問題。
assetsPublicPath 指靜態資源的引用路徑,默認配置爲 /,即網站根目錄,與 assetsSubDirectory 組合起來就是完整的靜態資源引用路徑 /static。
寫到這裏解決方法已經很明顯了,只要把根目錄改爲相對目錄就好了:
build.assetsSubDirectory:
'static' build.assetsPublicPath:
'./' |
沒錯!就是一個 . 的問題
文章還在完善中,歡迎大家一起討論 Vue.JS 開發中遇到的一些問題哈 /
話說收藏好多,你確定收藏了會記得看嗎_
讀一讀開發的時候至少會有個印象,點個贊打卡啦~
原文:VueJS 開發常見問題集錦
https://blog.beard.ink/JavaScript/VueJS-開發常見問題集錦/