title: Vue.js框架
date: 2019-08-06 20:00:27
tags: [前端框架,Vue.js]
categories: Web前端
一、關于Vue.js
Vue (讀音 /vju?/,類似于 view) 是一套用于構建用戶界面的漸進式框架。與其它大型框架不同的是,Vue 被設計為可以自底向上逐層應用。Vue 的核心庫只關注視圖層,不僅易于上手,還便于與第三方庫或既有項目整合。另一方面,當與現代化的工具鏈以及各種支持類庫結合使用時,Vue 也完全能夠為復雜的單頁應用提供驅動。 --摘自官網
總體來說作為一門前端框架,Vue.js本身可以最大程度減少用戶操作Dom的工作
二 、數據的雙向綁定
Vue.js可以將簡潔的模板語法來聲明式的將數據渲染到頁面中,且聲明的數據是雙向的。而且對于一些特殊的標簽可以通過綁定元素特性。例如
<div id="app">
姓氏:<input type="text" v-model = 'firstname'>
名字:<input type="text" v-model = 'lastname'>
<p>{{firstname+lastname}}</p>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el:'#app', //綁定id=app的元素
data: { //聲明數據
firstname:'',
lastname:''
}
})
解釋一下上例,功能:輸入姓氏和名字來合成名字。
首先為兩個Input綁定了v-model,因為數據是雙向的,默認值為Vue構造函數中聲明的“”,但是當修改文本框中的值時,app.firstname
和app.lastname
也會對應發生改變。
三、 指令
在上例中使用的v-model
就是一個指令,它可以用于綁定<input>、<select>、<textarea>
、components標簽的數據。指令一般都以v-
開頭。
3.1 用v-on
指令綁定監聽
基本格式:(事件方法內容可以直接為Js語句也可以是方法名)簡寫格式為@
v-on: 監聽方式='事件方法'
例如:功能:輸入價格,可以利用按鈕來增減數量,并顯示價格。通過v-on
指令來綁定click
事件,
方法應當掛載在methods
下。
<div id="app">
<div>
<label for="">價格</label>
<input type="text" v-model='price'>
</div>
<div>
數量:<button v-on:click='jian'>-</button><span>{{ number }}</span><button v-on:click='jia'>+</button>
</div>
<div>
<strong>{{ price*number }}</strong>
</div>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
new Vue({
el:'#app',
data:{
price:0,
number:0
},
methods:{
jian:function(){
--this.number
},
jia:function(){
++this.number
}
}
})
</script>
3.2 條件與循環
-
v-if
作為條件指令,在引號內,值可以為綁定的數據,也可以直接填寫true
或false
,當然還包括
v-else
和v-else-if
進行配套使用
<div id="app3">
<span v-if='true'>NiKon</span>
<span v-if='nonSeen'>Canen</span>
</div>
new Vue({
el:"#app3",
data:{
nonSeen: true
}
})
結果:NiKon Canen
- v-for
作為循環指令。在要要遍歷的標簽中綁定該指令,值的格式為
'xx[,index] in xxs'`
<div id="app4">
<ul>
<li v-for='todo,index in todos'>
{{ index+'·'+todo.text }}
</li>
</ul>
</div>
new Vue({
el:'#app4',
data: {
todos:[
{
index:1,
text:'wowo'
},
{
index:2,
text:'wowo'
},
{
index:3,
text:'wowo'
}
]
}
})
結果:
0·wowo
1·wowo
2·wowo
3.3 v-model指令
用于作為表單控件的雙向綁定的指令,格式:v-model="xxx"
3.4 v-bind屬性渲染指令
對于原有的HTML標簽屬性可以通過v-bind
來渲染,為單向數據綁定。簡寫格式為:
<input type="text" v-bind: value="xxx">
或者
<input type="text" :class="{xxx:true of false}">
3.5 v-once只渲染一次指令
數據不再綁定,僅在在第一次加載頁面時進行渲染。
3.6 v-text和v-cloak指令
在vue管理的模板入口節點作用v-cloak
這兩條指令是為了解決模板語法在頁面加載時會出現頁面抖動。
<h1 v-text="message" ><h1>
<style>
[v-cloak]{
display:none;
}
</style>
<div v-cloak>
<h1>{{ message }}</h1>
</div>
一般v-text
使用較少
3.7 v-show顯示標簽指令
這條指令是在渲染層上將改標簽隱藏。
<h1 v-show="false">hehe</h1>
和v-if
不同的是,該指令是將改標簽的樣式設置為display: none
,而v-if
是直接將該標簽從Dom樹中移除。
3.8 v-pre跳過編譯指令
跳過不需要Vue編譯的節點和其子節點,顯示其原始內容。可以加快編譯。??
四、 自定義指令
如果到了不得不自己去操作Dom元素時,且沒有Vue內置指令來實現需求時。可以使用自定義指令來解決需求。
4.1 定義自定義指令
自定義指令分為全局和局部,即可以定義為在所有組件下都可以使用的自定義指令,也可以定義為只能在當前組件下使用的自定義指令。
4.1.1 全局自定義指令
Vue.directive('指令名',{
鉤子函數
})
4.1.2 局部自定義指令
new Vue({
...,
directives:{
指令名:{
鉤子函數
}
},
...
})
4.2 鉤子函數
鉤子函數就是當自定義指令在何時出發什么樣的動作
形式都為鉤子名:function([el[,binding]])
其中,el
是當前綁定指令的Dom元素,而binding是指令對象其中value
屬性包含了指令被賦的值。
Vue官方文檔中給出的鉤子函數有5個:
-
bind
-在指令被綁定時執行一次,無法讀取被綁定的Dom元素的父節點el.parentNode
-
inserted
-和bind一樣是在指令被綁定時執行一次,但是在bind
后面執行。并且可以讀取被綁定的Dom元素的父節點 -
update
-當指令所在的被Vue管理的整個模板更新時執行,binding.value是更新后的值 -
componendUpdated
-和update一樣,但是binding.value是更新前的值 -
unbind
-當指令被解綁時執行,例如清除定時器(移除Dom時自動解綁)
4.3 復現一個v-show
<div id="app">
<input type="text" v-focus>
<div style="width: 100px;height: 100px ;background-color:#005151" v-myshow = "show" ></div>
</div>
4.3.1 全局定義方式
Vue.directive('myshow',{
bind:function(el,binding){
if(binding.value){
el.style.display = "block"
}else{
el.style = "none"
}
},
// inserted:function(){
// console.log("inserted is called")
// },
update:function(el,binding){
if(binding.value){
el.style.display = "block"
}else{
el.style.display= "none"
}
},
// componentUpdated:function(){
// console.log("componentUpdated is called")
// },
// unbind:function(){
// console.log("unbind is called")
// }
})
大多數情況都是bind & update鉤子函數配合使用的比較多,且大多情況下兩者的代碼是一樣的,所以可以使用簡寫方式
Vue.directive('myshow',function(el,binding){
if(binding.value){
el.style.display = "block"
}else{
el.style.display= "none"
}
})
4.3.2 局部定義
app = new Vue({
el:'#app',
data:{
show: true
},
directives:{
myshow:function(el,binding){
if(binding.value){
el.style.display = "block"
}else{
el.style.display= "none"
}
}
}
五、 計算屬性和偵聽器
5.1 計算屬性computed
計算屬性要在computed
對象中定義。
計算屬性的本質就是方法,在使用時就像data
中定義的屬性那樣去使用。
每一個計算屬性都必須有一個getter,還可以根據需求來加入一個setter。
- 只包含getter的計算屬性的一般使用方法
computed:{
remaingCount:{
get(){
return this.todoList.filter(item=>!item.completed).length || 0
}
}
}
- 對于只包含getter的計算屬性還有一種簡寫方法
computed:{
remaingCount(){
return this.todoList.filter(item=>!item.completed).length || 0
}
},
- 同時存在getter和setter的標準計算屬性定義方式
computed:{
toggleAll:{
get(){
return this.todoList.every(item => item.completed)
},
set([value]){
let boolean = !this.toggleAll
this.todoList.forEach(element => {
element.completed = boolean
});
}
}
}
5.2 偵聽器watch
偵聽器要在watch
對象中定義。
偵聽器的作用是每當偵聽對象發生改變時就作出反應。
同情況下計算屬性要比偵聽器合適,但是還有一些自定義業務需求需要使用偵聽器。
-
hanlder:function(val,oldval)
是在觸發監聽對象時去執行的那個自己定義的方法 -
deep:boolean
當監聽對象為一個引用類型的對象時,只能監聽到對象整體,無法監聽到對象內的個體。當設置為true時即可同時監聽到對象內的個體。 -
immediate:boolean
在監聽開始前就會調用該監聽器下的hanlder
方法
watch:{
todoList:{
handler(val,oldVal){
window.localStorage.setItem("todoList",JSON.stringify(val))
},
deep: true //深度監聽
},
//只包含handler時的簡寫方法
toggleAll(){
console.log('計算屬性也可以被監聽哦')
}
},
六、 組件
Vue中,可以將單獨功能的模塊可以拆分為一個一個的組件。一個項目的組件構成通常為一個樹形結構。一個典型的TodoMVC案例可以拆封為這樣一個組件示例圖:
6.0 組件的使用方式
定義好的屬性,只需要像使用html標簽一樣使用,只不過把標簽名換為組建名就可以了
6.1 組件定義方式
組件定義方式和自定義指令一樣,有全局和局部之分。
- 全局定義
Vue.component('myComponent',{
template:`<p>hello</p>`,
data:function(){
return {
...,
...
}
}
})
- 局部定義
new Vue({
el:'#app',
template:`<app></app>`
components:{
app:{
template = `<header></header> .. `,
components:{
header:{
template:`<p>hello i am header</p>`,
data:function(){
return {
...,
...
}
},
//當然組件中也可以放子組件
components:{
....
}
},
...其他組件
}
}
}
})
在Vue根實例中創建的這些組件的父子節點可能不同,但是他們的根組件都是這個Vue實例。因為這個Vue實例本身也是一個組件。
6.2 組件間的通信
6.2.1 直接父子通信
- 父傳子通信(
prop down
):
要將父組件中的屬性值傳送給其子組件
-
需要在父組件中的模板中調用子組件的部分加上v-bind屬性(如傳送name屬性給其子組件son)
const Father = { template: `<son v-bind:name="name"></son>`, data(){ return { name:"jack" } } componends:{ son } }
-
在子組件體中,使用
props
數組屬性將name接收過來,之后就可以像自己的data定義的屬性一樣去用了。const son = { template:`<p> my father is {{ name }}</p>`, props:["name"] }
注意:Vue不允許在子組件去修改基本類型的父組件傳過來的值。但是很不幸。因為傳過來的數據如果是引用l類型的話,你可以修改成功,且不會報錯,但是請不要這樣去做!,如有子元素要修改穿過來的值的需求,請使用子傳夫通信方法*
- 子傳父通信(
Event up
)
父組件的值就要在父組件中進行,所以子組件可以把修改好的父組件數據整一個副本(不要修改原數據),然后通過訂閱事件的方法來傳給父組件。
-
再子組件的方法中生成數據副本
const son = { template:`<button @click = "handleUpdateName"> my father is {{ name }}</button>`, props:["name"], methods:{ handleUpateName(){ let value = 'maojiankai' this.$emit('UpdatedName',value) // 向父組件提交UpdatedName事件 } } }
-
在父組件中訂閱對應事件并處理數據
const Father = { template: `<son v-bind:name="name" @UpatedName="handleName"></son>`, //訂閱UpdatedName事件 data(){ return { name:"jack" } }, componends:{ son }, methods:{ handleName(name){ //對應的處理 this.name = name } } }
-
非父子關系的組件通信
- 簡單環境中
Event Bus
- 設組件a,b非父子關系但他們同屬于一個Vue根實例,所以在Vue的根實例之前創建一個名字為
bus
的空Vue實例
var bus = new Vue()
- 在組件A中向bus提交自定義事件
bus.$emit('Edited',value)
- 在組件B的創建鉤子函數中,訂閱bus中組件A提交的事件
bus.$on('Edited',function(e){ ... })
- 復雜環境中
VueX
(狀態管理模式)
- 簡單環境中
七、插槽
7.1 關于插槽solt
在父組件調用子組件時,會使用子組件的模板HTML內容,但是如果父組件想要加入一些自己的內容到使用子組件的的模板HTML內容可能就是個問題了,尤其是當父組件多次引用子組件時,想要加入不同得內容到子組件模板當中去。
為此,Vue提供了一個插槽---slot
標簽。在子組件得template
中加入一個slot
標簽后,父組件只需在引用子組件的標簽內加入自己想加入的內容即可。
需要說明的是,如果想要插槽具有默認的內容,只需要在加入的slot
標簽內添加你需要的標簽即可。
<div id='app'>
<cnp><button>hehe</button></cnp>
<cnp></cnp>
</div>
<template id="cnp">
<div>
<h2>我是組件</h2>
<p>123456789</p>
<slot ><button>footer</button></slot>
</div>
</template>
const app = new Vue({
el:'#app',
components:{
cnp:{
template:'#cnp'
}
}
})
7.2 具名插槽
如果子組件存在多個不同的插槽,當你在調用時只想改動其中的一小部分插槽,其他的插槽都保留其原始值時,就需要用到Vue提供的具名插槽了。
具名插槽的定義是在插槽的name
屬性上設置插槽的名稱
<template>
<slot name="left"><span>left</span></slot>
<slot name="center"><span>center</span></slot>
<slot name="right"><span>right</span></slot>
<slot><span>defalut</span></slot>
</template>
具名插槽的使用需要在先使用設置了v-slot:name
指令的template
標簽包裹要改動的內容
<div id='app'>
<cnp>
<template v-slot:center>
<h1 >123</h1>
</template>
</cnp>
<cnp></cnp>
</div>
7.3 作用域插槽
首先是Vue的編譯作用域。
摘自Vue官網:父級模板里的所有內容都是在父級作用域中編譯的;子模板里的所有內容都是在子作用域中編譯的。
其實很意思很簡單,比如:
<div id="app">
<cnp>
<button v-show='show'>hello</button>
</cnp>
</div>
<!-- 子組件模板 -->
<template id='cnp'>
<div>
<h1>我是模板</h1>
<slot v-show='show'></slot>
</div>
</template>
<!-- 入口Vue實例 -->
<script>
const app = new Vue({
el:'#app',
data:{
show:false
},
components:{
cnp:{
template:'#cnp',
data(){
return {
show:true
}
}
}
}
})
</script>
通過不斷調試Vue實例和組件cnp
中的show,可以看出。模板tempate
屬于組件cnp
,所以模板中的show是組件cnp
中的show,而在實際調用中的show是屬于Vue實例中的show。
這說明Vue的是具有編譯作用域的,且父級模板里的所有內容都是在父級作用域中編譯的;子模板里的所有內容都是在子作用域中編譯的。
雖然Vue的編譯作用域是合理且安全的。但是影響到了一種插槽需求,就是當引用該子組件時,需要用到一些將子組件的一些數據以不同的形式在組件的插槽內顯示。
所以,Vue提供了作用域插槽。
定義v-bind:屬性名='表達式'
<template id="cnp">
<div>
<h2>我是組件</h2>
<p>123456789</p>
<!-- 定義一個隨意名稱的屬性 -->
<slot :data='pLanguages'></slot>
</div>
</template>
使用v-solt:name或者default='數據名'
(name是具名時用于替換指定插槽,default是默認插槽)
<div id='app'>
<cnp>
<!-- 完整的 -->
<template v-slot:default='slot'>
<span>{{ slot.data.join(' - ') }}</span>
</template>
</cnp>
</div>
如果像上例一樣使用的是默認插槽,還可以使用簡寫方式
簡寫時請勿與具名插槽混用
<div id='app'>
<cnp>
<!-- 簡寫的 -->
<template v-slot='slot'>
<span>{{ slot.data.join(' - ') }}</span>
</template>
</cnp>
</div>
如果是多個插槽,則需要使用完整的使用格式。
Example
比如現在要來實現將子組件中提供的編程語言數組在插槽中以兩種不同形式顯示
<div id='app'>
<cnp>
<template v-slot:default='slot'>
<span>{{ slot.data.join(' - ') }}</span>
</template>
</cnp>
<cnp>
<template v-slot='slot'>
<span>{{ slot.data.join(' * ') }}</span>
</template>
</cnp>
</div>
<template id="cnp">
<div>
<h2>我是組件</h2>
<p>123456789</p>
<slot :data='pLanguages'></slot>
</div>
</template>
<script>
const app = new Vue({
el:'#app',
components:{
cnp:{
template:'#cnp',
data(){
return {
pLanguages:['Java','C++','JavaScript','Python','Php']
}
}
}
}
})
</script>