文章首發於我的博客javascript
使用vue的時候常常會遇到一些問題,其實仔細閱讀查閱官方文檔,就會發現文檔中已提到一些格外須要注意的點; 爲了深刻的理解官方文檔中對這些問題的解釋,查閱了一些資料,再加上本身的理解,整理了一些常見的問題;若是哪方面解釋的不太合理但願各路大神指出;html
文章篇幅較長,可是很實用;vue
類比引用數據類型 Object是引用數據類型, 每一個組件的data 都是內存的同一個地址,一個數據改變了其餘也改變了;java
那麼用什麼方法可使每一個組件的data相互獨立,不受影響呢?git
當一個組件被定義,data 必須聲明爲返回一個初始數據對象的函數,由於組件可能被用來建立多個實例。若是 data 仍然是一個純粹的對象,則全部的實例將共享引用同一個數據對象!經過提供 data 函數,每次建立一個新實例後,咱們可以調用 data 函數,從而返回初始數據的一個全新副本數據對象。es6
經過數組的下標去修改數組的值,數據已經被修改了,可是不觸發updated函數,視圖不更新,github
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,視圖更新vuex
data() {
userProfile: {
name: '小明',
}
}
複製代碼
想要給userProfile加一個age屬性後端
addProperty () {
this.userProfile.age = '12';
console.log(555, this.userProfile);
}
複製代碼
執行addProperty函數時,打印以下api
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知道你添加了屬性, 它會給你作處理
keep-alive
,組件被激活時調用keep-alive
,組件被移除時調用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';查看效果
複製代碼
vue中使用props向子組件傳遞數據 1): 子組件在props中建立一個屬性,用於接收父組件傳過來的值 2): 父組件中註冊子組件 3): 在子組件標籤中添加子組件props中建立的屬性 4): 把須要傳給子組件的值賦給該屬性
子組件主要經過事件傳遞數據給父組件 1), 子組件中須要以某種方式,例如點擊事件的方法來觸發一個自定義事件 2),將須要傳的值做爲$emit的第二個參數,該值將做爲實參數傳給相應自定義事件的方法 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>
複製代碼
項目中寫vue也沒注意到<keep-alive></keep-alive>
這個組件,最近在深刻的研究vue組件的生命週期函數,每個函數都是幹嗎的,而後其中有activated
和deactivated
這兩個函數與<keep-alive></keep-alive>
這個組件有關
activated
: keep-alive組件激活時調用deactivated
: keep-alive組件停用時調用<keep-alive>
包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們<keep-alive>
是一個抽象組件:它自身不會渲染一個DOM元素,也不會出如今父組件鏈中<keep-alive>
內被切換,它的activated
和deactivated
這兩個生命週期鉤子函數將會被對應執行<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>
複製代碼
es6的箭頭函數的出現,是咱們能夠用更少的代碼實現功能,可是應該注意箭頭函數和普通函數的最大區別是this的指向問題: 箭頭函數的this指向函數所在的所用域,普通函數的this指向函數的調用者;
官方文檔中特別提醒中已經指出這一點:
vue中生命週期函數, methods, watch 自動綁定 this 上下文到實例中,所以你能夠訪問數據,對屬性和方法進行運算。這意味着 你不能使用箭頭函數來定義一個生命週期方法, 這是由於箭頭函數綁定了父上下文,所以 this 與你期待的 Vue 實例不一樣
咱們能夠將同一個函數定義爲methods或者computed,用這兩種方式,獲得的結果是相同的,不一樣的是computed是基於它們的依賴進行緩存的,計算屬性只有在它相關的依賴發生改變時才從新求值;
從新計算開銷很大的話,選computed; 不但願有緩存的選methods
watch 有新舊值兩個參數, 計算屬性沒有,可是計算屬性能夠從setter得到新值
對於計算屬性要特別說明一點: 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總體的程序設計;