文章首發於個人部落格
前言
使用vue的時候經常會遇到一些問題,其實仔細閱讀查閱官方文件,就會發現文件中已提到一些格外需要注意的點; 為了深入的理解官方文件中對這些問題的解釋,查閱了一些資料,再加上自己的理解,整理了一些常見的問題;如果哪方面解釋的不太合理希望各路大神指出;
文章篇幅較長,但是很實用;
目錄
- 元件裡面, data必須是一個函式
- vue中$set的使用場景
- vue生命週期詳解
- vue元件通訊
- vue元件之keep-alive
- 生命週期函式/methods/watch裡面不應該使用箭頭函式
- methods/computed/watch
1.元件裡面, data必須是一個函式
類比引用資料型別 Object是引用資料型別, 每個元件的data 都是記憶體的同一個地址,一個資料改變了其他也改變了;
那麼用什麼方法可以使每個元件的data相互獨立,不受影響呢?
當一個元件被定義,data 必須宣告為返回一個初始資料物件的函式,因為元件可能被用來建立多個例項。如果 data 仍然是一個純粹的物件,則所有的例項將共享引用同一個資料物件!通過提供 data 函式,每次建立一個新例項後,我們能夠呼叫 data 函式,從而返回初始資料的一個全新副本資料物件。
2.vue中$set的使用場景
場景1:
通過陣列的下標去修改陣列的值,資料已經被修改了,但是不觸發updated函式,檢視不更新,
export default {
data () {
return {
items: ['a', 'b', 'c']
};
},
updated () {
console.log('資料更新', this.items[0]);
},
methods: {
changeItem1 () {
this.items[0] = 'x';
console.log(111, this.items[0]);
},
changeItem2 () {
this.$set(this.items, 0, 'x');
console.log(222, this.items[0]);
},
}
};
複製程式碼
執行changeItem1, 控制檯列印 111 'x', 沒有觸發updated,檢視不更新 執行changeItem2, 控制檯列印 222 'x', 資料更新 'x'; 觸發updated,檢視更新
場景2: vue中檢測不到物件屬性的新增和刪除
data() {
userProfile: {
name: '小明',
}
}
複製程式碼
想要給userProfile加一個age屬性
addProperty () {
this.userProfile.age = '12';
console.log(555, this.userProfile);
}
複製程式碼
執行addProperty函式時,列印如下
555 { name: '小明', age: '12'}
複製程式碼
但是沒有觸發updated, 檢視未更新 改成下面這種
addProperty () {
this.$set(this.userProfile, 'age', '12');
console.log(666, this.userProfile);
}
複製程式碼
再次執行, 資料發生變化, 觸發updated, 檢視更新;
有時你想向已有物件上新增多個屬性,例如使用 Object.assign() 或 _.extend() 方法來新增屬性。但是,新增到物件上的新屬性不會觸發更新。在這種情況下可以建立一個新的物件,讓它包含原物件的屬性和新的屬性:
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
複製程式碼
這是vue中很典型的一個問題,使用的時候一定要注意!
簡單的解釋一下原理:
vue在建立例項的時候把data深度遍歷所有屬性,並使用 Object.defineProperty 把這些屬性全部轉為 getter/setter。讓 Vue 追蹤依賴,在屬性被訪問和修改時通知變化。所以屬性必須在 data 物件上存在才能讓 Vue 轉換它,這樣才能讓它是響應的。
當你在物件上新加了一個屬性newProperty,當前新加的這個屬性並沒有加入vue檢測資料更新的機制(因為是在初始化之後新增的),vue.$set是能讓vue知道你新增了屬性, 它會給你做處理
3.vue生命週期詳解
1. vue的生命週期
- beforeCreate: 元件例項剛剛被建立,元件屬性計算之前,如data屬性
- created: 元件例項建立完成,屬性已繫結,但是DOM還未完成,$el屬性還不存在
- beforeMount:模板編譯/掛載之前
- mounted: 模板編譯/掛載之後
- beforeUpdate: 元件更新之前
- updated: 元件更新之後
- activated: for
keep-alive
,元件被啟用時呼叫 - deactivated: for
keep-alive
,元件被移除時呼叫 - beforeDestroy: 元件銷燬前被呼叫
- destoryed: 元件銷燬後呼叫
ps:下面程式碼可以直接複製出去執行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
<body>
<div id="app">{{a}}</div>
<script>
var vm = new Vue({
el: '#app',
data: {
a: 'vuejs',
},
beforeCreate: function() {
console.log('建立前');
console.log(this.a);
console.log(this.$el);
},
created: function() {
console.log('建立之後');
console.log(this.a);
console.log(this.$el);
},
beforeMount: function() {
console.log('mount之前');
console.log(this.a);
console.log(this.$el);
},
mounted: function() {
console.log('mount之後');
console.log(this.a);
console.log(this.$el);
},
beforeUpdate: function() {
console.log('更新之前');
console.log(this.a);
console.log(this.$el);
},
updated: function() {
console.log('更新完成');
console.log(this.a);
console.log(this.$el);
},
beforeDestroy: function() {
console.log('元件銷燬之前');
console.log(this.a);
console.log(this.$el);
},
destroyed: function() {
console.log('元件銷燬之後');
console.log(this.a);
console.log(this.$el);
},
})
</script>
</body>
</html>
複製程式碼
beforeCreated: el和data並未初始化 created: 完成data資料的初始化,el沒有 beforeMount: 完成了el和data初始化 mounted: 完成掛載
開啟命令列在命令列中輸入vm.a = 'change';檢視效果
複製程式碼
4.vue元件通訊
1.父元件給子元件傳遞資料
vue中使用props向子元件傳遞資料 1): 子元件在props中建立一個屬性,用於接收父元件傳過來的值 2): 父元件中註冊子元件 3): 在子元件標籤中新增子元件props中建立的屬性 4): 把需要傳給子元件的值賦給該屬性
2.子元件向父元件傳遞資料
子元件主要通過事件傳遞資料給父元件 1), 子元件中需要以某種方式,例如點選事件的方法來觸發一個自定義事件 2),將需要傳的值作為$emit的第二個引數,該值將作為實引數傳給相應自定義事件的方法 3),在父元件中註冊子元件並在子元件標籤上繫結自定義事件的監聽
3.子元件向子元件傳遞資料
vue找那個沒有直接子元件對子元件傳參的方法,建議將需要傳遞資料的在元件,都合併為一個元件,如果一定需要子元件對子元件傳參,可以先傳到父元件,再傳到子元件,為了方便開發,vue推出了一個狀態管理工具vuex,可以啃方便的實現元件之間的引數傳遞
具體的例項程式碼如下:可以自行參考相關程式碼在編輯器中嘗試 父元件向子元件傳遞資料
// 父元件向子元件傳遞資料
<!--
msg 是在data中(父元件)定義的變數
如果需要從父元件中獲取logo的值,就需要使用props['msg'], 如30行
在props中新增了元素以後,就不需要在data中(子元件)中再新增變數了
-->
<template>
<div>
<child @transferuser="getUser" :msg="msg"></child>
<p>使用者名稱為:{{user}}(我是子元件傳遞給父元件的資料)</p>
</div>
</template>
<script>
import child from './child.vue';
export default {
components: {
child,
},
data() {
return {
user: '',
msg: '我是父元件傳給子元件的資訊',
};
},
methods: {
getUser(msg) {
this.user = msg;
console.log(msg);
},
},
};
</script>
複製程式碼
子元件向父元件傳遞資料
// 子元件向父元件傳遞資料
<!--
1.@ : 是 v-on的簡寫
2.子元件主要通過事件傳遞資料給父元件
3.當input的值發生變化時,將username傳遞給parent.vue,首先宣告瞭一個setUser,用change事件來呼叫setUser
4.在setUser中,使用了$emit來遍歷transferUser事件,並返回this.username,其中transferuser是一個自定義事件,功能類似一箇中轉,this.username通過這個事件傳遞給父元件
-->
<template>
<div>
<div>{{msg}}</div>
<span>使用者名稱</span>
<input v-model="username" @change='setUser'>向父元件傳值</button>
</div>
</template>
<script>
export default {
data() {
return {
username: '測試',
};
},
props: {
msg: {
type: String,
},
},
methods: {
setUser() {
this.$emit('transferuser', this.username);
},
},
};
</script>
複製程式碼
5.vue元件之keep-alive
專案中寫vue也沒注意到<keep-alive></keep-alive>
這個元件,最近在深入的研究vue元件的生命週期函式,每一個函式都是幹嘛的,然後其中有activated
和deactivated
這兩個函式與<keep-alive></keep-alive>
這個元件有關
activated
: keep-alive元件啟用時呼叫deactivated
: keep-alive元件停用時呼叫
keep-alive用法
<keep-alive>
包裹動態元件時,會快取不活動的元件例項,而不是銷燬它們<keep-alive>
是一個抽象元件:它自身不會渲染一個DOM元素,也不會出現在父元件鏈中- 當元件在
<keep-alive>
內被切換,它的activated
和deactivated
這兩個生命週期鉤子函式將會被對應執行
具體的例項如下
- 是一個簡單的tab切換,可以嘗試把
<keep-alive>
去掉之後,對比一下,然後就會發現它的好處
test.vue
<template>
<div class="test">
<div class="testNav">
<div :class="{'selected':tab === 1,'testTitle':true}" @click="toTab(1)">標題一</div>
<div :class="{'selected':tab === 2,'testTitle':true}" @click="toTab(2)">標題二</div>
</div>
<div class="container">
<keep-alive>
<Test1 v-if="tab === 1">
</Test1>
<Test2 v-else>
</Test2>
</keep-alive>
</div>
</div>
</template>
<script>
import Test1 from './test1.vue';
import Test2 from './test2.vue';
export default {
data() {
return {
tab: 1,
};
},
components: {
Test1,
Test2,
},
methods: {
toTab(index) {
this.tab = index;
},
},
}
</script>
<style lang="less">
.test {
width: 100%;
.testNav {
height: 60px;
line-height: 60px;
display: flex;
border-bottom: 1px solid #e5e5e5;
.testTitle {
flex: 1;
text-align: center;
}
.selected {
color: red;
}
}
}
</style>
複製程式碼
測試結果如下:
注意看一下頁面和控制檯輸出的資訊,可以更加直觀的注意到<keep-alive>
的作用及activated
和deactivated
這兩個函式什麼時候會被觸發
- 開啟頁面,會出現下面這樣
用setTimeout模擬請求後端介面的場景
- 點選
title2
,出現下面的情況 - 再次點選
title1
,出現下面的情況,你會發現從後端請求的資料會快速顯示出來,但是如果你此時不用
test1.vue
和test2.vue
的相關程式碼如下:
test1.vue
<template>
<div class="test1">
test1
{{testInfo1}}
</div>
</template>
<script>
export default {
data() {
return {
testInfo1: '',
};
},
activated() {
console.log('測試1被啟用');
},
deactivated() {
console.log('測試1被快取');
},
created() {
setTimeout(() => {
this.testInfo1 = '這是測試一的資料';
}, 2000);
},
}
</script>
複製程式碼
test2.vue
<template>
<div>
test2
{{testInfo2}}
</div>
</template>
<script>
export default {
data() {
return {
testInfo2: '',
}
},
activated() {
console.log('測試2被啟用');
},
deactivated() {
console.log('測試2被快取');
},
created() {
setTimeout(() => {
this.testInfo2 = '這是測試二的資料';
}, 2000);
},
}
</script>
複製程式碼
6. 生命週期函式/methods/watch裡面不應該使用箭頭函式
es6的箭頭函式的出現,是我們可以用更少的程式碼實現功能,但是應該注意箭頭函式和普通函式的最大區別是this的指向問題: 箭頭函式的this指向函式所在的所用域,普通函式的this指向函式的呼叫者;
官方文件中特別提醒中已經指出這一點:
vue中生命週期函式, methods, watch 自動繫結 this 上下文到例項中,因此你可以訪問資料,對屬性和方法進行運算。這意味著 你不能使用箭頭函式來定義一個生命週期方法, 這是因為箭頭函式繫結了父上下文,因此 this 與你期待的 Vue 例項不同
7.methods/computed/watch
methods VS computed
我們可以將同一個函式定義為methods或者computed,用這兩種方式,得到的結果是相同的,不同的是computed是基於它們的依賴進行快取的,計算屬性只有在它相關的依賴發生改變時才重新求值;
適用場景:
重新計算開銷很大的話,選computed; 不希望有快取的選methods
computed vs watch
watch 有新舊值兩個引數, 計算屬性沒有,但是計算屬性可以從setter獲得新值
關於computed
對於計算屬性要特別說明一點: vue的計算屬性computed預設只有getter,需要使用getter的時候需要自己加一個setter
export default {
data () {
return {
firstName: '張',
lastName: '三',
};
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
},
},
methods: {
changeFullName () {
this.fullName = '李 四';
}
},
};
其中computed裡的程式碼完整寫法是
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
}
},
複製程式碼
執行 changeFullName 發現報錯[Vue warn]: Computed property "fullame" was assigned to but it has no setter.
我們需要給計算屬性fullName新增一個setter
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
},
複製程式碼
總結
上述這些問題從vue官方文件中均能找到答案,當然想要更深入的理解為什麼,還需要從vue原始碼分析入手;
下一篇文章打算從原始碼入手去解釋這些問題,理解vue整體的程式設計;