[toc] 首發日期:2019 01 19 前言: 有時候,一個後端開發者“不得不”自己去搭建前端界面。如果有的選,當然選一個比較好學的前端“框架”咯(框架很多時候封裝了普通的html元素,使得能更加方便地使用)。 如果你做的項目的界面是一個偏後臺管理的而且要求並不高的界面的時候,你可以考慮easy ...
目錄
首發日期:2019-01-19
前言:
有時候,一個後端開發者“不得不”自己去搭建前端界面。如果有的選,當然選一個比較好學的前端“框架”咯(框架很多時候封裝了普通的html元素,使得能更加方便地使用)。
如果你做的項目的界面是一個偏後臺管理的而且要求並不高的界面的時候,你可以考慮easy UI這個較為知名的前端框架。
如果你想要界面變得好看一些(easy UI的界面太簡單了,缺乏較強的定製性),那麼你可以考慮vue這個框架。vue這個框架本身並沒有多少好看的樣式,但學習了這個框架就可以學會怎麼使用它的第三方組件庫了(這些第三庫看起來都還行)。
iview組件庫示例
element組件庫示例
【Vue自己有官方教程,為什麼寫這個呢?主要是感覺自己的前端知識不太穩固,看教程的時候有點迷糊。推己及人,所以有了這個博文】
Vue的介紹
- 官網:https://cn.vuejs.org/
- [官方的話]:Vue (讀音 /vjuː/,類似於 view) 是一套用於構建用戶界面的漸進式框架。與其它大型框架不同的是,Vue 被設計為可以自底向上逐層應用。Vue 的核心庫只關註視圖層,不僅易於上手,還便於與第三方庫或既有項目整合。另一方面,當與現代化的工具鏈以及各種支持類庫結合使用時,Vue 也完全能夠為複雜的單頁應用提供驅動。
- 小菜鳥前端:Vue是一個簡單容易上手前端框架,在網上有很多基於vue的web UI框架(element等),由於有大量類似於easyUI這些快速搭建頁面的框架,所以我們可以使用vue來快速搭建頁面。如果我們作為一個後端開發者想掌握一個前端框架,vue是一個好選擇,因為它足夠的易學。
例如下麵的代碼可以快速構建一個表格:
<template>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="date" label="日期" width="180"></el-table-column>
<el-table-column prop="name" label="姓名" width="180"></el-table-column>
<el-table-column prop="address" label="地址"> </el-table-column>
</el-table>
</template>
<script>
export default {
data() {
return {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀區金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀區金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀區金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀區金沙江路 1516 弄'
}]
}
}
}
</script>
相容性:
Vue不支持IE8 及以下版本,因為 Vue 使用了 IE8 無法模擬的 ECMAScript 5 特性。但它支持所有相容 ECMAScript 5 的瀏覽器。【所以如果項目相容性要求較高,那麼不適合使用Vue】
學習Vue需要的前置知識:
- html+ js 【基礎的內容,前端的基礎,不瞭解不行。但只要你能看得懂基本的html和js代碼即可,不瞭解的可以查。】
- es6 【 ECMAScript 6(ES6)是JavaScript語言的下一代標準。但不需要瞭解太多,Vue裡面有些語法是ES6的,但我們可以看著例子瞭解有哪些常見的語法即可】
- webpack 【不需要瞭解太多,是一個項目構建工具,會涉及一些Vue項目的運行知識。】
- npm 【vue入門不需要瞭解太多,可能只需要瞭解npm的幾個命令】
MVVM模型
- MVVM是Model-View-ViewModel的簡寫。
- MVVM(Model-View-ViewModel)的基礎是MVP模式,【MVP類似於MVC,後端開發者應該對MVC比較熟悉了】
【常見的MVP前端開發有幾個步驟:1.創建頁面元素,2.在script中給元素綁定事件,3.在script中處理事件(這個事件可能會影響頁面的元素的顯示或返回數據)】
【上面的幾個步驟可以說是內容(頁面元素)行為(js操作)分離了】
【為什麼要有MVVM呢?主要是MVP模式有太多的dom操作了(大量的DOM 操作使頁面渲染性能降低,載入速度變慢)。MVVM解決了這個問題,並且提供了一些其他的好處。】
【如果你瞭解ORM模型,你應該很能體會到MVVM模型的好處,有了VM層幫我們管理了數據,我們就只需要處理好Model層了,這就好像ORM中定義了數據映射關係,然後我們只需要操作實體類。】
補充:
- Vue的核心特點是簡單易用,所以它相對適合非專業前端的學習。它需要一些前置的前端知識,而且它自己的知識也是需要時間來學習的,如果與easy UI相比,easy UI的學習速度會更快,但vue更能構建比較優美的頁面。
安裝/導入
【要想使用Vue,必須先導入/安裝,就像使用jquery必須導入jquery.js一樣;而vue可以使用webpack來構建,所以也可以使用webpack安裝Vue,但如果你是初學者,先掌握靜態導入的方法吧】
導入Vue
Vue的本質也是javascript,所以它也可以通過導入js文件來使用功能(js可以用cdn的,也可以手動導入本地的vue.js)。
這種也是最簡單的使用vue的方式,所以我們可以這種方式來作為入門學習,但正式使用時都會使用webpack來構建vue項目。
在html文件中使用如下代碼:【這裡先講導入,後面講使用】
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
安裝
- 安裝vue:
npm install vue
- 全局安裝vue-cli:
npm install --global vue-cli
- 創建一個基於 webpack 模板的新項目:
vue init webpack 項目名
兩種方式的區別:
- 對於普通的靜態js導入,在面對多個組件共同開發的時候,會比較不方便。【如果你是後端開發者,你應該知道把多個類寫在一個文件中是多麼地不方便,這也是一樣的】
- js:
- 簡單,不需要安裝,直接導入js即可。
- npm
- 在用 Vue 構建大型應用時推薦使用 NPM 安裝
- NPM 能很好地和諸如 webpack 或 Browserify 模塊打包器配合使用。
- 同時 Vue 也提供配套工具來開發單文件組件。
在入門部分將使用js導入方式的例子,在涉及構建多個組件的時候(頁面需要多個組件時,如果把多個組件定義在一個文件中會顯得贅餘。這好比把多個類放到同一個文件中定義。)將使用npm安裝方式演示。
HelloWorld示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!--2.這是被vue實例管理的區域-->
<div id="app">
{{ message }}
</div>
</body>
<!--1.導入js-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//3. 創建實例:new Vue()裡面有一個{}類型的參數,屬性el是一個元素的id名,代表被vue實例管理的區域;
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
</html>
代碼分析:
- 導入了js:
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
- 定義一個元素,給它id起名app。[名字是可以隨意的,但在下麵的new Vue創建實例時,el參數必須是這個id名]
- 新建一個vue實例,屬性el代表把對應元素區域交給vue管理(
el: '#app'
代表把id為app的區域交給vue管理)。那麼對應元素區域裡面就可以使用vue的語法了。 - data屬性:data裡面可以定義一系列變數,可用於這個實例掌控的區域。
- {{ message }} : vue語法的內容,代表獲取變數名為message的數據,並顯示出來。
代碼效果:
導入了js之後,vue把管理的區域裡面的vue語法都解析了,所以 {{ message }}就取出了vue實例中名叫message的數據。
現在我們瞭解了一些vue的渲染頁面的知識,下麵我們來瞭解更多的渲染技巧。
實例中可以定義的內容
每個 Vue 應用都是通過使用 Vue 函數創建一個新的 Vue 實例開始的。
實例中的定義的內容就是我們可以使用在Vue應用中的內容。
下麵講實例中可以定義哪些內容。
- 數據
- 方法
- 生命周期鉤子函數
- 其他(有些內容比較重要,留到後面講)
定義數據
- 定義了數據,那麼就可以在Vue管理的區域中使用Vue的獲取數據的語法來獲取數據。
- 這種操作就像是我們把數據裝到實例中,實例再把數據傳給視圖組件。
- 任何你想顯示到頁面的數據你都可以考慮定義在實例中。
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
//<div id="app">
// {{ message }}
//</div>
定義方法methods
- 在js中,方法是很重要的,Vue實例中可以定義方法。【為什麼要有方法這個就不解釋了吧?】
<body>
<div id="app">
<!-- 觸發事件,建議使用Vue的語法來綁定事件(@事件類型,代表綁定什麼事件) -->
<!-- 實例內定義的函數,使用Vue的語法來綁定;實例外定義的,可以使用原生的方式綁定事件 -->
<button @click="myFunction">按鈕</button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
methods: {
myFunction: function () {
alert('haha')
}
}
})
</script>
生命周期與生命周期鉤子函數
【要結合圖來看(懂英文是多麼地好)】
- beforeCreate:在創建了vue示例時,在進行了一部分基礎的初始化之後就會自動執行beforeCreate函數
- created:創建完示例後,會自動執行created函數
- beforeMounted:渲染完模板後(可以說是vue知道區域內是一個怎麼的html環境,但還沒把數據顯示到這個環境中的時候),會自動執行這個函數
- mouted:模板與數據結合之後自動執行這個函數。【此時頁面已經渲染完畢】
- beforeUpdated:數據發生變化後,新數據被渲染前會自動執行這個函數。
- updated:新數據被渲染成功會自動執行這個函數。
- beforeDestory:當實例被destory時會自動執行這個函數。
- destory:當實例被完全銷毀是自動執行這個函數。
【如果你對生命周期鉤子感興趣,可以自查,這裡不多講,後面之後根據開發需求來講一些】
測試代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<button @click='updateValue'>點擊觸發beforeUpdate,updated</button>
<button onclick='deleteApp()'>點擊觸發beforeDestory,destoryed</button>
<p ref='a'>{{ msg }}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
msg: 'hello'
},
beforeCreate: function(){
console.log('beforeCreate')
},
created: function(){
console.log('created')
},
beforeMount: function(){
console.log(this.$refs.a)
// 上面的你會看到undefined,因為數據還沒綁定上
console.log('beforeMount')
},
mounted: function(){
console.log(this.$refs.a)
// 上面的你會看到p,因為數據綁定上了
console.log('mounted')
},
beforeUpdate: function(){
console.log('beforeUpdate')
},
updated: function(){
console.log('updated')
},
beforeDestroy: function(){
console.log('beforeDestory')
},
destroyed: function(){
console.log('destoryed')
},
methods: {
updateValue: function () {
this.msg = 'haha'
}
}
})
function deleteApp() {
app.$destroy()
}
</script>
</html>
補充:
- 實例中其實還可以定義別的內容(如計算屬性,監聽器等)。但主要內容是上面這些,暫時瞭解這些就夠了。
- 創建了實例,就可以在控制臺中使用實例名來獲取數據了,比如上面定義了實例app,那麼app.message就可以獲取定義在data中名為message的數據,app.myFunction()就可以調用定義在methods中名為myFunction的函數。【所以後面的例子中的一些數據更改都是使用控制台來更改實例中的數據】
渲染
插入文本
在上面已經演示了使用Mustache表達式(可以俗稱插值表達式){{ }}
來獲取實例中的數據了,其實還可以使用其他方式來輸出數據
v-text
v-text可以向元素中輸出文本內容
<div id="app">
<span v-text="message"></span>
<!-- message: 'Hello Vue!' -->
<!--
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
-->
</div>
v-html
v-html可以向元素中輸出html內容:
<div id="app">
<span v-html="message"></span>
<!-- message: 'Hello Vue!'(帶標題樣式的) -->
</div>
<!--
var app = new Vue({
el: '#app',
data: {
message: '<h1>Hello Vue!</h1>'
}
})
-->
Mustache 語法不能作用在 HTML 特性上,也就是說在html元素中沒有 id="{{ id }}" 這樣的操作 ,這種時候需要使用vue語法:v-bind:id="id"
{{ }}裡面可以使用javascript表達式,例如:{{ message.split('').reverse().join('') }}
用v-bind綁定屬性:
v-bind用於給元素的屬性綁定值
- 綁定標題:v-bind:title
<div id="app">
<span v-bind:title="message">懸浮幾秒,查看標題</span>
</div>
<!--
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
-->
- 給a元素綁定href : v-bind:href='xxx'
- 給元素綁定id : v-bind:id='xxx'
【理論上,可以使用v-bind給元素的任意屬性綁定值】
v-bind的簡寫
<!-- 完整語法 -->
<a v-bind:href="url">...</a>
<!-- 縮寫 -->
<a :href="url">...</a>
把對象的所有屬性綁定到元素:
v-bind的參數可以是一個對象,是一個對象時,會把對象的所有屬性都綁定到元素上。具體如下圖。
條件渲染
- v-if可以判斷是否渲染一個元素,如果不渲染,這個元素將不會產生(不是隱藏!可以理解為這個元素被delete了。)
<div id="app">
<p v-if="seen">現在你看到我了</p>
<!-- seen: true 時渲染,seen: false時不渲染 -->
</div>
<!--
var app = new Vue({
el: '#app',
data: {
seen: false
}
})
-->
- 除了v-if,還有v-else和v-else-if,v-else不可以再帶條件判斷,v-else-if可以帶條件判斷。
<div id="app">
<h1 v-if="gender === '男'">男</h1>
<h1 v-else-if="gender === '女'">女</h1>
<h1 v-else>unknown</h1>
</div>
<!--
var app = new Vue({
el: '#app',
data: {
gender: '女'
},
})
-->
使用v-else 的元素必須緊跟在帶 v-if 或者 v-else-if 的元素的後面,否則它將不會被識別。
- v-show 的元素始終會被渲染並保留在 DOM 中。v-show 只是簡單地切換元素的 CSS 屬性 display。
<div id="app">
<h1 v-show="ok">Hello!</h1>
</div>
<!--
var app = new Vue({
el: '#app',
data: {
ok: true
},
})
-->
迴圈
- v-for可以迴圈渲染出若幹個元素。
- v-for 指令需要使用 item in items 形式的特殊語法,items 是源數據數組,而 item 是數組元素迭代的別名。
<div id="app">
<ol>
<!-- 每一次迭代了出數據給todo,都會有一個li元素,並會在li元素中輸出todo的text -->
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
<!--
var app = new Vue({
el: '#app',
data: {
todos: [
{ text: '學習 JavaScript' },
{ text: '學習 Vue' },
{ text: '搞事情' }
]
}
})
-->
代碼效果:
在 v-for 塊中,我們擁有對父作用域屬性的完全訪問許可權。[這是來自官網的話,我覺得有點多餘,感覺子元素獲取父元素的數據是很正常的操作。]
v-for 還支持一個可選的第二個參數為當前項的索引,它會從0開始。用來標識當前迭代的是第幾個元素。
可以用 of 替代 in 作為分隔符,因為它是最接近 JavaScript 迭代器的語法:
<div v-for="item of items"></div>
也可以用 v-for 迭代一個對象的所有屬性,第一個參數是value,第二個可選參數是key,第三個可選參數為索引
第二個參數為key
<div v-for="(value, key) in object">
{{ key }}: {{ value }}
</div>
第三個參數為索引:
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>
使用三個參數時的代碼效果:
在遍歷對象時,是按 Object.keys() 的結果遍歷,但是不能保證它的結果在不同的 JavaScript 引擎下是一致的。
v-for 也可以取一個整數作為源數據(v-for="i in 10"
)。在這種情況下,它將重覆多次模板。
當 v-if 與 v-for 一起使用時,v-for 具有比 v-if 更高的優先順序。【這個問題官網提了一下,這裡我也提一下,註意使用】
計算屬性
- 當某個數據需要另外一個數據計算得到的時候,你可能會定義兩個數據,其中一個數據初始值由另一個數據計算得出。但上面的值只能初始化一次,如果希望數據A變的時候,數據B也變,那麼你可能需要去監聽數據的變化,從而每次數據變化時再做操作。
- 計算屬性簡化了這樣的情況,它的返回值是一個計算得到的數據,當內部的計算元素髮生變化的時候,返回值也會變化。
- 計算屬性使用computed來定義。
<div id="app">
<p>原消息: "{{ message }}"</p>
<p>逆反後: "{{ reversedMessage }}"</p>
</div>
<!--
var app = new Vue({
el: '#app',
data: {
message: 'Hello'
},
computed: {
// 計算屬性的 getter
reversedMessage: function () {
// `this` 指向 vm 實例
return this.message.split('').reverse().join('')
}
}
})
-->
與函數的區別
下麵的代碼也可以達到上面的效果
(也是即時的,有人不懂為什麼這個函數會重覆調用,而非頁面初始化時調用一次;因為當頁面中數據更新的時候,涉及頁面數據的函數也會重新執行。)
<div id="app">
<p>原消息: "{{ message }}"</p>
<p>逆反後: "{{ reversedMessage() }}"</p>
<p>{{ Date.now() }}"</p>
<!-- 後面的now是用來測試數據刷新時,函數會重新執行 -->
</div>
<!--
var app = new Vue({
el: '#app',
data: {
message: 'Hello'
},
methods: {
reversedMessage: function () {
// `this` 指向 vm 實例
return this.message.split('').reverse().join('')
}
}
})
-->
函數也可以達到同樣的效果,但是函數沒有緩存效果,而計算屬性有緩存。沒有緩存時,函數每一次都要重新計算來得到結果,如果這是一個比較消耗資源的計算的話,那麼會減慢頁面的速度;而計算屬性有緩存,只要內部的計算元素沒有發生變化,那麼會使用緩存來作為計算結果。
與偵聽屬性的區別
上面的計算屬性達到效果需要留意計算元素的變化,你可能會想到一些類似的監聽數據變化的方法,而vue中也是有這樣的東西的。
下麵的代碼監聽了firstName和lastName,當這兩個數據變化的時候會重新給fullName賦值。
<div id="app">
<div>{{ fullName }}</div>
</div>
<!--
var app = new Vue({
el: '#app',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
-->
使用計算屬性達到上面效果(顯然省了不少代碼):
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
計算屬性的setter
- 上面的fullName例子中,當firstName和lastName更改的時候,fullName會更改。
- 但有沒有可能要求fullName更改的時候,firstName和lastName會對應更改呢?
- 上面的獲取變化後的fullName是getter,其實還可以設置setter
<div id="app">
<div>{{ fullName }}</div>
</div>
<!--
var app = new Vue({
el: '#app',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
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]
}
}
}
})
-->
偵聽器
雖然計算屬性已經提供了很多的好處,但有些時候計算屬性也不能滿足我們的要求。比如我們希望監聽某個屬性的變化來得到另一個屬性的結果,但是不希望它馬上得到結果,那麼這時候計算屬性就達不到需求了。而監聽器裡面可以寫一些其他代碼(比如一些延遲去得到結果的代碼)。
當需要在數據變化時執行非同步或開銷較大的操作時,watch是最有用的。
為什麼有時候不希望計算新結果那麼快呢?這就好比有人在百度搜索中輸入一個個字,如果每個字都要進行檢索的話,那麼就對百度來說開銷就很大了,如果稍微等等,確認用戶輸入完畢再去計算,那麼就節省了很多資源了,下麵的來自官網的例子正是一種這樣的考慮了資源開銷的例子。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 發生改變,這個函數就會運行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
// `_.debounce` 是一個通過 Lodash 限制操作頻率的函數。
// 在這個例子中,我們希望限制訪問 yesno.wtf/api 的頻率
// AJAX 請求直到用戶輸入完畢才會發出。想要瞭解更多關於
// `_.debounce` 函數 (及其近親 `_.throttle`) 的知識,
// 請參考:https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
}
}
})
</script>
</html>