一文速覽Vue全棧
Vue 是一套用於構建用戶界面的漸進式框架。與其它大型框架不同的是,Vue 被設計為可以自底向上逐層應用,專注於聲明式渲染視圖層,結合豐富的生態系統和核心插件,致力於簡單靈活快速驅動SPA、MPA等大小型應用。
本文依次介紹 雙向數據綁定、計算屬性、組件、事件機制、插件機制、前端路由、狀態管理和服務端渲染等。
1. Vue實例與數據綁定
實例
Vue.js應用的創建很簡單,通過構造函數 Vue 就可以創建一個 Vue 的根實例,並啟動 Vue;
var app = new Vue({
//選項
});
變量 app 就代表了這個 Vue 實例,事實上幾乎所有的代碼都是一個對象,用來寫入 Vue 實例的選項內的。
首先,必不可少的一個選項就是el,el用於指定一個頁面中己存在的DOM元素來掛載Vue 實例,它可以是 HTMLElement ,也可以是CSS選擇器,比如:
<div id='app'></div>
var app =new Vue({
el: document.getElementByld('app') // 或者是'#app'
});
一個 Vue 應用由一個通過 new Vue() 創建的根Vue實例,以及可選的嵌套的、可復用的組件樹組成。
數據綁定
當一個 Vue 實例被創建時,它將 data 對象中的所有的屬性加入到 Vue 的響應式系統中。當這些屬性的值發生改變時,視圖將會產生“響應”,即匹配更新為新的值。
建議所有會用到的數據都預先在data內聲明,這樣不至於將數據散落在業務邏輯中,難以維護。
Vue實例本身也代理了 data對象里的所有屬性,所以可以這樣訪問:
var app = new Vue({
el: '#app',
data: {
a: 2
}
});
console.log(app.a); // 2
除了顯式地聲明數據外,也可以指向一個己有的變量,並且它們之間默認建立了雙向綁定,當修改其中任意一個時,另一個也會一起變化:
var myData = {
a: 1
};
var app =new Vue({
el: '#app',
data: myData
});
console.log(app.a) ; // 1
//修改屬性,原數據也會隨之修改
app.a = 2;
console.log(myData.a); // 2
//反之,修改原數據, Vue屬性也會修改
myData.a = 3;
console.log(app.a); // 3
生命周期
每個 Vue 實例創建時,都會經歷一系列的初始化過程,同時也會調用相應的生命周期鈎子, 我們可以利用這些鈎子,在合適的時機執行我們的業務邏輯。
Vue的生命周期大致分為四個階段:
beforeCreate(此時date、method和el均沒有初始化,可以在此加載loading)
created(此時date和method初始化完成,但是DOM節點並沒有掛載,判斷是否有el節點,如果有則編譯template,如果沒有則使用vm.
beforeMount(編譯模板,並且將此時在el上掛載一個虛擬的DOM節點)
mounted(編譯模板,且將真實的DOM節點掛載在el上)
beforeUpdate(在數據有更新時,進入此鈎子函數,虛擬DOM被重新創建)
updated(數據更新完成時,進入此鈎子函數)
beforeDestory(組件銷毀前調用,此時將組件上的watchers、子組件和事件都移除掉)
destoryed(組件銷毀后調用)
在創建時,父子組件的生命周期是:
父組件beforeCreated -> 父組件created -> 父組件beforeMounted -> 子組件beforeCreated -> 子組件created -> 子組件beforeMounted -> 子組件mounted -> 父組件mounted。
在銷毀時,父子組件的生命周期是:
父組件beforeDestory -> 子組件beforeDestoryed -> 子組件destoryed -> 父組件destoryed
總之記住,父子組件的生命周期遵循:由外到內,再由內到外。
不要在選項屬性或回調上使用箭頭函數,vue會自動綁定this的上下文環境。
模版語法
Vue.js 使用了基於 HTML 的模板語法,允許開發者聲明式地將 DOM 綁定至底層 Vue 實例的數據。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循規范的瀏覽器和 HTML 解析器解析。
在底層的實現上,Vue 將模板編譯成虛擬 DOM 渲染函數。結合響應系統,Vue 能夠智能地計算出最少需要重新渲染多少組件,並把 DOM 操作次數減到最少。
使用雙大括號(Mustache 語法)“{{}}”是最基本的文本插值方法,它會自動將我們雙向綁定的數據實時顯示出來,
<span>Message: {{ msg }}</span>
如果想顯示{{}}標簽,而不進行替換,使用v-pre即可跳過這個元素和它的子元素的編譯過程,例如 :
<span v-pre>{{這里的內容是不會被編譯的}}</span>
在{{}}中,除了簡單的綁定屬性值外,還可以使用JavaScript表達式進行簡單的運算、三元運算等,例如 :
<div id='app'>
{{ number / 10 ))
{{ isOK ? ’確定’ : ’取消’ }}
{{ text.split(’,’).reverse().join(’,’) }}
</div>
通過使用 v-once 指令,你也能執行一次性地插值,當數據改變時,插值處的內容不會更新。但請留心這會影響到該節點上的其它數據綁定:
<span v-once>這個將不會改變: {{ msg }}</span>
如果你熟悉虛擬 DOM 並且偏愛 JavaScript 的原始力量,你也可以不用模板,直接寫渲染 (render)函數,使用可選的 JSX 語法。
指令
指令(Directives) 是帶有 v- 前綴的特殊特性。指令特性的值預期是單個 JavaScript 表達式 (v-for是例外情況)。指令的職責是當表達式的值改變時,將其產生的連帶影響,響應式地作用於 DOM。
常用的指令如下:
v-cloak
v-cloak不需要表達式,它會在 Vue 實例結束編譯時從綁定的 HTML 元素上移除 ,
經常和css的 display: none配合使用:
<div id='app' v-cloak> {{ message }}</div>
<style>
[v-cloak] {
display: none;
}
</style>
當網速較慢 Vue.js 文件還沒加載完時,在頁面上會顯示{ { message }}的字樣,直到 Vue 創建實例、編譯模板時, DOM 才會被替換,所以這個過程屏幕是有閃動的,只要加上v-cloak就可以避免了。在一般情況下, v-cloak 是一個解決初始化慢導致頁面閃動的最佳實踐,對於簡單的項目很實用,但是在具有工程化的項目里,項目的HTML 結構只有一個空的 div元素,剩余的內容都是由路由去掛載不同組件完成的,所以不再需要 v-cloak;
v-once
v-once 也是一個不需要表達式的指令,作用是定義它的元素或組件只渲染一次,包括元素或組件的所有子節點。首次渲染后,不再隨數據的變化重新渲染,將被視為靜態內容,例如:
<span v-once>{{ message }}</div>
v-once在業務中也很少使用,當你需要進一步優化性能時,可能會用到。
v-html
為了輸出真正的 HTML,需要使用 v-html 指令;
var contenthtml = `<span>哈哈大笑😄</span>`;
<span v-html="contenthtml"></span>
你的站點上動態渲染的任意 HTML 可能會非常危險,因為它很容易導致 XSS 攻擊。請只對可信內容使用 HTML 插值,絕不要對用戶提供的內容使用插值,必要時在服務端進行提前過濾轉義。
v-if
用於條件性地渲染一塊內容,該指令是惰性的,當初始值為false時dom節點不會進行渲染,是針對dom節點的移除和添加,例如 :
<div id='app'>
<span v-if='false'>{{ message }}</span>
</div>
// 會被渲染為以下節點:
<div id='app'></div>
v-show
v-show的用法與v-if基本一致,只不過v-show是改變元素的css屬性display。當v-show 表達式的值為 false 時, 元素會隱藏,查看 DOM 結構會看到元素上加載了內聯樣式 display: none; 例如 :
<div id='app'>
<span v-show='false'>{{ message }}</span>
</div>
// 會被渲染為以下節點
<div id='app'>
<span style="display: none;”>哈哈大笑😄</span>
</div>
v-show不能在<template>上使用。相比之下, v-if更適合條件不經常改變的場景,因為它切換開銷相對較大,而 v-show 適用於頻繁切換條件。
v-else
v-else 元素必須緊跟在帶 v-if 或者 v-else-if的元素的后面,否則它將不會被識別。例如 :
<span v-if='show'>{{ message.a }}</span>
<span v-else>{{ message.b }}</span>
v-for
當需要將一個數組遍歷或枚舉一個對象循環顯示時,就會用到列表渲染指令 v-for。它的表達式需結合 in來使用,類似 item in items 的形式,看下面的示例 :
<ul>
<li v-for=”book in books” :key="book.id">{{ book.name }}</li>
</ul>
當 Vue 正在更新使用 v-for 渲染的元素列表時,它默認使用“就地更新”的策略。如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序,而是就地更新每個元素,並且確保它們在每個索引位置正確渲染,所以需要為每個元素設置唯一的key。
v-bind
v-bind用於動態更新 HTML 元素上的屬性,比如 id、class等;
<div id=”app”>
<a v-bind:href=”url”〉鏈接</a>
<img v-bind:src=”imgUrl”>
</div>
以上是 v-bind 最基本的用法,它在 Vue.js 組件中還有着極其重要的作用,可以簡寫為:;
v-on
v-on在事件綁定上,類似原生 JavaScript 的 onclick等寫法,也是在 HTML 上進行監昕的,例如:
<button v-on:click=”counter++”>+ 1</button>
v-on:click的表達式可以直接使用 JavaScript 語句,也可以是一個在 Vue實例中 methods選項內的函數名,例如:
<button v-on:click=”handleAdd(1)”>+ 1</button>
methods: {
handleAdd: function(count) {
this.counter += count;
}
}
v-on:click調用的方法名后可以不跟括號“()” ;
<a href=”http://www.apple.com” @click="handleClick ('禁止打開',$event)">打開鏈接 </a>
methods: {
handleClick: function(message, event) {
event.preventDefault();
}
}
v-on可以簡寫為@。
-
v-model
v-model用於表單,進行雙向數據綁定。例如:
<input type=”text” id="name" v-model="fullname" />
<p>你好,{{fullname}} !</p>
過濾器
Vue.js 允許你自定義過濾器,可被用於一些常見的文本格式化。過濾器可以用在兩個地方:雙花括號插值和 v-bind 表達式。過濾器應該被添加在 JavaScript 表達式的尾部,由“管道”符號指示:
<!-- 在雙花括號中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
你可以在一個組件的選項中定義本地的過濾器:
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
// 或者在創建 Vue 實例之前全局定義過濾器:
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
過濾器可以串聯:
{{ message | filterA | filterB }}
過濾器是 JavaScript 函數,因此可以接收參數:
{{ message | filterA('arg1', arg2) }}
這里,filterA 被定義為接收三個參數的過濾器函數。其中message 的值作為第一個參數,普通字符串 'arg1' 作為第二個參數,表達式 arg2 的值作為第三個參數。
2. 計算屬性與響應式依賴
在一個計算屬性里可以完成各種復雜的邏輯,包括運算、函數調用等,只要最終返回 一個結果就可以。計算屬性還可以依賴多個Vue實例的數據,只要其中任一數據變化,計算屬性就會重新執行,視圖也會更新。
<span>{{fullName}}</span>
computed: {
fullName: {
get: function() {
return this.firstName + ' ' + this.lastName;
},
set: function(newValue) {
var names= newValue.split (' ') ;
this.firstName = names[O);
this.lastName = names[names.length - 1];
}
}
}
我們可以通過在表達式中調用方法來達到同樣的效果:
<span>{{getName()}}</span>
methods: {
getName: function() {
return this.firstName + ' ' + this.lastName;
}
}
我們可以將同一函數定義為一個方法而不是一個計算屬性。兩種方式的最終結果確實是完全相同的。然而,不同的是計算屬性是基於它們的響應式依賴進行緩存的。只在相關響應式依賴發生改變時它們才會重新求值。
雖然計算屬性在大多數情況下更合適,但有時也需要一個自定義的偵聽器。這就是為什么 Vue 通過 watch選項提供了一個更通用的方法,來響應數據的變化。當需要在數據變化時執行異步或開銷較大的操作時,這個方式是最有用的。
watch: {
message(newvalue, oldValue) {
newvalue && this.getNewMessage();
}
}
3. 組件
組件是可復用的 Vue 實例,通常一個應用會以一棵嵌套的組件樹的形式來組織:
組件 ( Component)是 Vue 最核心的功能,也是整個框架設計最精彩的地方,當然也是最難 掌握的。組件需要注冊后才可以使用,注冊有全局注冊和局部注冊兩種方式。全局注冊后, 任何 Vue 實例都可以使用。
全局注冊示例代碼如下 :
Vue.component(’my-component’, {
//選項
})
在 Vue 實例中,使用 components選項可以局部注冊組件,注冊后的組件只有在該實例作用域下有效。組件中也可以使用components選項來注冊組件,使組件可以嵌套。示例代碼如下:
<div id=”app”>
<my-component></my-component>
</div>
<script>
var Child = {
template: '<div>局部注冊組件的內容</div>'
};
components: {
’my-component’: Child
}
</script>
Vue 組件的模板在某些情況下會受到HTML的限制,比如<table>內規定只允許是<tr>、<td>、<th>等這些表格元素,所以在<table>內直接使用組件是無效的。這種情況下可以使用特殊的is屬性來掛載組件,示例代碼如下 :
<div id=”app”>
<table>
<tbody is=”my-component”></tbody>
</table>
</div>
<script>
var Child = {
template: '<div>局部注冊組件的內容</div>'
};
components: {
’my-component’: Child
}
</script>
常見的限制元素還有<ul>、<ol>、<select>;如果使用字符串模板是不受限制的;
除了 template選項外,組件中還可以像 Vue實例那樣使用其他的選項,比如 data、 computed、 methods 等。但是在使用 data 時和實例稍有區別, data 必須是函數,然后將數據 return 出去, 例如:
Vue.component('my-component', {
template: '<div>{{ message }}</div>',
data: function() {
return {
message: 'aaa'
}
}
});
props傳遞數據、 events觸發事件和slot內容分發就構成了Vue組件的3個API來源,再復 雜的組件也是由這 3 部分構成的。
-
props
組件不僅僅是要把模板的內容進行復用,更重要的是組件間要進行通信。通常父組件的模板 中包含子組件,父組件要正向地向子組件傳遞數據或參數,子組件接收到后根據參數的不同來渲染不同的內容或執行操作。這個正向傳遞數據的過程就是通過 props 來實現的。
在組件中,使用選項 props 來聲明需要從父級接收的數據, props 的值可以是兩種, 一種是字符串數組,一種是對象:
// 字符串數組
Vue.component ('my-component', {
props: [ ’ message ’ ] ,
template: ’<div>{{ message }}</div>’
});
// 對象形式
Vue.component ('my-component', {
props: {
//必須是數字類型
propA : Number,
//必須是字符串或數字類型
propB : [String , Number],
//布爾值,如果沒有定義,默認值就是 true
propC: {
type: Boolean,
default: true
},
//數字,而且是必傳
propD: {
type: Number,
required: true
},
//如果是數組或對象,默認值必須是一個函數來返回
propE: {
type: Array,
default: function() {
return [];
}
}
} ,
template: ’<div>{{ message }}</div>’
});
-
events
用集中式的事件中間件可以做到簡單的數據傳遞,這會讓組件之間的通信非常順利,即使是兄弟組件。因為 Vue 通過事件發射器接口執行實例,實際上你可以使用一個空的 Vue 實例,通過單獨的事件中心管理組件間的通信:
var eventHub = new Vue();
然后在組件中,可以使用 $emit, $on, $off 分別來分發、監聽、取消監聽事件:
eventHub.$emit('delete', id);
eventHub.$on('delete', this.delete);
eventHub.$off('delete', this.delete)
-
slot
Vue 實現了一套內容分發的 API,這套 API 的設計靈感源自 Web Components 規范草案,將
-普通插槽:
// 父組件
<submit-button>save</submit-button>
// 子組件
<button type="submit">
<slot>Submit</slot>
</button>
// 最終渲染
<button type="submit">
Save
</button>
-具名插槽:
// 父組件
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Heres some contact info</p>
</template>
</base-layout>
// 子組件
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
// 最終渲染
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Heres some contact info</p>
</footer>
</div>
-作用域插槽:
// 父組件
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
// 子組件
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
插槽對於組件意義非凡,可以自定義拓展實現很多復雜的業務場景,且低耦合;
-
通信方式
-props/$emit
父子組件進行通信最常用這種方式:
// 父組件
<template>
<child :updateTitle='updateTitle'></child>
</template>
<script>
export {
data: {
return {
title: '我是父組件'
}
},
methods: {
updateTitle(value) {
this.title = value;
}
}
}
</script>
// 子組件
<template>
<header>
<h1 @click="changeTitle">{{title}}</h1>
</header>
</template>
<script>
export {
props: {
title: {
type: String,
default: '我是父組件'
}
},
methods: {
changeTitle() {
this.$emit('updateTitle', '我是子組件');
}
}
}
</script>
-eventBus
父子、兄弟組件之間都可以使用這種方式:
var Event=new Vue();
Event.$emit(事件名,數據);
Event.$on(事件名,data => {});
$emit負責分發事件,
vuex
Vuex 實現了一個單向數據流,在全局擁有一個 State 存放數據,當組件要更改 State 中的數據時,必須通過 Mutation 進行,Mutation 同時提供了訂閱者模式供外部插件調用獲取 State 數據的更新。而當所有異步操作(常見於調用后端接口異步獲取更新數據)或批量的同步操作需要走 Action,但 Action 也是無法直接修改 State 的,還是需要通過 Mutation 來修改 State 的數據。最后,根據 State 的變化,渲染到視圖上。
一般用於比較復雜的大中型應用,一個應用只有一個store,通過插件的機制注入應用本身,下面的全部組件都可以訪問到store中的數據,便於數據的管理和追蹤變化;
$attrs/$listeners
$attrs包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class 和 style 除外),並且可以通過 v-bind="on負責監聽了自定義事件,因為有時不確定何時會觸發事件,一般會在mounted或created鈎子中來監聽。vuex
Vuex實現了一個單向數據流,在全局擁有一個State存放數據,當組件要更改State中的數據時,必須通過Mutation進行,Mutation同時提供了訂閱者模式供外部插件調用獲取State數據的更新。而當所有異步操作(常見於調用后端接口異步獲取更新數據)或批量的同步操作需要走Action,但Action也是無法直接修改State的,還是需要通過Mutation來修改State的數據。最后,根據State的變化,渲染到視圖上。一般用於比較復雜的大中型應用,一個應用只有一個sto
