前言
本文主要總結了vue實際開發專案當中應該如何解決一些實際的開發問題,可能你認為很簡單,但短時間內也許你並沒解決思路的。
建議閱讀時間:15-25min
更多精彩內容請關注我掘金主頁或者 達摩兵的空間部落格
常見技術解答
for迴圈中針對ui樣式的特徵性樣式或者事件
- 針對ui有特定的資料欄位進行判斷(也叫資料模型方法)
這種書資料的要求比較高,且要求你能夠找到比較好的對應關係,需要針對class進行特徵性的元件渲染。當你需要改變時改變資料即可重新渲染達到改變樣式的目的。
<li v-for="item of list" :key="item.id" :class="item.status?`color`:``" @click="changeColor(item.id)">{{item.name}}</li>
return {
list:[
{id:1,status:true,name:1111},
{id:2,status:true,name:222}]
}
methods:{
changeColor(id){
this.list.map((item)=>{
if(item.id==id){
item.status=!item.status;
}
return item;
})
}
}
複製程式碼
- 傳入對應的引數以及事件源,可以進行相應的判斷改變class
特點更加靈活,也可以根據需要傳入你需要傳入的item屬性引數進行與class的匹配判斷,不用改變介面返回的資料結構。
<li v-for="item of list" :key="item.id" @click="changeColor($event)">{{item.name}}</li>
return {
list:[
{id:1,name:1111},
{id:2,name:222}]
}
changeColor(e){
let el=e.target;
if(el.classList.contains("color")){
el.classList.remove("color")
}else{
el.classList.add("color")
}
}
複製程式碼
計算屬性方法的使用
問題描述:如果你的計算屬性依賴於data的部分,而你的data對應的欄位在data裡沒有申明,只是在請求介面時進行申明賦值,那麼當介面請求時,雖然資料發生了變化,但是計算屬性的值不會發生更新。
解決方案 :需要你在data裡申明你計算屬性依賴的欄位,哪怕是空或者null
事件執行順序問題
問題描述 :定義了輸入框blur,再按鈕點選事件問題,其中預設click的話,執行順序是先執行blur再執行click.如果你需要場景在點選的時候不執行blur的事件
解決方案:
1 常規方案 :
需要吧點選事件變成@mousedown.prevent ,前者會讓點選優於blur執行,後者會阻止blur執行
2 el-input並不生效,可以用計時器延遲執行
將失去焦點的事件計時器延遲執行,然後點選事件裡清除定時器,也是可以只執行點選事件邏輯的
路由引數變化元件不更新
問題描述 :路由引數變化,但是元件沒有對應的更新,主要是因為一般獲取引數寫在了created路由鉤子函式中,路由引數變化的時候,這個生命週期不會重新執行。
解決方案:watch監聽router
watch: {
// 方法1
`$route` (to, from) { //監聽路由是否變化
if(this.$route.params.articleId){// 判斷條件1 判斷傳遞值的變化
//獲取文章資料
}
}
//方法2
`$route`(to, from) {
if (to.path == "/page") { /// 判斷條件2 監聽路由名 監聽你從什麼路由跳轉過來的
this.message = this.$route.query.msg
}
}
}
複製程式碼
非同步函式中使用this無法指向vue例項物件
問題描述 : 在定時器或者其他非同步函式中使用傳統的func導致this指向不到vue例項,主要原因是因為this指向的問題,詳細的可以參考我的《神奇的this》這篇文章。
解決方案 :用箭頭函式或者指定變數賦值為this(其他一些不能用箭頭函式的地方自己也要注意)
定時器在元件銷燬後還在執行
問題描述 :一些耗費效能的計時器或者動畫在元件銷燬之後還是執行的,導致效能變低。
解決方案 :在銷燬元件的生命週期中銷燬定時器或者一些動畫的js
//元件銷燬前執行的鉤子函式,跟其他生命週期鉤子函式的用法相同。
beforeDestroy(){
//我通常是把setInterval()定時器賦值給this例項,然後就可以像下面這麼停止。
clearInterval(this.intervalId);
},
複製程式碼
元件名與引入時大小寫不一致導致報錯
問題描述:
This can lead to unexpected behavior when compiling on a filesystem with other case-semantic.
Use equal casing. Compare these module identifiers:
複製程式碼
解決方案 :需要嚴格對應元件的大小寫,避免低階錯誤
動態新增的dom沒有樣式
問題描述:作為常識我們知道style中的樣式都會追加scoped,這樣針對模板dom中的樣式就可以生效,但其生效後的最終樣式並不是我們寫的樣式名,而是編碼後的,所以我們在js中拼接上的dom結構樣式並不會生效。
解決思路:
1 當新增的部分樣式不會太多,而且是動態載入的,可以將其設定為非scopred的
2 將新增dom部分用的樣式放到非scoped樣式標籤中
3 將新增的部分,如果有必要,可以另外寫一個頁面拆分的vue元件
擴充 :
專案中引入的其他ui框架的樣式,如果你想覆蓋修改,也是需要不加scoped的,如果你想整個專案覆蓋,就可以在src/styles下定義customer-element.scss 這樣的來重寫覆蓋樣式。
vue中直接修改資料,頁面檢視不更新
問題描述 :在常規理解中,檢視與資料是雙向繫結的,但是有時候修改data的陣列或者物件值,檢視不會更新 。
data() { // data資料
return {
arr: [1,2,3],
obj:{
a: 1,
b: 2
}
};
},
// 資料更新 陣列檢視不更新
this.arr[0] = `OBKoro1`;
this.arr.length = 1;
console.log(arr);// [`OBKoro1`];
// 資料更新 物件檢視不更新
this.obj.c = `OBKoro1`;
delete this.obj.a;
console.log(obj); // {b:2,c:`OBKoro1`}
複製程式碼
解決方案 :由於js的限制,Vue 不能檢測以上陣列的變動,以及物件的新增/刪除,很多人會因為像上面這樣操作,出現檢視沒有更新的問題。
1 this.$set(你要改變的陣列/物件,你要改變的位置/key,你要改成什麼value)
this.$set(this.arr, 0, "OBKoro1"); // 改變陣列
this.$set(this.obj, "c", "OBKoro1"); // 改變物件
複製程式碼
2 陣列原生方法觸發檢視更新:
splice()、 push()、pop()、shift()、unshift()、sort()、reverse()
推薦使用splice方法會比較好自定義,因為slice可以在陣列的任何位置進行刪除/新增操作
3 替換陣列
比方說:你想遍歷這個陣列/物件,對每個元素進行處理,然後觸發檢視更新。
// 文件中的栗子: filter遍歷陣列,返回一個新陣列,用新陣列替換舊陣列
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
複製程式碼
需要無腦重複某內容
<div v-for="n in 5">
<span>這裡會被渲染5次,渲染模板{{n}}</span>
</div>
複製程式碼
babel-plugin-transform-runtime使用
- 出現問題:這個外掛可以相容並轉化大部分的es6語法,但是部分語法也是不能轉化或者存在具體問題的。
- 非同步載入元件時,會產生 polyfill 程式碼冗餘
- 不支援對全域性函式與例項方法的 polyfill。不支援全域性函式(如:Promise、Set、Map),Set 跟 Map 這兩種資料結構應該大家用的也不多,影響較小。但是 Promise 影響可能就比較大了。不支援例項方法(如:`abc`.include(`b`)、[`1`, `2`, `3`].find((n) => n 等等),這個限制幾乎廢掉了大部分字串和一半左右陣列的新特性。
而兩個問題的原因均歸因於 babel-plugin-transform-runtime 採用了沙箱機制來編譯我們的程式碼(即:不修改宿主環境的內建物件)。由於非同步元件最終會被編譯為一個單獨的檔案,所以即使多個元件中使用了同一個新特性(例如:Object.keys()),那麼在每個編譯後的檔案中都會有一份該新特性的 polyfill 拷貝。如果專案較小可以考慮不使用非同步載入,但是首屏的壓力會比較大。
- 解決方案:一般情況下 babel-plugin-transform-runtime 能滿足大部分的需求,當不滿足需求時,推薦使用完整的 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` 複製程式碼
- 首先,從專案中移除 babel-plugin-transform-runtime,解除安裝該依賴:
ES6 import 引用問題
在 ES6 中,模組系統的匯入與匯出採用的是引用匯出與匯入(非簡單資料型別),也就是說,如果在一個模組中定義了一個物件並匯出,在其他模組中匯入使用時,匯入的其實是一個變數引用(指標),如果修改了物件中的屬性,會影響到其他模組的使用。
通常情況下,系統體量不大時,我們可以使用 JSON.parse(JSON.stringify(str)) 簡單粗暴地來生成一個全新的深度拷貝的 資料物件。不過當元件較多、資料物件複用程度較高時,很明顯會產生效能問題,這時我們可以考慮使用 Immutable.js。
鑑於這個原因,進行復雜資料型別的匯出時,需要注意多個元件匯入同一個資料物件時修改資料後可能產生的問題。
此外,模組定義變數或函式時即便使用 let 而不是 const,在匯入使用時都會變成只讀,不能重新賦值,效果等同於用 const 宣告。
動態懶載入元件
背景:在webpack的新特性中支援元件的懶載入,也就是說我們可以在載入到該路由的時候再把這部分指令碼進行載入,同時這個在專案進行打包的時候,對應的檔案也會被單獨打包,對於首屏優化以及其他頁面的資源載入優化都是非常好的。這也要求我們在每個頁面元件使用元件的時候儘量按需引入,提升體驗。
問題場景:那麼我們需要解決的問題是:
0 webpack是靜態解析路徑的,直接傳入變數並不可行
1 每次都寫一串載入元件的程式碼很不方便,是否可以支援寫成一個載入元件的方法
2 是否支援區分生產和開發環境,因為開發環境使用懶載入會導致熱更新,導致更新變慢,所以開發環境使用全量預設載入,生產環境使用懶載入
解決方案如下 :
1 webpack的路徑使用變數拼接,必須預先給出一個相對路徑,然後把具體的元件路徑在傳入
2 用一個箭頭函式,將需要傳入的元件名或者相對路徑傳入
3 用process.env.NODE_ENV確定使用哪種載入方式
程式碼如下:
在原來的router/index.js中,定義一個載入元件的_import方法。
// router/index.js
const _import = require(`./_import_` + process.env.NODE_ENV)
//使用時
{
path: `/`,
name: `HelloWorld`,
component: _import(`HelloWorld`)
},
// router/_import_development.js
module.exports = file => require(`@/views/` + file + `.vue`).default // vue-loader at least v13.0.0+
// router/_import_production.js 如果你載入的vue不是這個路徑 請自定義哦
module.exports = file => () => import(`@/views/` + file + `.vue`)
複製程式碼
vue中的data必須為函式
場景 :vue入門的人可能在頁面單獨引入vue的時候,直接使用data為物件型別的,並沒有問題,但是在spa應用中,如果元件中的data為物件型別就會報錯。
解決方案 :data換為函式,返回物件型別的鍵值對。
擴充 :你可能知道要這樣做,這裡稍微科普下原因,主要是因為根元件只會用一次,所以可以用物件,而子元件可能在一個應用中被多次使用,為了避免多個元件使用同一資料互相影響,所以講data約定為了返回函式型別,返回需要的物件,以此保證子元件在資料渲染的時候不會互相影響。
有父子標籤關係的自定義元件渲染失敗
場景 :在自定義元件的時候,很多時候需要將ul下的li標籤,table下的tr d標籤進行封裝為自定義元件,但直接使用自定義元件會導致其最終生成的位置不是我們想要的。其標籤會渲染到tbody標籤以外。
Vue.component("row",{
template:`<tr><td>{{content}}</td></tr>`,
data(){
return {
content:`this is a row`
}
},
})
複製程式碼
解決方案 :原因是因為html會進行標籤解析,tbody下的標籤必須為tr,其他的同理。那麼我們可以將其子標籤設定為原來的標籤型別,然後用is=”selfComponent” 來解決這個問題。
<tr is="row"></tr>
擴充:
- 不要將渲染vue的容器元素定位到html或者body上,否則提示:
Do not mount Vue to <html> or <body> - mount to normal elements instead.
- 確保有在vue新建例項的時候將el屬性繫結到一個html模板的標籤上
ref使用
場景 :雖然vue不建議直接操作dom,但是在複雜的場景中,我們需要進行dom的操作,這時候就可以藉助ref實現。比如下面我們舉一個簡單的例子,通過ref獲取dom節點,拿到其內容。
解決方案 :
<div @click="handleClick" ref="hello">hello world
</div>
handleClick(){
console.log(this.$refs.hello)
}
複製程式碼
- 擴充案例 :實現計數器加和
場景 :假設我們有兩個計數器元件的例項,現在需要用ref的方案得到兩個計數器的加和。
程式碼如下:
<counter ref="one" @change="handleChange"></counter>
<counter ref="two" @change="handleChange"></counter>
<span>{{total}}</span>
Vue.component("counter",{
template:"<div @click=`change`>{{number}}</div>",
data(){
return {
number:0}
},
methods:{
change(){
this.number++;
this.$emit("change")
}
}
})
//app父元件方法
handleChange(){
this.total=this.$refs.one.number+this.$refs.two.number
},
複製程式碼
-
擴充認知 :
this.$refs.name中如果是原生標籤,拿到的是原生標籤的節點,如果是元件,拿到的是元件的引用。