從新對博客內容進行了修正,爲了不本身的文字引來沒必要要的歧義,因此刪本身關於技術方面的,計劃這一分類更多的採用引用爲主。javascript
本文轉自簡書《教你如何學好vue《文檔》》,但對內容有所刪減(Vue 歷史、做者以及案例)以及補充,若想了解還請移步至原文。css
ECMAScript 6.0(如下簡稱 ES6 )是 JavaScript 語言的下一代標準,已經在 2015 年 6 月正式發佈了也叫 ECMAScript 2015 。它的目標,是使得 JavaScript 語言能夠用來編寫複雜的大型應用程序,成爲企業級開發語言html
ES6 新增了 let
命令,用來聲明變量。它的用法相似於var,可是所聲明的變量,只在 let
命令所在的代碼塊內有效。前端
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1 for (let i = 0; i < 10; i++) { // ... } console.log(i); // ReferenceError: i is not defined
const
聲明一個只讀的常量。一旦聲明,常量的值就不能改變。vue
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable. const foo = {}; // 爲 foo 添加一個屬性,能夠成功 foo.prop = 123; foo.prop // 123 // 將 foo 指向另外一個對象,就會報錯 foo = {}; // TypeError: "foo" is read-only
const
和let
不存在變量提高html5
let a = 1; let b = 2; let c = 3; // ES6 容許寫成下面這樣。 let [a, b, c] = [1, 2, 3]; // 對象的解構賦值 let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb" let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb" let { baz } = { foo: 'aaa', bar: 'bbb' }; baz // undefined
let place = "world" // 變量place沒有聲明 let msg = `Hello, ${place}`;
屬性的簡潔表示法java
const foo = 'bar'; const baz = {foo}; baz // {foo: "bar"} // 等同於 const baz = {foo: foo}; function f(x, y) { return {x, y}; } // 等同於 function f(x, y) { return {x: x, y: y}; } f(1, 2) // Object {x: 1, y: 2}
函數的擴展 ES6 容許使用 =>
(箭頭)定義函數。node
var f = v => v; // 等同於 var f = function (v) { return v; };
若是箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,而且使用return語句返回。webpack
var sum = (num1, num2) => { return num1 + num2; }
箭頭函數有幾個使用注意點:ios
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。
Promise 對象是一個構造函數,用來生成Promise實例。下面代碼創造了一個Promise實例。
const promise = new Promise(function(resolve, reject) { if (/* 異步操做成功 */){ resolve(value); // 容器狀態爲成功 } else { reject(error); // 容器狀態爲失敗 } }); promise.then(function() { console.log('resolved.'); });
對於錯誤返回的捕獲
// bad promise.then(function(data) { // success }, function(err) { // error }); // good promise.then(function(data) { //cb // success }).catch(function(err) { // error });
內容補充:爲何要用 Promise?因爲 Javascript 是單線程的,因此對於普通函數來說執行順序是 fun1 > fun2 > fun3 > fun4,但如是多個異步函數,則這種多線程操做就會致使執行順序不固定,爲此咱們爲了能保障執行順序就須要在一個異步函數中再去運行另外一個異步函數,這個行爲稱之爲 回調地獄 。
Promise 自己是一個同步函數。它能夠理解爲一個容器,該容器用來監控其中異步函數的執行結果。
Promise 函數執行後,能夠經過鏈式追加 .then() 決定函數成功或失敗後須要操做的內容。
promise.all() 能夠將多個 Promise 實例包裝成一個新的 Promise 實例。成功的時候返回的是一個結果數組,而失敗的時候則返回最早被 reject 失敗狀態的值。
promise.race() 顧名思義,就是賽跑的意思,Promise.race([p1, p2, p3]) 裏面哪一個結果得到的快,就返回那個結果,無論結果自己是成功狀態仍是失敗狀態。
ES2017 標準引入了 async 函數,使得異步操做變得更加方便。async 函數是什麼?一句話,它就是 Generator 函數的語法糖。
基本用法
async 函數返回一個 Promise 對象,可使用 then 方法添加回調函數。當函數執行的時候,一旦遇到 await 就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function asyncPrint(value, ms) { await timeout(ms); console.log(value); } asyncPrint('hello world', 50); // 上面代碼指定 50 毫秒之後,輸出 hello world。
返回 Promise 對象
async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world"
歷史上,JavaScript 一直沒有模塊(module)體系,沒法將一個大程序拆分紅互相依賴的小文件,再用簡單的方法拼裝起來。其餘語言都有這項功能,好比 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,可是 JavaScript 任何這方面的支持都沒有,這對開發大型的、複雜的項目造成了巨大障礙。
在 ES6 以前,社區制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD(require.js 庫,專門用於在瀏覽器中進行模塊化開發,幾乎已經淘汰了) 兩種。前者用於服務器(Node.js),後者用於瀏覽器。ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。
爲了學習方便,咱們這裏使用 Node.js 環境來舉例說明 ES6 模塊規則
這是一個 Node 文件,將其命名爲 foo.js ,內容以下:
// export 也用於導出 // 可是能夠屢次使用 export const a = 1 export const b = 2 export const c = 3 export const d = 4 function add(x, y) { return x + y } // 等價於 module.exports = add // export default 只能使用一次 export default add // 模塊中有多個成員,通常都使用 export xxx // 若是隻有一個成員,那就 export default
補充內容:module.export 與 export 的區別
export 導出的源碼邏輯以下
module = {export: {}}
var export = module.export
也就是說 export === module.export
當 module.export 內有多個屬性的時候,也能夠經過 export 導出
但若想單獨導出方法或變量時,則只能使用 export default
由於當 export 重定義方法後就與 module.export 脫離了聯繫
將 foo.js 注入項目入口文件 main.js:
// 加載 export default 導出的成員 // import foo from './foo' // console.log(foo) // 按需加載 export 導出的成員 // import { a, b } from './foo.mjs' // console.log(a, b) // 一次性加載全部成員(包括 default 成員) // import * as foo from './foo.mjs' // console.log(foo) // console.log(foo.a) // console.log(foo.default(10, 3)) // 不多這樣去訪問 default 成員 // 爲了方便,先加載默認 default 成員,而後加載其它 export 成員 // import abc, { a, b } from './foo' // console.log(abc) // export default 成員 // console.log(a, b) // // 可使用 as 起別名 import { a as aa } from './foo' console.log(aa) // console.log(foo(1, 2))
補充內容:
import * as xxx from 'xxx' // 會將若干 export 導出的內容組合成一個對象返回
import xxx from 'xxx' // 只會導出這個默認的對象做爲一個對象
它自己只是一個用於數據驅動視圖更新的庫,但隨着衍生生態的發展,現在已經有了 Vue Router
、Vuex
、Vue CLI
、Vue Server Renderer
等功能庫,因此當 Vue 和這些核心功能庫結合到一塊兒的時候,咱們稱之爲一個框架(Vue 全家桶)。
Vue.js 不支持 IE8 及其如下版本,由於 Vue 使用了 IE8 沒法模擬的 ECMAScript 5 特性。但它支持全部兼容 ECMAScript 5 的瀏覽器。最新穩定版本:2.6.10
直接下載
CDN
<script src="https://cdn.jsdelivr.net/npm/vue"></script> // 最新穩定版 <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script> // 指定版本
使用 npm 下載(最好建立一個 package.json 文件,用來存儲第三方包依賴信息)
npm install vue // 最新穩定版 npm install vue@版本號 // 指定版本
<meta charset="UTF-8"> <title>Document</title> <div id="app"> {{ message }} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script> <script> new Vue({ el: '#app', // 實例選項 data: { message: 'Hello Vue.js!' // 聲明式渲染 } }) </script>
實例選項 - el
實例選項 - data
new Vue({ el: '#app', data(){ return {message:"demo"} } }) new Vue({ el: '#app', data:{message:"demo"} })
組件的定義只接受 function ,模板中訪問的數據必須初始化到 data 中
// 文本 <p>{{ message }}</p> <span>{{ message }}</span> <strong>{{ message }}</strong> // JavaScript 表達式 <p>{{ number + 1 }}</p> <p>{{ number + 1 > 10 ? 'number大於10' : 'number小於10' }}</p> <p>{{ arr }}</p> <p>{{ message.split('').reverse().join('') }}</p>
指令 (Directives) 是帶有 v- 前綴的特殊特性。指令特性的值預期是單個 JavaScript 表達式 (v-for 是例外狀況,稍後咱們再討論)
v-if :指令用於條件性地渲染一塊內容。這塊內容只會在指令的表達式返回真值的時候被渲染。
<h1 v-if="awesome">Vue is awesome!</h1>
v-else :
<div v-if="Math.random() > 0.5"> Now you see me </div> <div v-else> Now you don't </div>
v-else-if :
<div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else> Not A/B </div>
v-show :
<h1 v-show="ok">Hello!</h1>
通常來講,v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。所以,若是須要很是頻繁地切換,則使用 v-show 較好;若是在運行時條件不多改變,則使用 v-if 較好
v-text :
<div v-text="text"></div> // 帶標籤的內容不會被解析
v-html :
<div v-text="text"></div> // 帶標籤的內容會被解析
v-model(表單輸入綁定) :
<input v-model="test"/> //這個指令是一個語法糖,通常只用於input標籤
v-for(列表渲染)
v-for="item in todos" v-for="(item, index) in todos"
v-bind(綁定指令)
綁定指令能綁定元素上的任何屬性,經常使用的好比 id, style, class, src, ... 也能綁定自定義屬性。
操做元素的 class 列表和內聯樣式是數據綁定的一個常見需求,由於它們都是屬性,因此咱們能夠用 v-bind 處理它們,在將 v-bind 用於 class 和 style 時,Vue.js 作了專門的加強。表達式結果的類型除了字符串以外,還能夠是對象或數組
Class 綁定
// 通常使用 // class 的值就是變量 className 的值 <div v-bind:class="className"></div> <div v-bind:class="className?'a':'b'"></div> // 對象語法 // 若是變量isActive 值爲真則有active這個類,不然沒有 <div v-bind:class="{ active: isActive }"></div> <script> data: { isActive: true } </script> // 渲染爲 <div class="active"></div> // 數組語法(使用偏少) <div v-bind:class="[activeClass, errorClass]"></div> <script> data: { activeClass: 'active', errorClass: 'text-danger' } </script> // 渲染爲 <div class="active text-danger"></div>
Style 綁定
// 對象語法 <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> <script> data: { activeColor: 'red', fontSize: 30 } </script> // 直接綁定到一個樣式對象一般更好,這會讓模板更清晰 <div v-bind:style="styleObject"></div> <script> data: { styleObject: { color: 'red', fontSize: '13px' } } </script>
縮寫
<a v-bind:href="url">...</a> // 完整語法 <a :href="url">...</a> // 縮寫
v-on(監聽指令)
能夠用 v-on 指令監聽 DOM 事件,並在觸發時運行一些 JavaScript 代碼。
<div id="example-1"> <button v-on:click="counter += 1">Add 1</button> <p>The button above has been clicked {{ counter }} times.</p> </div> var example1 = new Vue({ el: '#example-1', data: { counter: 0 } })
許多事件處理邏輯會更爲複雜,因此直接把 JavaScript 代碼寫在 v-on 指令中是不可行的。所以 v-on 還能夠接收一個須要調用的方法名稱
<div id="example-2"> <!-- `greet` 是在下面定義的方法名 --> <button v-on:click="greet">Greet</button> <!-- DOM的原生事件,能夠用特殊變量 $event 把它傳入方法 --> <button v-on:click="say('hi')">Say hi</button> </div> var example2 = new Vue({ el: '#example-2', data: { name: 'Vue.js' }, methods: { // 在 `methods` 對象中定義方法 greet: function (event) { alert('Hello ' + this.name + '!') // `this` 在方法裏指向當前 Vue 實例 if (event) { // `event` 是原生 DOM 事件 alert(event.target.tagName) } }, say: function (message,ev) { alert(message) console.log(ev) } } })
縮寫
<a v-on:click="doSomething">...</a> // 完整語法 <a @click="doSomething">...</a> // 縮寫
補充內容:修飾符
.stop 阻止事件冒泡
.self 當事件在該元素自己觸發時才觸發事件
.capture 添加事件偵聽器是,使用事件捕獲模式
.prevent 阻止默認事件
.once 事件只觸發一次
補充內容:《 vue中的slot(插槽)》
插槽(Slot)是 Vue 提出來的一個概念,正如名字同樣,插槽用於決定將所攜帶的內容,插入到指定的某個位置,從而使模板分塊,具備模塊化的特質和更大的重用性。
插槽顯不顯示、怎樣顯示是由父組件來控制的,而插槽在哪裏顯示就由子組件來進行控制。
<template> <div> <slotOne1> <p style="color:red">我是父組件插槽內容</p> </slotOne1> </div> </template>
在父組件引用的子組件中寫入想要顯示的內容,<p style="color:red">我是父組件插槽內容</p> 爲組件插槽顯示的內容
<template> <div class="slotOne1"> <slot></slot> </div> </template>
在子組件中寫入slot,slot所在的位置承接的就是父組件中 <p style="color:red">我是父組件插槽內容</p>
具名插槽
<template> <div class="slottwo"> <slot name="header"></slot> <slot></slot> <slot name="footer"></slot> </div> </template>
在子組件中定義了三個 slot 標籤,其中有兩個分別添加了 name 屬性 header 和 footer
<template> <div> <slot-two> <p>啦啦啦,啦啦啦,我是賣報的小行家</p> <template slot="header"> <p>我是name爲header的slot</p> </template> <p slot="footer">我是name爲footer的slot</p> </slot-two> </div> </template>
在父組件中使用 template 並寫入對應的 slot 值來指定該內容在子組件中現實的位置(固然也不用必須寫到 template ),沒有對應值的其餘內容會被放到子組件中沒有添加 name 屬性的 slot 中
// 基礎用法 data() { return { str: 'string' } } computed: { beautifyStr() { return this.str.split(''); } }
對 data 屬性的監聽,說明屬性是在 data 中聲明過的屬性更新時調用監聽函數,可選參數分別爲新值和舊值,對屬性從新設置值只要跟原來的值相等就不會觸發函數調用,這一點跟計算屬性是類似的,監聽某一個值,當被監聽的值發生變化時,執行對應的操做,與 computed 的區別是,watch 更加適用於監聽某一個值的變化並作對應的操做,好比請求後臺接口等,而 computed 適用於計算已有的值並返回結果
// 基礎用法 new Vue({ el: '#id', data: { firstName: 'Leo', lastName: 'Alan', obj1: { a: 0 } }, watch: { // 監聽firstName,當firstName發生變化時就會執行該函數 firstName(newName, oldName) { // 執行須要的操做... // 注:初始化不會執行,只有當被監聽的值(firstName)發生變化時纔會執行 }, // 監聽lastName lastName: { handler(newName, oldName) { // 執行須要的操做... }, immediate: true // true: 初始化時就會先執行一遍該監聽對應的操做 }, obj1: { handler() { // 執行須要的操做... }, deep: true // 該屬性默認值爲 false. // 當被監聽的值是對象,只有 deep 爲 true 時,對應屬性的值( obj1.a )發生變化時才能觸發監聽事件,可是這樣很是消耗性能 }, // 監聽對象具體的屬性,deep 就不須要設置爲 true 了 'obj1.a': { handler() { // 執行須要的操做... } } } })
Vue CLI 是 Vue 的腳手架工具,它能夠幫助咱們快速生成 Vue 基礎項目代碼,提供開箱即用的功能特性。
// > 依賴要求:Vue CLI 須要 Node.js 8.9 或更高版本 (推薦 8.11.0+)。 npm install -g @vue/cli
在終端中輸入 vue --version
若能夠打印出版本號,則表明安裝成功。
運行如下命令來建立一個新項目
vue create my-project
內容補充:輸入命令後,會跳出以下問題Project name (baoge) 項目名稱(注意這裏的名字不能有大寫字母,若是有會報錯)
Project description (A Vue.js project) 項目描述,也可直接點擊回車,使用默認名字
Author () 做者
Runtime + Compiler: recommended for most users 運行加編譯,既然已經說了推薦,就選它了
Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specificHTML) are ONLY allowed in .vue files - render functions are required elsewhere 僅運行時,已經有推薦了就選擇第一個了
Install vue-router? (Y/n) 是否安裝vue-router,這是官方的路由,大多數狀況下都使用,這裏就輸入 y 後回車便可
Use ESLint to lint your code? (Y/n) 是否使用 ESLint 管理代碼,ESLint 是個代碼風格管理工具
Pick an ESLint preset (Use arrow keys) 選擇一個 ESLint 預設,編寫 vue 項目時的代碼風格,直接 y 回車
Setup unit tests with Karma + Mocha? (Y/n) 是否安裝單元測試
Setup e2e tests with Nightwatch(Y/n)? 是否安裝 e2e 測試配置完成後,能夠看到目錄下多出了一個項目文件夾,而後 cd 進入這個文件夾
啓動開發模式
npm run serve
項目打包
npm run build
代碼檢查
npm run lint
開發期間不要關閉命令窗口,若是關了,就從新 npm run serve
瀏覽器中打開 http://localhost:8080/
看到 vue 初始頁面,則說明初始化建立成功了
node_modules // 第三方包存儲目錄 public // 靜態資源,已被託管 src // 源代碼 assets // 資源目錄,存儲靜態資源,例如圖片等 components // 存儲其它組件的目錄 App.vue // 根組件 main.js // 入口文件 .gitignore // git 忽略文件 babel.config.js // 不用關心 package.json // 包說明文件 README.md // 項目說明文件 package-lock.json // 包的版本鎖定文件
找到 main.js 入口 > 加載 Vue > 加載 App 組件 > 建立 Vue 實例 > 將 App 組件替換到入口節點
組件 Component 是 Vue.js 最強大的功能之一。組件能夠擴展 HTML 元素,封裝可重用的代碼。
推薦把通用組件建立到 components 目錄中
單文件組件只是承載組件的容器而已,既不是全局也不是局部,若是要使用這個單文件組件,必須註冊
單文件組件模板結構以下:
<template> <div>foo 組件</div> </template> <script> export default { data() {}, methods: {} } </script> <style></style>
<style>
標籤有 scoped 屬性時,它的 CSS 只做用於當前組件中的元素在 main.js 文件中,能夠在任何組件中使用
import Vue from 'vue' import Com1 from './components/Com1.vue' Vue.component('Com1', Com1) // 接下來就能夠在任何組件中使用 Com1 組件了
在某個組價中局部註冊使用
<template> <div> <!-- 使用 Com2 組件 --> <Com2></Com2> </div> </template> <script> import Com2 from './components/Com2' export default { components: { Com2 } } </script>
初始化階段
beforeCreate
表示組件建立前的準備工做,爲事件的發佈訂閱和生命週期的開始作初始化
這個鉤子函數中數據拿不到,真實DOM也拿不到,這個鉤子在項目中咱們沒有什麼實際用途
beforeCreate () { // 表示組件建立前的準備工做( 初始化事件和生命週期 ) console.log('beforeCreate') console.log(this.msg) // undefind console.log(document.querySelector('p')) // null 沒有真實DOM }
created
表示組件建立結束,這個鉤子函數中數據拿到了,可是真實DOM沒有拿到
這個鉤子函數在項目通常數據請求,而後能夠進行一次默認數據的修改
created () { // 組件建立結束 console.log('created') console.log(this.msg) //(vue.js) 有數據 console.log(document.querySelector('p')) //null 沒有真實DOM axios({ url: './data.json' }).then( res => { this.msg = res }).catch( error => { throw error }) }
beforeMounte
表示組件裝載前的準備工做,判斷 el 選項有沒有, 判斷 template 選項有沒有, 若是沒有那麼須要手動裝載,若是有那麼經過 render 函數進行模板的渲染
這個鉤子函數中數據拿到了,真實DOM沒有拿到,這個鉤子函數在項目中數據請求,它也能夠進行一次數據修改
beforeMount () { console.log('beforeMount') console.log(this.msg) //(vue.js) 有數據 console.log(document.querySelector('p')) //null 沒有真實DOM // axios({ // url: './data.json' // }) // .then( res => { // this.msg = res // }) // .catch( error => { // throw error // }) }
mounted
表示組件裝載結束,就是咱們能夠在視圖中看到了,這個鉤子函數中數據拿到了,真實DOM也拿到了
這個鉤子函數在項目 DOM 操做就能夠進行了, 第三方庫的實例化
mounted () { console.log('mount') console.log(this.msg) //(vue.js) 有數據 console.log(document.querySelector('p')) //有真實DOM axios({ url: './data.json' }).then( res => { this.msg = res }).catch( error => { throw error }) }
總結:由上對比,咱們能夠知道, 數據請求越提早越好一些, created 經常使用於數據的請求和數據的修改, 第三方庫的實例化常在 mounted 中進行書寫
運行中階段
beforeUpdate
表示數據更新前的準備工做。這個鉤子不主動執行,當數據修改了纔會執行。這個鉤子函數中數據拿到了,而且拿到的是修改後的數據,DOM也輸出了
這個鉤子函數更多的工做內容爲:生成新的 VDOM , 而後經過 diff 算法進行兩次 VDOM 對比
這個鉤子在項目中由於他主要作的事情是內部進行的, 因此對咱們而言沒有太多的操做意義
updated
表示數據更新結束。經過 render 函數渲染真實 DOM 。這個鉤子函數的執行也是當數據修改的時候才執行。這個鉤子函數中數據拿到了, DOM也拿到了。
總結: 數據更新, 也要進行DOM操做那麼, 咱們使用update這個鉤子
銷燬階段
beforeDestroy
destroyed
這兩個鉤子無差異,在項目中作善後工做。手動清除一些計時器和一些方法,還有第三方實例化出來的對象
Vue.component ('LifeCircle', { template: '#life-circle', methods: { destroy () { this.$destroy() } }, created () { this.timer = setInterval( () => { console.log('1') },1000) }, beforeDestroy () { console.log('beforeDestory') }, destroyed () { console.log('destroyed') clearInterval( this.timer ) // 若是是用$destroy這個方法來清除組件, 那麼咱們必須手動清除這個組件的外殼 document.querySelector('#app div').remove() } }) new Vue ({ el: '#app', data: { flag: true } })
組件就像零散的積木,咱們須要把這些積木按照必定的規則拼裝起來,並且要讓它們互相之間能進行通信,這樣才能構成一個有機的完整系統。
在真實的應用中,組件最終會構成樹形結構,就像人類社會中的家族樹同樣
在樹形結構裏面,組件之間有幾種典型的關係:父子關係、兄弟關係、沒有直接關係。
相應地,組件之間有如下幾種典型的通信方案:
直接的父子關係
this.$refs
訪問子組件this.$parent
訪問其父組件this.$children
訪問其子組件父組件經過 Props
給子組件下發數據
咱們能夠用 v-bind 來動態地將 prop 綁定到父組件的數據。每當父組件的數據變化時,該變化也會傳導給子組件
// HelloWorld.vue <div> <child :myMessage="parentMsg"></child> // 父組件動態變量 parentMsg,經過 myMessage 傳遞給子組件 </div>
子組件經過prop屬性接受,prop中的變量名不能在data中定義
<script> import HelloWorld from './components/HelloWorld.vue' export default { name: 'app', props: ['myMessage'] // 經過 props 接收到傳遞過來的數據,props 是一個數組對象 components: { HelloWorld } } </script>
Prop 是單向綁定的,當父組件的屬性變化時,將傳導給子組件,可是反過來不會,這是爲了防止子組件無心間修改了父組件的狀態。
每次父組件更新時,子組件的全部 prop 都會更新爲最新值。這意味着你不該該在子組件內部改變 prop。若是你這麼作了,Vue 會在控制檯給出警告。
Prop 驗證
咱們能夠爲組件的 prop 指定驗證規則。若是傳入的數據不符合要求,Vue 會發出警告。這對於開發給他人使用的組件很是有用。要指定驗證規則,須要用對象的形式來定義 prop。
export default { props: { propA: Number, // 基礎類型檢測 (`null` 指容許任何類型) propB: [String, Number], // 多是多種類型 propC: { type: String, required: true // 必傳且是字符串 }, propD: { type: Number, default: 100 // 數值且有默認值 }, propE: { type: Object, default: function () { // 數組/對象的默認值應當由一個工廠函數返回 return { message: 'hello' } } }, propF: { validator: function (value) { // 自定義驗證函數 return value > 10 } } } }
type 能夠是下面原生構造器:
子組件經過 事件
方式給父組件發送消息
父組件使用 prop 傳遞數據給子組件。但子組件怎麼跟父組件通訊呢?
須要在子組件中調用 $emit()
方法發佈一個事件
methods: { incrementCounter: function () { this.counter += 1 this.$emit('increment') // 發佈一個名字叫 increment 的事件 // this.$emit(事件名, '參數對象"); } }
在父組件中提供一個子組件內部發布的事件處理函數
new Vue({ methods: { incrementTotal: function () { this.total += 1 } } });
在使用子組件的模板的標籤上訂閱子組件內部發布的事件
<div id="counter-event-example"> <!-- 訂閱子組件內部發布的 increment 事件 當子組件內部 $commit('increment') 發佈的時候,就會調用到父組件中的 incrementTotal 方法 --> <button-counter v-on:increment="incrementTotal"></button-counter> </div>
沒有直接關係
簡單場景:藉助於事件機制進行通信
有時候,非父子關係的兩個組件之間也須要通訊。在簡單的場景下,可使用一個空的 Vue 實例做爲事件總線:
var bus = new Vue(); // 觸發組件 A 中的事件 bus.$emit('id-selected', 1); // 在組件 B 建立的鉤子中監聽事件 bus.$on('id-selected', function (id) { // ... });
利用 sessionStorage 和 localStorage 進行通信
Vue 不像 jQuery 內置了 ajax 請求函數,在 Vue 中沒有提供這樣的功能。因此當咱們須要在 Vue 中和服務端進行通訊的時候可選擇的方式會更靈活一些。
注意:Vue 不提供的緣由是爲了讓 Vue 自己更專一於視圖部分,保持其漸進靈活的特性
axios 是一個基於 Promise 的第三方 HTTP 客戶端請求庫,能夠用於瀏覽器或者 Node.js。 axios 自己和 Vue 沒有一毛錢關係,只是簡單純粹的封裝了 HTTP 請求功能。能夠運行在任何支持 JavaScript 環境的平臺。
axios 依賴原生的 ECMAScript 6 Promise 支持。若是瀏覽器不支持 ECMAScript 6 Promise,可使用 es6-promise 進行兼容處理
npm install axios
const axios = require('axios'); // Make a request for a user with a given ID axios.get('/user?ID=12345').then(function (response) { // handle success console.log(response); }).catch(function (error) { // handle error console.log(error); }).finally(function () { // always executed }); // Want to use async/await? Add the `async` keyword to your outer function/method. async function getUser() { try { const response = await axios.get('/user?ID=12345'); console.log(response); } catch (error) { console.error(error); } }
axios.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }).then(function(response) { console.log(response) }).catch(function(error) { console.log(error) })
function getUserAccount() { return axios.get('/user/12345') } function getUserPermissions() { return axios.get('/user/12345/permissions') } axios.all([getUserAccount(), getUserPermissions()]).then( axios.spread(function(acct, perms) { // Both requests are now complete }) )
axios(config),咱們能夠像使用 $.ajax() 同樣來使用 axios
// Send a POST request axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } }) // GET request for remote image axios({ method: 'get', url: 'http://bit.ly/2mTM3nY', responseType: 'stream' }).then(function(response) { response.data.pipe(fs.createWriteStream('ada_lovelace.jpg')) })
補充內容:如下補充參考《 VueX(Vue狀態管理模式)》
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,能夠幫助咱們管理共享狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
若是您不打算開發大型單頁應用,使用 Vuex 多是繁瑣冗餘的。確實是如此——若是您的應用夠簡單,您最好不要使用 Vuex。一個簡單的 store 模式就足夠您所需了。可是,若是您須要構建一箇中大型單頁應用,您極可能會考慮如何更好地在組件外部管理狀態,Vuex 將會成爲天然而然的選擇。
在 VueX 對象中,其實不止有 state ,還有用來操做 state 中數據的方法集,以及當咱們須要對 state 中的數據須要加工的方法集等等成員。
成員列表:
首先,Vue 組件若是調用某個 VueX 的方法過程當中須要向後端請求時或者說出現異步操做時,須要 dispatch VueX 中 actions 的方法,以保證數據的同步。能夠說,action 的存在就是爲了讓 mutations 中的方法能在異步操做中起做用。
若是沒有異步操做,那麼咱們就能夠直接在組件內提交狀態中的M utations 中本身編寫的方法來達成對 state 成員的操做。注意,1.3.3節中有提到,不建議在組件中直接對 state 中的成員進行操做,這是由於直接修改(例如:this.$store.state.name = 'hello')的話不能被 VueDevtools 所監控到。
最後被修改後的 state 成員會被渲染到組件的原位置當中去。
npm install vuex --save
在 src 文件目錄下新建一個名爲 store 的文件夾,爲方便引入並在 store 文件夾裏新建一個index.js(若是腳手架生成有 vuex 那直接跳到 3 步)
import Vue from 'vue'; import Vuex from 'vuex'; // 掛載Vuex Vue.use(Vuex); // 建立VueX對象 const store = new Vuex.Store({ state:{ name:'helloVueX' // 存放的鍵值對就是所要管理的狀態 } }) export default store;
將 store 掛載到當前項目的 Vue 實例(main.js),這樣一來就能夠在任何一個組件裏面使用 this.$store 了
import Vue from 'vue'; import Vuex from 'vuex'; import store from './store'//引入store new Vue({ // el: '#app', store // store: store,將咱們建立的Vuex實例掛載到這個 vue 實例中 render: h => h(App), }).$mount('#app')
補充內容:Vue 的掛在方式一共,分別爲
el;
$mount() 手動掛載到節點;
template 以組件形式注入;
render 以原生 js 的寫法注入;
在組件中使用Vuex,例如在 App.vue 中,咱們要將 state 中定義的 name 拿來在 h1 標籤中顯示
<template> <div id='app'> name: <h1>{{ $store.state.name }}</h1> </div> </template>
或者要在組件方法中使用
methods:{ add(){ console.log(this.$store.state.name) // 注意,請不要在此處更改state中的狀態的值 } },
設置倉庫要保存的值和操做那些值的方法
回到 store 文件的 index.js 裏面,咱們先聲明一個 state 變量,並賦值一個空對象給它,裏面隨便定義兩個初始屬性值;而後再在實例化的 Vuex.Store 裏面傳入一個空對象,並把剛聲明的變量 state 仍裏面
import Vue from 'vue'; import Vuex from 'vuex'; //掛載Vuex Vue.use(Vuex); const state = { // 要設置的全局訪問的 state 對象 showFooter: true, changableNum: 0 // 要設置的初始屬性值 }; const store = new Vuex.Store({ state }); export default store;
mutations 是操做 state 數據的方法的集合,好比對該數據的修改、增長、刪除等等
具體的用法就是給裏面的方法傳入參數 state 或額外的參數,而後利用 vue 的雙向數據驅動進行值的改變,一樣的定義好以後也把這個 mutations 扔進 Vuex.Store 裏面
mutattions 也是一個對象,都有默認的形參([state] [,payload])
例如,咱們編寫一個方法,當被執行時,能把下例中的 name 值修改成 'jack',咱們只須要這樣作
import Vue from 'vue' import Vuex from 'vuex' // 掛載Vuex Vue.use(Vuex) const store = new Vuex.store({ state:{ name:'helloVueX' }, mutations:{ edit(state){ // es6 語法,等同 edit:funcion(){...} state.name = 'jack' } } }) export default store
而在組件中,咱們須要這樣去調用這個 mutation ——例如在 App.vue 的某個 method 中
this.$store.commit('edit')
在實際生產過程當中,會遇到須要在提交某個 mutation 時須要攜帶一些參數給方法使用
// 單個值提交時 this.$store.commit('edit') // 當須要多參提交時,推薦把他們放在一個對象中來提交 this.$store.commit('edit',{age: 15,sex: '男'})
接收掛載的參數
edit(state,payload){ state.name = 'jack' console.log(payload) // 15 或 {age: 15,sex: '男'} }
另外一種提交方式
this.$store.commit({ type:'edit', payload:{ age: 15, sex: '男' } })
爲了配合 Vue 的響應式數據,咱們在 Mutations 的方法中,應當使用 Vue 提供的方法來進行操做。若是使用 delete 或者 xx.xx = xx 的形式去刪或增,則 Vue 不能對數據進行實時響應。
Vue.set 爲某個對象設置成員的值,若不存在則新增
例如對state對象中添加一個age成員
Vue.set(state, 'age', 15)
Vue.delete 刪除成員
將剛剛添加的 age 成員刪除
Vue.delete(state, 'age')
vuex 官方 API 提供了一個 getters,和 vue 計算屬性 computed 同樣,來實時監聽 state 值的變化(最新狀態),並把它也仍進 Vuex.Store 裏面
getters 中的方法有兩個默認參數
getters:{ nameInfo(state){ return "姓名:" + state.name // 當前 VueX 對象中的狀態對象 }, fullInfo(state, getters){ // 參數 getters 用於將 getters 下的其餘 getter ,這裏也就是 nameInfo 拿來用 return getters.nameInfo + '年齡:' + state.age } }
組件中調用
this.$store.getters.fullInfo
因爲直接在 mutation 方法中進行異步操做,將會引發數據失效。因此提供了 Actions 來專門進行異步操做,最終提交 mutation 方法
Actions 中的方法有兩個默認參數,Action 提交的是 mutation,而不是直接變動狀態
因爲 setTimeout 是異步操做,因此須要使用 actions,請看以下示例
const store = new Vuex.Store({ state: { name: '' }, mutations: { edit(state, payload){ state.name = 'jack' console.log(payload) // 15 或 {age: 15,sex: '男'} } }, actions: { aEdit(context, payload){ setTimeout(() => { context.commit('edit', payload) // 經過 commit 提交 mutation 的方式來修改數據狀態 }, 2000) } } })
在組件中調用
this.$store.dispatch('aEdit', {age: 15})
當項目龐大,狀態很是多時,能夠採用模塊化管理模式。Vuex 容許咱們將 store 分割成模塊(module)。每一個模塊擁有本身的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行一樣方式的分割。
models:{ a:{ state:{}, getters:{}, .... } }
組件內調用模塊a的狀態
this.$store.state.a
而提交或者 dispatch 某個方法和之前同樣,會自動執行全部模塊內的對應 type 的方法
this.$store.dispatch('aEditKey')
模塊中 mutations 和 getters 中的方法接受的第一個參數是自身局部模塊內部的 state
models: { a: { state: {key: 5}, mutations: { editKey (state) { state.key = 9 } } } }
getters 中方法的第三個參數是根節點狀態
models: { a: { state: {key: 5}, getters: { getKeyCount (state, getter, rootState) { return rootState.key + state.key } } } }
actions 中方法獲取局部模塊狀態是 context.state,根節點狀態是 context.rootState
models: { a: { state: {key: 5}, actions: { aEidtKey (context) { if (context.state.key === context.rootState.key) { context.commit('editKey') } } } } }
補充內容:高級用法 《 關於mapState,mapGetters,mapMutations,mapActions的學習筆記》
由於只有 mutation 才能改變 state, 若是須要須要執行異步操做修改 state 須要以下操做
補充內容:Vue 建議咱們 mutation 類型用大寫常量表示
簡單一句話:非異步操做修改狀態使用 mutation,異步操做修改狀態使用 action 。其次咱們使用 Vuex 並不意味着你須要將全部的狀態放入 Vuex。雖然將全部的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。若是有些狀態嚴格屬於單個組件,最好仍是做爲組件的局部狀態。你應該根據你的應用開發須要進行權衡和肯定。
補充內容:如下補充參考《 從頭開始學習vue-router》
這裏的路由並非指咱們平時所說的硬件路由器,這裏的路由就是 SPA(單頁應用)的路徑管理器。再通俗的說,vue-router 就是 WebApp 的連接路徑管理系統。
vue-router 是 Vue.js 官方的路由插件,它和 vue.js 是深度集成的,適合用於構建單頁面應用。vue 的單頁面應用是基於路由和組件的,路由用於設定訪問路徑,並將路徑和組件映射起來。傳統的頁面應用,是用一些超連接來實現頁面切換和跳轉的。在 vue-router 單頁面應用中,則是路徑之間的切換,也就是組件的切換。路由模塊的本質 就是創建起 url 和頁面之間的映射關係。
至於咱們爲啥不能用a標籤,這是由於用 Vue 作的都是單頁應用(當你的項目準備打包時,運行 npm run build 時,就會生成 dist 文件夾,這裏面只有靜態資源和一個 index.html 頁面),因此你寫的 標籤是不起做用的,你必須使用 vue-router 來進行管理。
使用腳手架初始化的時候能夠初始化安裝 vue-router,若是沒有就手動下載和使用
什麼是單頁應用
單頁應用(英語:single-page application,縮寫SPA):在傳統的網頁應用中,瀏覽器更多的是充當一個展現層,路由處理、服務調用、頁面跳轉流程都由服務端來處理。即 MVC 都放在服務器端,SPA 技術將邏輯從服務器轉移到了客戶端。這致使 Web 服務器發展爲一個純數據 API 或 Web 服務。這種架構的轉變在一些圈子中被稱爲「瘦服務器架構」,以強調複雜性已從服務端轉移到客戶端,並認爲這最終下降了系統的總體複雜性。
傳統的網站有如下特色
重服務端,因爲MVC 都存在於服務器上,所以這類應用在開發資源和開發的重心都偏向後端,每每是後端工程師來主導整個項目開發;
頁面頻繁刷新,因爲瀏覽器端只是一個展示層,當頁面功能有所變化的時,頁面就刷新,這會致使資源的浪費,用戶須要花費額外的時間等待頁面刷新,用戶體驗不佳。
而單頁面應用,只有一張 Web 頁面的應用,是一種從Web服務器加載的富客戶端,單頁面跳轉僅刷新局部資源 ,公共資源(js、css等)僅需加載一次。
單頁應用的優缺點
優勢
缺點
vue-router 實現原理
vue-router 在實現單頁面前端路由時,提供了兩種方式:Hash 模式和 History 模式;根據 mode 參數來決定採用哪種方式。
使用 URL 的 hash 來模擬一個完整的 URL,因而當 URL 改變時,頁面不會從新加載。 hash(#)是URL 的錨點,表明的是網頁中的一個位置,單單改變#後的部分,瀏覽器只會滾動到相應位置,不會從新加載網頁,也就是說 hash 出如今 URL 中,但不會被包含在 http 請求中,對後端徹底沒有影響,所以改變 hash 不會從新加載頁面;同時每一次改變#後的部分,都會在瀏覽器的訪問歷史中增長一個記錄,使用「後退」按鈕,就能夠回到上一個位置;因此說 Hash 模式經過錨點值的改變,根據不一樣的值,渲染指定 DOM 位置的不一樣數據。hash 模式的原理是 onhashchange 事件(監測 hash 值變化),能夠在 window 對象上監聽這個事件。
History模式
因爲 hash 模式會在 url 中自帶#,若是不想要很醜的 hash,咱們能夠用路由的 history 模式,只須要在配置路由規則時,加入"mode: 'history'",這種模式充分利用了 html5 history interface 中新增的 pushState() 和 replaceState() 方法。這兩個方法應用於瀏覽器記錄棧,在當前已有的 back、forward、go 基礎之上,它們提供了對歷史記錄修改的功能。只是當它們執行修改時,雖然改變了當前的 URL,但瀏覽器不會當即向後端發送請求。
// main.js文件中 const router = new VueRouter({ mode: 'history', routes: [...] })
npm install vue-router
新建 router 文件夾,而後創建一個 index.js 文件,寫入路由的代碼
import Vue from 'vue' //引入Vue import Router from 'vue-router' //引入vue-router import Hello from '@/components/HelloWorld' //引入組件目錄下的 HelloWorld.vue 組件 import Demo from '@/components/Demo' //引入組件目錄下的 Demo.vue 組件 Vue.use(Router) // Vue 全局使用 Router export default new Router({ // 配置路由,這裏是個數組 // 每個連接都是一個對象 routes: [{ path: '/', //連接路徑 當路徑爲 http://localhost:8080/#/ 顯示此組件 name: 'Hello', //路由名稱, component: Hello //對應要顯示的組件 }, { //每個連接都是一個對象 path: '/demo', //連接路徑,當路徑爲 http://localhost:8080/#/demo 顯示此組件 name: 'Demo', //路由名稱, component: Demo //對應要顯示的組件 }] })
在 main.js 全局註冊 router
import Vue from 'vue' import App from './App.vue' import router from "./router/index.js" // 導入路由 Vue.config.productionTip = false new Vue({ // el: '#app', router, // 使用路由 }).$mount('#app')
在 App.js 中要使用 router-view
組件
<template> <div id="app"> <router-view></router-view> // 路由匹配到的組件將渲染在這裏 </div> </template>
聲明式導航
<template> <div id="app"> <p> <router-link to="home">Home</router-link> // 字符串的形式 //下面幾種爲動態綁定 <router-link :to="index">Home</router-link> <script> export default { data () { return { index: '/' } } } </script> // 這個路徑就是路由中配置的路徑 <router-link :to="{ path: '/home' }">Home</router-link> // 在路由的配置的時候,添加一個name屬性 <router-link :to="{ name: 'User'}">User</router-link> // 直接路由帶查詢參數 query,地址欄變成 /apple?color=red <router-link :to="{path: 'apple', query: {color: 'red' }}">to apple</router-link> </p> <router-view></router-view> // 路由匹配到的組件將渲染在這裏 </div> </template>
內容補充:《 vue2.0中router-link詳解》
在vue2.0中,原來的 v-link 指令已經被 <router-link> 組件替代了
<router-link> 組件支持用戶在具備路由功能的應用中點擊導航。默認渲染爲帶有正確鏈接的 標籤
<router-link> 組件的屬性有:
to(必選參數):類型string/location。表示目標路由的連接,該值能夠是一個字符串,也能夠是動態綁定的描述目標位置的對象
tag:類型: string 默認值: 'a'。若是想要 <router-link> 渲染成某種標籤,例如 <li>。 因而咱們使用 tag
active-class:類型: string 默認值: "router-link-active"。設置連接激活時使用的 CSS 類名
exact-active-class:類型: string 默認值: "router-link-exact-active"。配置當連接被精確匹配的時候應該激活的 class
exact:類型: boolean 默認值: false。"是否激活" 默認類名的依據是 inclusive match (全包含匹配)
event:類型: string | Array<string> 默認值: 'click'。聲明能夠用來觸發導航的事件。能夠是一個字符串
replace:類型: boolean 默認值: false。設置 replace 屬性的話,當點擊時,會調用 router.replace() 而不是 router.push(),因而導航後不會留下 history 記錄
append:類型: boolean 默認值: false。設置 append 屬性後,則在當前 (相對) 路徑前添加基路徑
編程式導航
除了使用 建立 a 標籤來定義導航連接,咱們還能夠藉助 router 的實例方法,經過編寫代碼來實現前進和後退 this.router.go(1):功能跟咱們瀏覽器上的後退和前進按鈕同樣,這在業務邏輯中常常用到。好比條件不知足時,咱們須要後退。
app.vue 文件里加入一個按鈕,按鈕並綁定一個 goback() 方法
<button @click="goback">後退</button> <script> export default { name: 'app', methods: { goback () { this.$router.go(-1); } } } </script>
編程式導航都做用就是跳轉,好比咱們判斷用戶名和密碼正確時,須要跳轉到用戶中心頁面或者首頁,都用到這個編程的方法 this.$router.push
來操做路由。
export default { name: 'app', methods:{ goHome(){ this.$router.push('/'); // 字符串 this.$router.push({name: 'applename', query: {color: 'red' }}); // 命名路由帶查詢參數 query,地址欄變成/apple?color=red } } }
補充內容:動態路由的獲取參數方法
在組件中:{{$route.params.color}}
在js裏:this.$route.params.color
export default new Router({ routes: [{ path: '/', name: 'Hello', component: Hello }, { // 對某個路由使用子路由,那麼需得在 Hi1 組件內使用 <router-view></router-view> 不然切換 // 了子路由也沒法顯示出來 path: '/h1', name: 'Hi1', component: Hi1, // 當訪問 /h1 的時候顯示Hi1這個組件 children: [{ path: '/', component: Hi1 }, { path: 'h2', component: Hi2 }] }] })
重定向也是經過 routes 配置來完成,下面例子是從 /a 重定向到 /b
const router = new VueRouter({ routes: [{ path: '/a', redirect: '/b' }] })
重定向的目標也能夠是一個命名的路由
const router = new VueRouter({ routes: [{ path: '/a', redirect: { name: 'foo' } }] })
補充內容:《 路由守衛》
路由跳轉前作一些驗證,好比登陸驗證,是網站中的廣泛需求。對此,vue-route 提供的 beforeRouteUpdate 能夠方便地實現導航守衛(navigation-guards)。