Vue-2 vue-cli,生命週期,computed,watch,自定義指令
vue-cli環境配置、項目安裝、目錄樹
我們使用webpack也可以手動安裝配置vue腳手架環境,但是有一套更簡單一點的vue-cli的構建工具,大大降低了webpack的使用難度。
一、配置vue的腳手架環境
npm install -g vue-cli
完成之後查看vue版本:
vue -V
如果終端承認vue命令,那麼腳手架安裝成功,否則要麼腳手架安裝失敗,要麼就是需要配置vue的path變量。
二、生成項目模板
1. 查看所有vue命令
vue -h
運行結果:
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
init generate a new project from a template
list list available official templates
build prototype a new project
create (for v3 warning only)
help [cmd] display help for [cmd]
2. 構建vue項目
vue init webpack my-pro
在安裝過程中,會出現以下一些選擇:
? Project name 輸入項目名稱
? Project description 輸入項目描述
? Author 作者
? Vue build 打包方式,回車就好了
? Install vue-router? 選擇 Y 使用 vue-router,輸入 N 不使用
? Use ESLint to lint your code? 代碼規範,推薦 N
? Setup unit tests with Karma + Mocha? 單元測試,推薦 N
? Setup e2e tests with Nightwatch? E2E測試,N
3. 項目的目錄結構
├── README.md // 項目說明文檔
├── node_modules // 依賴包目錄
├── build // webpack相關配置文件(都已配置好,一般無需再配置)
│ ├── build.js //生成環境構建
│ ├── check-versions.js //版本檢查(node,npm)
│ ├── dev-client.js //開發服務器熱重載 (實現頁面的自動刷新)
│ ├── dev-server.js //構建本地服務器(npm run dev)
│ ├── utils.js // 構建相關工具
│ ├── vue-loader.conf.js //csss 加載器配置
│ ├── webpack.base.conf.js //webpack基礎配置
│ ├── webpack.dev.conf.js // webpack開發環境配置
│ └── webpack.prod.conf.js //webpack生產環境配置
├── config // vue基本配置文件(可配置監聽端口,打包輸出等)
│ ├── dev.env.js // 項目開發環境配置
│ ├── index.js // 項目主要配置(包括監聽端口、打包路徑等)
│ └── prod.env.js // 生產環境配置
├── index.html // 項目入口文件
├── package-lock.json // npm5 新增文件,優化性能
├── package.json // 項目依賴包配置文件
├── src // 項目核心文件(存放我們編寫的源碼文件)
│ ├── App.vue // 根組件
│ ├── assets // 靜態資源(樣式類文件、如css,less,和一些外部的js文件)
│ │ └── css //樣式
│ │ └── font //字體
│ │ └── images //圖片
│ ├── components // 組件目錄
│ │ └── Hello.vue // 測試組件
│ ├── main.js // 入口js文件
│ └── router // 路由配置文件夾
│ └── index.js // 路由配置文件
└── static // 靜態資源目錄(一般存放圖片類)
注:assets和static文件夾的區別
assets目錄中的文件會被webpack處理解析爲模塊依賴,只支持相對路徑形式。例如,在 <img src="./logo.png">
和 background: url(./logo.png)中
,"./logo.png"
是相對的資源路徑,將由Webpack解析爲模塊依賴。
static/
目錄下的文件並不會被Webpack處理:它們會直接被複制到最終的打包目錄(默認是dist/static
)下。必須使用絕對路徑引用這些文件,這是通過在 config.js
文件中的 build.assetsPublicPath
和 build.assetsSubDirectory
連接來確定的。
任何放在 static/
中文件需要以絕對路徑的形式引用:/static/[filename]
。
在我們實際的開發中,總的來說:static放不會變動的文件 assets放可能會變動的文件。
4. 安裝好了之後啓動項目
npm run dev
Component的封裝、調用
vue-cli開發的是單頁面的應用,只有一個 html 文件,在開發時,我們將 整個項目按功能劃分成模塊,每個模塊都可以被任意的複用。高效率,低耦合。
模塊的具體呈現形式就是組件,一個模塊就是一個組件,每個組件有自己的數據、函數、週期等。
項目默認安裝好了之後,可以在 src 文件夾中看到有一個默認的HelloWorld.vue文件。這就是默認安裝的組件,一個組件就是一個 vue 文件。
我們在 src 的 components 文件夾中創建組件。
一、組件的基本構成
<template>
<div class="home">
<!-- 當前組件中的結構 -->
</div>
</template>
<script>
// 當前組件的腳本
export default {
// 當前組件的名稱
name: 'Home',
data () {
return {
// 當前組件中所有的數據
}
},
methods: {
// 當前組件中所有的函數
}
}
</script>
<style>
/* 當前組件的樣式 */
</style>
注:
- 組件中的結構必須包含在
<template>
標籤中。 - 組件所有的子結構都必須包含在一個父級標籤中,建議使用block標籤。
- 組件的
<style>
標籤中的樣式,是全局的樣式,如果這個組件被其他組件調用,那麼這個組件中的style樣式會作用於當前頁面中的所用同選擇器元素。 <style>
標籤加了scoped(如<style scoped></style>
)屬性之後,其樣式就是局部的,只作用於當前組件。
二、組件的調用
組件的調用方式有多種,作爲子組件被調用,路由調用,插槽調用等,在這裏我們先說傳統的調用方式,即作爲子組件被調用。
調用組建時,需要這麼幾個步驟:
- 導出當前組件,
exprot default {}
。 - 在要使用當前組件的父級組件中,導入當前組件。
- 將當前組件傳入父組件的components中。
- 在父組件中使用標籤形式調用組件。
<!-- App.vue -->
<template>
<div id="app">
<!--
在App.vue中調用Home.vue
注: App.vue是根組件,它是和index.html有直接關係,在main.js中被實例化
-->
<Home/>
</div>
</template>
<script>
// 導入Home.vue
import Home from './components/home'
export default {
name: 'App',
components: {
// 在此聲明
Home
}
}
</script>
<style></style>
生命週期鉤子函數
每個 Vue 實例在被創建時都要經過一系列的初始化過程——例如,需要設置數據監聽、編譯模板、將實例掛載到 DOM 並在數據變化時更新 DOM 等,我們把這一系列的過程稱爲組件的生命週期(組件從註冊到銷燬的整個過程)。我們有時候需要在組件生命週期的某個過程中,執行某些代碼,基於此,vue 提供了生命週期鉤子函數,給了用戶在不同階段添加自己的代碼的機會。
但是在此之前,我們要詳細的介紹下組件的聲明週期,以及生命週期中每個階段組件完成和未完成的部分。
一、組件的生命週期
從圖中可以看到,vue 爲生命週期提供了 8 個鉤子函數:
- beforeCreate: 創建前
- created: 創建後
- beforeMount: 掛載前
- mounted: 掛載後
- beforeUpate: 更新前
- upated: 更新後
- beforeDestoy: 銷燬前
- destoyed: 銷燬後
二、每個鉤子函數執行時的組件形態
我們可以在組件的生命週期鉤子函數中執行一些代碼。
<template>
<div id="app">
<button @click="flag = !flag">點擊</button>
<Home v-if="flag"/>
</div>
</template>
<script>
import Home from './components/home'
export default {
name: 'App',
components: {
Home
},
data() {
return {
flag: true,
info: 'i am info of component'
}
},
//數據沒有 元素沒有
beforeCreate:function(){
console.log("創建前========");
console.log(this.info);
console.log(this.$el);
},
//數據有 元素沒有
created:function(){
console.log("已創建========");
console.log(this.info);
console.log(this.$el);
},
//數據有 元素有 虛擬dom
beforeMount:function(){
console.log("mount之前========");
console.log(this.info);
console.log(this.$el);
},
//數據有 元素有,真實dom
mounted:function(){
console.log("mounted========");
console.log(this.info);
console.log(this.$el);
},
beforeUpdate:function(){
console.log("更新前========");
console.log(this.flag);
console.log(this.$el);
},
updated:function(){
console.log("更新完成========");
console.log(this.flag);
console.log(this.$el);
},
beforeDestroy:function(){
console.log("銷燬前========");
console.log(this.info);
console.log(this.$el);
},
destroyed:function(){
console.log("已銷燬========");
console.log(this.info);
console.log(this.$el);
}
}
</script>
<style></style>
注:
- 因爲 vue 是通過控制數據來控制頁面元素的,所以當數據發生變化時,頁面中的元素也會隨之響應,所以更新發生在數據變化時。beforeUpdate時,數據和節點都已經更新了。
- 可以在 beforeCreate 中,執行一些loading效果。
- created中結束loading,還可以做一些初始化操作,可以將一些頁面剛開始加載就執行的代碼放在這裏,但是要注意,這個時候是沒有節點的。
- mounted:在這發起axios請求,拿回數據,配合路由鉤子做一些事情,這個時候有節點又有數據,可以實例化一些插件效果。
- beforeDestory: destoryed :當前組件已被刪除,清空相關內容。
computed和watch
computed(計算數據) 和 watch(觀察者) 是 vue 組件中非常重要的兩個功能。
一、computed
創建一個組建時,組件中的數據都聲明在 data 中,但是有些數據需要依賴 data 中的其他數據計算,這個數據會隨着依賴數據的變化而變化(比如購物車中被選中的商品總價,需要通過購物車中的商品列表計算,當購物車中商品發生變化,這個總價也會變化)。
我們把這種需要依賴其他變量進行處理之後產生的數據都聲明在computed(計算屬性)中。 計算屬性允許我們對指定的視圖,複雜的值計算。這些值將綁定到依賴項值,只在需要時更新。
計算屬性示例:
<template>
<div class="home">
count: <input type="number" v-model.number="current.count">
price: <input type="number" v-model.number="current.price">
<button @click="addGood">添加</button>
<h3>{{ sum }}</h3>
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
current: {
count: 0,
price: 0
},
list: [{
count: 3,
price: 39.79
},{
count: 1,
price: 109.34
},{
count: 3,
price: 0.45
}]
}
},
methods: {
addGood() {
this.list.push(this.current)
}
},
computed: {
sum() {
let sum_ = 0;
for(let l of this.list) {
sum_ += l.count * l.price;
}
return sum_;
}
}
}
</script>
注:
computed和methods有時候從代碼角度而言,沒有區別,但是底層的機制是完全不同的。
methods中的函數,在每次使用這個數據時都要調用,computed依賴於data中的數據,只有在它的相關依賴數據發生改變時纔會重新求值,如果依賴數據不變,計算屬性會緩存這個數據上次的值,不會重複調用複雜的計算過程。
官方文檔反覆強調對於任何複雜邏輯,都應當使用computed計算屬性
computed和methods的區別示例:
<template>
<div class="home">
<button @click="n++">a變化</button>
<button @click="m++">b變化</button>
<h3>{{ sum_of_c }}</h3>
<h3>{{ sum_of_c }}</h3>
<h3>{{ sum_of_c }}</h3>
<hr>
<h3>{{ sum_of_m() }}</h3>
<h3>{{ sum_of_m() }}</h3>
<h3>{{ sum_of_m() }}</h3>
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
n: 0,
m: 0
}
},
methods: {
sum_of_m() {
console.log('methods中的sum執行了');
return this.n + this.m;
}
},
computed: {
sum_of_c() {
console.log('computed中的sum執行了');
return this.n + this.m;
}
}
}
</script>
二、watch
有時候我們需要在 data 中的數據發生變化時做一些操作,那就需要監聽這個數據的變化,vue 提供了 watch(偵聽器),來監聽數據的變化。
computed 計算依賴於某些數據的值。watch 監聽數據本身。
雖然計算屬性在大多數情況下更合適,但有時也需要一個自定義的偵聽器。當需要在數據變化時執行異步或開銷較大的操作時,這個方式是最有用的。
方式一:
<template>
<div class="test2">
<button @click="num = 1000">點擊</button>
</div>
</template>
<script>
export default {
name: "Test2",
data() {
return {
num: 100
}
},
watch: {
num(newVal, oldVal) {
console.log(newVal, oldVal);
}
}
}
</script>
這種寫法也可以改寫成這樣:
// ...
methods: {
numChange(newVal, oldVal) {
console.log(newVal, oldVal);
}
},
watch: {
num: 'numChange'
}
// ...
方式二:
方式一使用 watch 時有一個特點,就是當值第一次綁定的時候,不會執行監聽函數,只有值發生改變纔會執行。如果我們需要在最初綁定值的時候也執行函數,則就需要用到immediate屬性。
// ...
watch: {
num: {
handler(newVal, oldVal) {
console.log(newVal, oldVal)
},
// immediate表示在watch中首次綁定的時候,是否執行handler,值爲true則表示在watch中聲明的時候,就立即執行handler方法,值爲false,則和一般使用watch一樣,在數據發生變化的時候才執行handler。
immediate: true
}
}
// ...
watch監聽對象時,對象的屬性和屬性的屬性發生變化時,默認不會觸發對於這個對象的監聽。想要在對象的任意屬性(無論嵌套多複雜)改變時,執行對象的watch函數,需要開啓deep。
watch: {
obj: {
handler(newVal, oldVal) {
// code
},
deep: true
}
}
// ...
只是想監聽對象的某個屬性的變化:
watch: {
'obj.key': {
handler(newVal, oldVal) {
// code
}
}
}
// ...
自定義指令
除了核心功能默認內置的指令 (v-model 和 v-show),Vue 也允許註冊自定義指令。注意,在 Vue2.0 中,代碼複用和抽象的主要形式是組件。然而,有的情況下,你仍然需要對普通 DOM 元素進行底層操作,Vue提供了一種更簡單的方式-自定義指令,方便我們對DOM的操作。
自定義指令聲明之後的調用方式和使用方式和 Vue 提供的默認指令一樣。
一、自定義指令的小示例:
全局的自定義指令,在 main.js 中創建
Vue.directive('focus', {
// 當被綁定的元素插入到 DOM 中時
inserted: function (el) {
// 聚焦元素
el.focus();
}
});
局部的自定義指令,創建在組件中:
export default {
name: 'Home',
directives: {
focus: {
// 指令的定義
inserted: function (el) {
el.focus()
}
}
}
}
無論是局部的還是全局的,調用方式都相同,只是全局的指令可以被任意組件調用,局部的指令只能被當前組件調用:
<input v-focus type="text">
二、鉤子函數和其參數
一個指令定義對象可以提供如下幾個鉤子函數 (均爲可選):
- bind:只調用一次,指令第一次綁定到元素時調用。在這裏可以進行一次性的初始化設置。
- inserted:被綁定元素插入父節點時調用 (僅保證父節點存在,但不一定已被插入文檔中)。
- update:所在組件的 VNode 更新時調用,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新 (詳細的鉤子函數參數見下)。
- componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新後調用。
- unbind:只調用一次,指令與元素解綁時調用。
指令鉤子函數會被傳入以下參數:
- el:指令所綁定的元素,可以用來直接操作 DOM 。
- binding:一個對象,包含以下屬性:
- name:指令名,不包括 v- 前綴。
- value:指令的綁定值,例如:v-my-directive=“1 + 1” 中,綁定值爲 2。
- oldValue:指令綁定的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
- expression:字符串形式的指令表達式。例如 v-my-directive=“1 + 1” 中,表達式爲 “1 + 1”。
- arg:傳給指令的參數,可選。例如 v-my-directive:foo 中,參數爲 “foo”。
- modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar 中,修飾符對象爲 { foo: true, bar: true }。
- vnode:Vue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情。
- oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。
更加具體的示例:
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ');
}
})
調用:
<template>
<div class="test3" v-demo:foo.a.b="message"></div>
</template>
<script>
export default {
name: "Test3",
data() {
return {
message: 'hello!'
}
}
}
</script>
注:
- 指令的參數可以是動態的。例如,在 v-mydirective:[argument]=“value” 中,argument 參數可以根據組件實例數據進行更新!這使得自定義指令可以在應用中被靈活使用。
- 在很多時候,你可能想在 bind 和 update 時觸發相同行爲,而不關心其它的鉤子。比如這樣寫:
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value;
})
- 如果指令需要多個值,可以傳入一個 JavaScript 對象字面量。記住,指令函數能夠接受所有合法的 JavaScript 表達式。
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
練習: 封裝v-on指令