vue中需要注意的問題總結(上)


React 與其說是一種框架,倒不如說是一種開發范式。它的核心理念非常簡單:

界面/視圖就是數據結構的可視化表達
UI = f(data)

而界面/視圖由組件組合而來
UI = f1(data) + f2(data) + f3(data) + ...

That's all.

React 扮演的角色就是上述公式中的 f,它完全是函數式的,組件就是函數,給它一個輸入(傳參),它就返回一個輸出(UI)。只有理解了這種開發理念,用 React 才會得心應手。如果只是去學“語法”,僅滿足於“能用”,React 確實會顯得非常怪異而麻煩。

Vue 的核心理念與 React 其實是一致的,但在 API 的實現上 Vue 對於用戶而言更加友好。不像 React,Vue 不會要求用戶“Think in Vue”,因為不需要,Vue 是一個漸進式的框架,它並沒有大幅改變用戶原有的開發方式,它既可以作為插件植入老的 jQuery 項目,也可以作為整個項目的核心框架層。並且 Vue 提供了各種實用的 API,而 React 本身的 API 非常少,許多功能需要自行封裝或引用現成的組件。

前言

使用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函數,視圖不更新,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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,視圖不更新
執行changeItem1, 控制台打印 222 ‘x’, 數據更新 ‘x’; 觸發updated,視圖更新

場景2: vue中檢測不到對象屬性的添加和刪除

1
2
3
4
5
data() {
userProfile: {
name: '小明',
}
}

想要給userProfile加一個age屬性

1
2
3
4
addProperty () {
this.userProfile.age = '12';
console.log(555, this.userProfile);
}

 

執行addProperty函數時,打印如下

1
555 { name: '小明', age: '12'}

但是沒有觸發updated, 視圖未更新
改成下面這種

1
2
3
4
addProperty () {
this.$set(this.userProfile, 'age', '12');
console.log(666, this.userProfile);
}

 

再次執行, 數據發生變化, 觸發updated, 視圖更新;

有時你想向已有對象上添加一些屬性,例如使用 Object.assign() 或 _.extend() 方法來添加屬性。但是,添加到對象上的新屬性不會觸發更新。在這種情況下可以創建一個新的對象,讓它包含原對象的屬性和新的屬性:

1
2
// 代替 `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:下面代碼可以直接復制出去執行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!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: 完成掛載

title

1
打開命令行在命令行中輸入vm.a = 'change';查看效果

 

title

4.vue組件通信

1.父組件給子組件傳遞數據

vue中使用props向子組件傳遞數據
1): 子組件在props中創建一個屬性,用於接收父組件傳過來的值
2): 父組件中注冊子組件
3): 在子組件標簽中添加子組件props中創建的屬性
4): 把需要傳給子組件的值賦給該屬性

2.子組件向父組件傳遞數據

子組件主要通過事件傳遞數據給父組件
1), 子組件中需要以某種方式,例如點擊事件的方法來觸發一個自定義事件
2),將需要傳的值作為$emit的第二個參數,該值將作為實參數傳給相應自定義事件的方法
3),在父組件中注冊子組件並在子組件標簽上綁定自定義事件的監聽

3.子組件向子組件傳遞數據

vue找那個沒有直接子組件對子組件傳參的方法,建議將需要傳遞數據的在組件,都合並為一個組件,如果一定需要子組件對子組件傳參,可以先傳到父組件,再傳到子組件,為了方便開發,vue推出了一個狀態管理工具vuex,可以啃方便的實現組件之間的參數傳遞

具體的實例代碼如下:可以自行參考相關代碼在編輯器中嘗試
父組件向子組件傳遞數據

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 父組件向子組件傳遞數據
<!--
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>

在父組件
<Child @transferuser='fn'/>

export default {
     name: 'parent',
     data: function () {
},
components: {
     child
},
methods: {
    fn: function (msg) { //回調方法,接收子組件傳的參數
         this.msg = msg;
}
},
mounted: function () {
});
}
};

 

5.vue組件之keep-alive 

項目中寫vue也沒注意到<keep-alive></keep-alive>這個組件,最近在深入的研究vue組件的生命周期函數,每一個函數都是干嘛的,然后其中有activateddeactivated這兩個函數與<keep-alive></keep-alive>這個組件有關

  • activated: keep-alive組件激活時調用
  • deactivated: keep-alive組件停用時調用

    keep-alive用法

  • <keep-alive>包裹動態組件時,會緩存不活動的組件實例,而不是銷毀它們
  • <keep-alive>是一個抽象組件:它自身不會渲染一個DOM元素,也不會出現在父組件鏈中
  • 當組件在<keep-alive>內被切換,它的activateddeactivated這兩個生命周期鈎子函數將會被對應執行

    具體的實例如下

  • 是一個簡單的tab切換,可以嘗試把<keep-alive>去掉之后,對比一下,然后就會發現它的好處

test.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<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>的作用及activateddeactivated這兩個函數什么時候會被觸發

  • 打開頁面,會出現下面這樣
    1

用setTimeout模擬請求后端接口的場景

  • 點擊title2,出現下面的情況
    2
  • 再次點擊title1,出現下面的情況,你會發現從后端請求的數據會快速顯示出來,但是如果你此時不用
    3

test1.vuetest2.vue的相關代碼如下:

test1.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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源碼分析入手;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM