Vue的完整學習筆記(介紹,基礎語法,元件開發與通訊,模組化概念,webpack,vue-cli,vue-router,vuex,axios)

小莫の咕噠君發表於2020-07-20

前端發展和架構

先聊一下前端開發模式的發展。

靜態頁面

最初的網頁以HTML為主,是純靜態的網頁。網頁是隻讀的,資訊流只能從服務端到客戶端單向流通。開發人員也只關心頁面的樣式和內容即可。

非同步重新整理,操作DOM

1995年,網景工程師Brendan Eich 花了10天時間設計了JavaScript語言.

隨著JavaScript的誕生,我們可以操作頁面的DOM元素及樣式,頁面有了一些動態的效果,但是依然是以靜態為主。

ajax盛行:

  • 2005年開始,ajax逐漸被前端開發人員所重視,因為不用重新整理頁面就可以更新頁面的資料和渲染效果。
  • 此時的開發人員不僅僅要編寫HTML樣式,還要懂ajax與後端互動,然後通過JS操作Dom元素來實現頁面動態效果。比較流行的框架如Jquery就是典型代表。

MVVM,關注模型和檢視

2008年,google的Chrome釋出,隨後就以極快的速度佔領市場,超過IE成為瀏覽器市場的主導者。

2009年,Ryan Dahl在谷歌的Chrome V8引擎基礎上,打造了基於事件迴圈的非同步IO框架:Node.js。

  • 基於事件迴圈的非同步IO
  • 單執行緒執行,避免多執行緒的變數同步問題
  • JS可以編寫後臺程式碼,前後臺統一程式語言

node.js的偉大之處不在於讓JS邁向了後端開發,而是構建了一個龐大的生態系統。

2010年,NPM作為node.js的包管理系統首次釋出,開發人員可以遵循Common.js規範來編寫Node.js模組,然後釋出到NPM上供其他開發人員使用。目前已經是世界最大的包模組管理系統。

隨後,在node的基礎上,湧現出了一大批的前端框架:

三種架構的介紹

MVVM模式

  • M:即Model,模型,包括資料和一些基本操作
  • V:即View,檢視,頁面渲染結果
  • VM:即View-Model,模型與檢視間的雙向操作(無需開發人員干涉)

在MVVM之前,開發人員從後端獲取需要的資料模型,然後要通過DOM操作Model渲染到View中。而後當使用者操作檢視,我們還需要通過DOM獲取View中的資料,然後同步到Model中。

而MVVM中的VM要做的事情就是把DOM操作完全封裝起來,開發人員不用再關心Model和View之間是如何互相影響的:

  • 只要我們Model發生了改變,View上自然就會表現出來。
  • 當使用者修改了View,Model中的資料也會跟著改變。

把開發人員從繁瑣的DOM操作中解放出來,把關注點放在如何操作Model上。

而我們今天要學習的,就是一款MVVM模式的框架:Vue

認識Vue

Vue (讀音 /vjuː/,類似於 view) 是一套用於構建使用者介面的漸進式框架。與其它大型框架不同的是,Vue 被設計為可以自底向上逐層應用。Vue 的核心庫只關注檢視層,不僅易於上手,還便於與第三方庫或既有專案整合。另一方面,當與現代化的工具鏈以及各種支援類庫結合使用時,Vue 也完全能夠為複雜的單頁應用提供驅動。

前端框架三巨頭:Vue.js、React.js、AngularJS,vue.js以其輕量易用著稱,vue.js和React.js發展速度最快,AngularJS還是老大。C

官網:https://cn.vuejs.org/

參考:https://cn.vuejs.org/v2/guide/

Git地址:https://github.com/vuejs

尤雨溪,Vue.js 創作者,Vue Technology創始人,致力於Vue的研究開發。

Node和NPM

NPM是Node提供的模組管理工具,可以非常方便的下載安裝很多前端框架,包括Jquery、AngularJS、VueJs都有。為了後面學習方便,我們先安裝node及NPM工具。

NPM的簡單使用

安裝

  1. 去node.js官網下載node.js然後一路下一步安裝
  2. 安裝完畢後開啟cmd視窗輸入npm -v ,出現版本號即為安裝成功

為什麼要使用

  • 很久很久以前我們都是把js檔案從網上下載解壓然後cpoy到專案中
  • 然後有了cdn我們就直接從網上一個一個找jquery,bootstrap,layui,mui,xxx等jscdn連結copy到檔案中
  • 現在我們只需要進入專案存放js的目錄執行一些npm的命令即可管理依賴感覺就像是後端的maven吧,好吧其實我也是沒辦法啊,現在Github上全是提供npm的安裝方式 ?

簡單的增刪改

安裝
  1. 使用命令列進入需要存放js的目錄,執行npm -init -y進行初始化-y是省的問一大堆的問題
  2. 命令執行完畢後就會在資料夾下生成package.json檔案,這個檔案和maven的pom檔案類似,這裡記錄著下載過的依賴
  3. 使用 npm i 名稱 安裝 就會把所有的依賴檔案拉下來 注意:是i不是-i
  4. 安裝指定版本的模組npm i 名稱@版本號
  5. 然後就可以在頁面把下載下來的js檔案引入
  6. 下載的所有js模組都會存放在node_modules下,並在package.json檔案中記錄,如果不小心刪除了node_modules只需要執行npm i命令就會根據package.json中的記錄把所有檔案都拉下來
解除安裝

解除安裝只需要執行npm uninstall 名稱就可以解除安裝,並且會把package.json中的記錄刪除掉

更新

更新只需要執行npm update名稱就可以更新,並且會把package.json中的版本號更新

命令作用
npm -init -y初始化專案,-y可以省掉一些詢問
npm install 名稱 -g/–save下載依賴,-g是全域性安裝,–save是本地安裝
npm uninstall 名稱解除安裝依賴,並且會把package.json中的記錄刪除掉
npm undate 名稱更新依賴,並且會把package.json中的版本號更新

修改映象

npm預設的倉庫地址是在國外網站,速度較慢,建議大家設定到淘寶映象。但是切換映象是比較麻煩的。推薦一款切換映象的工具:nrm

我們首先安裝nrm,這裡-g代表全域性安裝。可能需要一點兒時間

npm install nrm -g

安裝成功後通過nrm ls命令檢視npm的倉庫列表,帶*的就是當前選中的映象倉庫:

可以通過nrm test 名稱來測試映象源的速度,直接nrm test是測試所有的

通過nrm use 名稱來指定要使用的映象源:

安裝Vue

這裡我們使用npm的方式

  1. 建立一個空資料夾vue-learning,然後開啟命令列進入該資料夾

  2. 使用 npm init -y 進行初始化

  3. 安裝Vue,輸入命令:npm install vue

    注意,如果要安裝的模組如果和當前資料夾名稱一樣會報錯,比如vue-learning改名為vue,在執行npm install vue時就會報錯

然後就會在hello-vue目錄發現一個node_modules目錄,並且在下面有一個vue目錄。

node_modules是通過npm安裝的所有模組的預設位置。

瀏覽器Vue外掛的安裝

Vue宣告式渲染

<body>
<!--vue物件的html模板-->
<div id="app">
    <!--花括號,js表示式-->
    <h1>大家好,我是{{name}}</h1>
    <h1>你好{{1+1}}</h1>
</div>

</body>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    //初始化一個vue例項
    const app=new Vue({
        el: "#app", //element的縮寫,選擇器
        data: { //資料模型
            name: "劉德華"
        }
    });
</script>
  • 首先通過 new Vue()來建立Vue例項
  • 然後建構函式接收一個物件,物件中有一些屬性:
    • el:是element的縮寫,通過id選中要渲染的頁面元素,本例中是一個div
    • data:資料,資料是一個物件,裡面有很多屬性,都可以渲染到檢視中
      • name:這裡我們指定了一個name屬性
  • 頁面中的h2元素中,我們通過{{name}}的方式,來渲染剛剛定義的name屬性。

開啟頁面檢視效果:

雙向繫結

我們對剛才的案例進行簡單修改:

<body>
<!--vue物件的html模板-->
<div id="app">
    <!--雙向繫結,v-model:資料模型-->
    <input type="text" v-model="num"/>
    <!--花括號,js表示式-->
    <h1>大家好,我是{{name}},{{num}}為妹子迷戀我</h1>
    <h1>你好{{1+1}}</h1>
</div>

</body>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    //初始化一個vue例項
    const app=new Vue({
        el: "#app", //element的縮寫,選擇器
        data: { //資料模型
            name: "劉德華",
            num: 100
        }
    });
</script>
  • 我們在data新增了新的屬性:num
  • 在頁面中有一個input元素,通過v-modelnum進行繫結。
  • 同時通過{{num}}在頁面輸出

我們可以觀察到,輸入框的變化引起了data中的num的變化,同時頁面輸出也跟著變化。

  • input與num繫結,input的value值變化,影響到了data中的num值
  • 頁面{{num}}與資料num繫結,因此num值變化,引起了頁面效果變化。

沒有任何dom操作,這就是雙向繫結的魅力。

注意,這個input要放在#app裡面

事件處理

我們在頁面新增一個按鈕,做到點選按鈕時候讓num自增1

<body>
<!--vue物件的html模板-->
<div id="app">
    <!--雙向繫結,v-model:資料模型-->
    <input type="text" v-model="num"/>
    <!--定義一個事件,v-on:事件名=js表示式-->
    <input type="button" value="點我加妹子"  v-on:click="incr" /> <!--v-on:click="num++"-->
    <!--花括號,js表示式-->
    <h1>大家好,我是{{name}},{{num}}為妹子迷戀我</h1>
    <h1>你好{{1+1}}</h1>
</div>

</body>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    //初始化一個vue例項
    const app=new Vue({
        el: "#app", //element的縮寫,選擇器
        data: { //資料模型
            name: "劉德華",
            num: 100
        },
        methods: {
            incr(){
                this.num++
            }
        }
    });
</script>

Vue例項

建立Vue例項

每個 Vue 應用都是通過用 Vue 函式建立一個新的 Vue 例項開始的:

var vm = new Vue({
  // 選項
})

在建構函式中傳入一個物件,並且在物件中宣告各種Vue需要的資料和方法,包括:

  • el
  • data
  • methods

等等

接下來我們一 一介紹。

模板和元素

每個Vue例項都需要關聯一段Html模板,Vue會基於此模板進行檢視渲染。

我們可以通過el屬性來指定。

例如一段html模板:

<div id="app">

</div>

然後建立Vue例項,關聯這個div

var vm = new Vue({
    el:"#app"
})

這樣,Vue就可以基於id為app的div元素作為模板進行渲染了。在這個div範圍以外的部分是無法使用vue特性的。

資料

當Vue例項被建立時,它會嘗試獲取在data中定義的所有屬性,用於檢視的渲染,並且監視data中的屬性變化,當data發生改變,所有相關的檢視都將重新渲染,這就是“響應式“系統。

html:

<div id="app">
    <input type="text" v-model="name"/>
</div>

js:

var vm = new Vue({
    el:"#app",
    data:{
        name:"劉德華"
    }
})
  • name的變化會影響到input的值
  • input中輸入的值,也會導致vm中的name發生改變

方法和this

Vue例項中除了可以定義data屬性,也可以定義方法,並且在Vue例項的作用範圍內使用。

html:

<!--vue物件的html模板-->
<div id="app">
    <!--雙向繫結,v-model:資料模型-->
    <input type="text" v-model="num" />
    <!--定義一個事件,v-on:事件名=js表示式-->
    <input type="button" value="點我加妹子"  v-on:click="incr" /> <!--v-on:click="num++"-->
    <!--花括號,js表示式-->
    <h1>大家好,我是{{name}},有{{num}}為妹子迷戀我</h1>
    <h1>你好{{1+1}}</h1>
</div>

js:

//初始化一個vue例項
    const app=new Vue({
        el: "#app", //element的縮寫,選擇器
        data: { //資料模型
            name: "劉德華",
            num: 100
        },
        methods: {
            incr(){
                this.num++
            }
        }
    });

this:

生命週期鉤子

生命週期

每個 Vue 例項在被建立時都要經過一系列的初始化過程 :建立例項,裝載模板,渲染模板等等。Vue為生命週期中的每個狀態都設定了鉤子函式(監聽函式)。每當Vue例項處於不同的生命週期時,對應的函式就會被觸發呼叫。

生命週期:

鉤子函式

  • beforeCreated:我們在用Vue時都要進行例項化,因此,該函式就是在Vue例項化時呼叫,也可以將他理解為初始化函式比較方便一點,在Vue1.0時,這個函式的名字就是init。
  • created:在建立例項之後進行呼叫。
  • beforeMount:頁面載入完成,沒有渲染。如:此時頁面還是{{name}}
  • mounted:我們可以將他理解為原生js中的window.οnlοad=function({.,.}),或許大家也在用jquery,所以也可以理解為jquery中的$(document).ready(function(){….}),他的功能就是:在dom文件渲染完畢之後將要執行的函式,該函式在Vue1.0版本中名字為compiled。 此時頁面中的{{name}}已被渲染成劉德華
  • beforeUpdate:元件更新之前。
  • updated:元件更新之後。
  • beforeDestroy:該函式將在銷燬例項前進行呼叫 。
  • destroyed:改函式將在銷燬例項時進行呼叫。

例如:created代表在vue例項建立後;

我們可以在Vue中定義一個created函式,代表這個時期的鉤子函式:

  const app=new Vue({
        el: "#app", //element的縮寫,選擇器
        created() {
            this.num=520
        },
        data: { //資料模型
            name: "劉德華",
            num: 100
        },
        methods: {
            incr(){
                console.log(this);
                this.num++
            }
        }
    });

重新整理頁面就是520,data中定義的1就會被覆蓋

陣列操作

push(): 在末尾新增
pop():在末尾刪除
shift(): 在開頭刪除
unshift():在開頭新增
splice() :可用於刪除,替換,新增
sort():排序
reverse():反轉

響應式注意點

如果在物件中沒有定義並且初始化值的key普通操作是不能做到響應式的。

指令

什麼是指令?

指令 (Directives) 是帶有 v- 字首的特殊特性。指令特性的預期值是:單個 JavaScript 表示式。指令的職責是,當表示式的值改變時,將其產生的連帶影響,響應式地作用於 DOM。

例如我們在入門案例中的v-on,代表繫結事件。

插值表示式

花括號

{{表示式}}

說明:

  • 該表示式支援JS語法,可以呼叫js內建函式(必須有返回值)
  • 表示式必須有返回結果。例如 1 + 1,沒有結果的表示式不允許使用,如:{{var a = 1 + 1}};
  • 可以直接獲取Vue例項中定義的資料或函式
<body>
<!--vue物件的html模板-->
<div id="app">
    <!--省略其他-->
    <h1>{{sum()}}個人</h1>
</div>

</body>
<script src="node_modules/vue/dist/vue.js"></script>
<script>
    //初始化一個vue例項
    const app=new Vue({
        el: "#app", //element的縮寫,選擇器
        created() {
            this.num=520
        },
        data: { //資料模型
            name: "劉德華",
            num: 100
        },
        methods: {
            incr(){
                console.log(this);
                this.num++
            },
            sum(){
                return this.num+100;
            }
        }
    });
</script>

插值閃爍現象

使用{{}}方式在網速較慢時會出現問題。在資料未載入完成時,頁面會顯示出原始的{{}},載入完畢後才顯示正確資料,我們稱為插值閃爍。

我們將網速調慢一些,然後試試看剛才的案例:

v-text和v-html

使用v-text和v-html指令來替代{{}}

說明:

  • v-text:將資料輸出到元素內部,如果輸出的資料有HTML程式碼,會作為普通文字輸出
  • v-html:將資料輸出到元素內部,如果輸出的資料有HTML程式碼,會被渲染

html:

<h1>插值表示式</h1>
<ul>
    <li v-text="name">張學友</li>
    <span v-text="num">人數</span>
    <li v-html="cname">學友哥</li>
</ul>

js:

data: { //資料模型
    name: "劉德華",
    num: 100,
    cname: "<span style='color: red'>華哥</span>"
},

重新整理頁面,效果如下,並且不會出現插值閃爍,當沒有資料時,會顯示空白。

v-pre

<div id="app">
    <p>{{message}}</p>
<!--我想要顯示{{message}}-->
    <p v-pre>{{message}}</p>

</div>

v-cloak

css:

<style>
    [v-cloak] {
        display: none;
    }
</style>

html:

<!-- 網路延遲可能會顯示{{message}}-->
<p>{{message}}</p>

<!--兩種解決方案-->

<!-- 1.使用v-clock在載入之前使用一個樣式來替換-->
<p v-cloak>{{message}}</p>
<!-- 2. 使用v-text,在載入之前使用'資訊'來替換-->
<p v-text="message">資訊</p>
</div>

js

setTimeout(()=>{
    const app=new Vue({
        el: "#app",
        data: {
            message: "你好呀"
        }
    })
    },2000);

v-model

剛才的v-text和v-html可以看做是單向繫結,資料影響了檢視渲染,但是反過來就不行。接下來學習的v-model是雙向繫結,檢視(View)和模型(Model)之間會互相影響。

既然是雙向繫結,一定是在檢視中可以修改資料,這樣就限定了檢視的元素型別。目前v-model的可使用元素有:

  • input
  • select
  • textarea
  • checkbox
  • radio
  • components(Vue中的自定義元件)

基本上除了最後一項,其它都是表單的輸入項。

checkbox案例

html:

<h1>v-model</h1>
<input type="checkbox" value="ios" v-model="language"/>ios
<input type="checkbox" value="java" v-model="language"/>java
<input type="checkbox" value="php" v-model="language"/>php <br/>
您選擇了:<span v-text="language">您配嗎?</span>

js:

data: { //資料模型
            name: "劉德華",
            num: 100,
            cname: "<span style='color: red'>華哥</span>",
            language: []
},

效果:

講道理,這個括號看著是不是很不爽?,稍微進行修改。{{這裡面是可以寫表示式的(v-text和v-html同理)}}

您選擇了:<span v-text="language.join(',')">您配嗎?</span>

多選使用陣列來儲存,比如checkbox和select。單選比如radio使用字串或數字來儲存

html:

<input type="radio" value="" v-model="sex"/><input type="radio" value="" v-model="sex"/><br/>
性別: {{sex}}

js:

data: { //資料模型
            name: "劉德華",
            num: 100,
            cname: "<span style='color: red'>華哥</span>",
            language: [],
            sex: ""
},

原理

修飾符

v-on

v-on指令用於給頁面元素繫結事件。

語法:v-on:事件名="js片段或函式名"

基礎語法

前面我們已經寫過了點選事件

html:

 <!--定義一個事件,v-on:事件名=js表示式-->
<input type="button" value="點我加妹子"  v-on:click="incr" /> <!--v-on:click="num++"-->

js:

methods: {
            incr(){
                console.log(this);
                this.num++
            },
            sum(){
                return this.num+100;
            }
}

另外,事件繫結可以簡寫,例如v-on:click='add'可以簡寫為@click='add'

事件修飾符

在事件處理程式中呼叫 event.preventDefault()event.stopPropagation() 是非常常見的需求。儘管我們可以在方法中輕鬆實現這點,但更好的方式是:方法只有純粹的資料邏輯,而不是去處理 DOM 事件細節。

為了解決這個問題,Vue.js 為 v-on 提供了事件修飾符。修飾符是由點開頭的指令字尾來表示的。

  • .stop :阻止事件冒泡到父元素
  • .prevent:阻止預設事件發生
  • .capture:使用事件捕獲模式
  • .self:只有元素自身觸發事件才執行。(冒泡或捕獲的都不執行)
  • .once:只執行一次

阻止預設事件,例如阻止瀏覽器預設的右鍵顯示選單事件

html:

<!--定義一個事件,v-on:事件名=js表示式-->
<input type="button" value="點我加妹子"  v-on:click="incr" /> <!--v-on:click="num++"-->
<!--右擊事件-->
<input type="button" value="右擊你懂得" @contextMenu="decr($event)"/>  

js:

methods: {
            incr(){
                this.num++
            },
            decr(ev){
                ev.preventDefault();
                this.num--;
            }
}

不用DOM操作

<input type="button" value="右擊你懂得" @contextMenu.prevent="decr()"/>

按鍵修飾符

在監聽鍵盤事件時,我們經常需要檢查常見的鍵值。Vue 允許為 v-on 在監聽鍵盤事件時新增按鍵修飾符:

html:

<!-- 只有在 keyCode 是 13 時呼叫 submit() -->
<input text="text" v-model="num" @keyup.13="submit"/>

js:

submit(){
     console.log("你已經提交了");
}

A是65

記住所有的 keyCode 比較困難,所以 Vue 為最常用的按鍵提供了別名:

  • .enter
  • .tab
  • .delete (捕獲“刪除”和“退格”鍵)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

例如上面的事件呼叫還可以這樣寫

自定義按鍵修飾符別名

你還可以通過全域性 config.keyCodes 物件自定義按鍵修飾符別名

// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

組合按鍵修飾符

可以用如下修飾符來實現僅在按下相應按鍵時才觸發滑鼠或鍵盤事件的監聽器。

  • .ctrl
  • .alt
  • .shift
  • .meta

注意:在 Mac 系統鍵盤上,meta 對應 command 鍵 (⌘)。在 Windows 系統鍵盤 meta 對應 Windows 徽標鍵 (⊞)。在 Sun 作業系統鍵盤上,meta 對應實心寶石鍵 (◆)。在其他特定鍵盤上,尤其在 MIT 和 Lisp 機器的鍵盤、以及其後繼產品,比如 Knight 鍵盤、space-cadet 鍵盤,meta 被標記為“META”。在 Symbolics 鍵盤上,meta 被標記為“META”或者“Meta”。

<input text="text" v-model="num" @keyup.ctrl.enter="submit"/>

v-for和v-bind

遍歷資料渲染頁面是非常常用的需求,Vue中通過v-for指令來實現。

遍歷陣列

語法:

v-for="item in items"
  • items:要遍歷的陣列,需要在vue的data中定義好。
  • item:迭代得到的陣列元素的別名

案例

v-bin可以縮寫為':'

html:

<div id="app">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
    <div class="container">
        <div class="row">
            <p
                    style="text-align: center;font-size:28px;color:#FFF;background: linear-gradient(#FF7F00, #FF82AB);">
                <b>
                    <i>GODDESS</i>
                </b>
            </p>
             遍歷
            <div class="col-md-3" v-for="(user,index,key) in goddess" :key="index" style="text-align: center;">
                <p>
                    <img v-bind:src="user.image" style="vertical-align:text-bottom;" height="200" />
                </p>
                <p>
                    {{index+1}}
                <p>
                    {{user.name}}
                </p>
                <p>
                    {{user.gender}}
                </p>
                <p>
                    {{user.age}}
                </p>
            </div>
        </div>
    </div>
 <div id="app">
js

data: { //資料模型
            name: "劉德華",
            num: 100,
            cname: "華哥",
            language: [],
            sex: "",
            year: 2020,
            goddess: [
                { image: 'https://ftp.bmp.ovh/imgs/2019/12/87863ad4c4d6e93a.jpg', name: '翁美玲', gender: '女', age: this.year - 1959 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeetTe.jpg', name: '藍潔瑛', gender: '女', age: this.year - 1963 + 1 },
                { image: 'https://ftp.bmp.ovh/imgs/2019/12/655f90ac240f8d26.jpg', name: '周慧敏', gender: '女', age: this.year - 1967 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeeBlt.jpg', name: '關之琳', gender: '女', age: this.year - 1962 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeeRYj.jpg', name: '邱淑貞', gender: '女', age: this.year - 1968 + 1 },
                { image: 'https://ftp.bmp.ovh/imgs/2019/12/c889f7b09a6478ed.jpeg', name: '林青霞', gender: '女', age: this.year - 1954 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeeTmT.jpg', name: '李若彤', gender: '女', age: this.year - 1967 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeerOf.jpg', name: '王祖賢', gender: '女', age: this.year - 1967 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeeWfs.jpg', name: '張柏芝', gender: '女', age: this.year - 1980 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeecTg.jpg', name: '張曼玉', gender: '女', age: this.year - 1964 + 1 },
                { image: 'https://ftp.bmp.ovh/imgs/2019/12/0821807f09267a9b.jpg', name: '蔡少芬', gender: '女', age: this.year - 1973 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/Qee560.jpg', name: '溫碧霞', gender: '女', age: this.year - 1966 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeeUFH.jpg', name: '萬綺雯', gender: '女', age: this.year - 1970 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeeaYd.jpg', name: '鍾欣潼', gender: '女', age: this.year - 1981 + 1 },
                { image: 'https://ftp.bmp.ovh/imgs/2019/12/32c501866339e345.png', name: '周秀娜', gender: '女', age: this.year - 1985 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/Qee4lq.jpg', name: '楊紫', gender: '女', age: this.year - 1992 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/Qee70U.png', name: '鄭爽', gender: '女', age: this.year - 1991 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/Qee2kQ.jpg', name: '唐嫣', gender: '女', age: this.year - 1983 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeeH7F.jpg', name: '佟麗婭', gender: '女', age: this.year - 1983 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeeIXV.jpg', name: '舒暢', gender: '女', age: this.year - 1987 + 1 },
                { image: 'https://ftp.bmp.ovh/imgs/2019/12/22c88e047a214210.jpg', name: '楊冪', gender: '女', age: this.year - 1986 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/Qeehpn.jpg', name: '劉亦菲', gender: '女', age: this.year - 1987 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/Qelv5T.jpg', name: '韓雪', gender: '女', age: this.year - 1983 + 1 },
                { image: 'https://s2.ax1x.com/2019/12/01/QeedfA.jpg', name: '宋祖兒', gender: '女', age: this.year - 1998 + 1 }
            ]
        },

陣列角標

在遍歷的過程中,如果我們需要知道陣列角標,可以指定第二個引數:

語法

v-for="(item,index) in items"
  • items:要迭代的陣列
  • item:迭代得到的陣列元素別名
  • index:迭代到的當前元素索引,從0開始。
<div class="col-md-3" v-for="(user,index,key) in goddess" :key="index" style="text-align: center;">
    <p>
        <img v-bind:src="user.image" style="vertical-align:text-bottom;" height="200" />
    </p>
    <p>
        {{index+1}}
    <p>

遍歷物件

v-for除了可以迭代陣列,也可以迭代物件。語法基本類似

語法:

v-for="value in object"
v-for="(value,key) in object"
v-for="(value,key,index) in object"
  • 1個引數時,得到的是物件的屬性值
  • 2個引數時,第一個是屬性值,第二個是屬性名
  • 3個引數時,第三個是索引,從0開始

html:

<span v-for="(value,key,index) in date" :key="index">{{key+' '+value+' '}}</span>

js:

 date: {
     Year: 2020,
     month: 4,
     day: 14
 }

key

當 Vue.js 用 v-for 正在更新已渲染過的元素列表時,它預設用“就地複用”策略。如果資料項的順序被改變,Vue 將不會移動 DOM 元素來匹配資料項的順序, 而是簡單複用此處每個元素,並且確保它在特定索引下顯示已被渲染過的每個元素。

這個功能可以有效的提高渲染的效率。

但是要實現這個功能,你需要給Vue一些提示,以便它能跟蹤每個節點的身份,從而重用和重新排序現有元素,你需要為每項提供一個唯一 key 屬性。理想的 key 值是每項都有的且唯一的 id。

示例:

<div class="col-md-3" v-for="(user,index) in goddess" :key=index style="text-align: center;" >
  • 這裡使用了一個特殊語法::key="" 我們後面會講到,它可以讓你讀取vue中的屬性,並賦值給key屬性
  • 這裡我們繫結的key是陣列的索引,應該是唯一的

使用key和不使用key的對比

當某一層有很多相同的節點時,也就是列表節點時,我們希望插入一個新的節點
我們希望可以在B和C之間加一個F,Diff演算法預設執行起來是這樣的。
即把C更新成F,D更新成C,E更新成D,最後再插入E,是不是很沒有效率?

所以我們需要使用key來給每個節點做一個唯一標識
Diff演算法就可以正確的識別此節點
找到正確的位置區插入新的節點。

所以一句話,key的作用主要是為了高效的更新虛擬DOM

注意:key不要使用index,因為繫結的inedx是會改變的,沒什麼用。

v-if和v-show

v-if,顧名思義,條件判斷。當得到結果為true時,所在的元素才會被渲染。

語法:v-if="布林表示式"v-show="布林表示式"

html:

<span v-if="show">你看我了,你是true,我是if</span><br/>
<span v-show="show">你看到我了,你是true,我是show</span>

js:

data: { 
   show: true
}

show和if的區別: if當為false時,不會進行渲染。二show會進行渲染,僅僅是設定不顯示罷了

v-else和v-else-if

你可以使用 v-else 指令來表示 v-if 的“else 塊”:

<input type="button" value="點我生成隨機數" @click="random=Math.random()*100" />{{random}}<br/>
<p v-if="random>60">大媽</p>
<p v-else-if="random>30">阿姨</p>
<p v-else-if="random>15">姐姐</p>
<p v-else-if="random>0" style="font-size: 40px;color: red">我們來鍊銅吧</p>
<p v-else style="font-size: 20px;color: aqua">快快點選生成隨機數吧!</p>

v-else-if 必須緊跟在帶 v-if 或者 v-else-if 的元素之後。

v-else 元素也必須緊跟在帶 v-if 或者 v-else-if 的元素的後面,否則它將不會被識別。

錯誤演示

只會顯示random>60時的“大媽”

<input type="button" value="點我生成隨機數" @click="random=Math.random()*100" />{{random}}<br/>
<p v-if="random>60">大媽</p><br/>
<p v-else-if="random>30">阿姨</p><br/>
<p v-else-if="random>15">姐姐</p><br/>
<p v-else-if="random>0" style="font-size: 40px;color: red">我們來鍊銅吧</p><br/>
<p v-else style="font-size: 20px;color: aqua">快快點選生成隨機數吧!</p>

v-bind

html屬性不能使用雙大括號形式繫結,只能使用v-bind指令。

上面在遍歷顯示女神時,圖片的src屬性就用的是v-bind

<img v-bind:src="user.image" style="vertical-align:text-bottom;" height="200" />
<input type="button" v-bind:class="{active: store>0}" value="購買"/>
<input type="button" :class="store>0?'active':'noactive'" value="購買"/>

css:

.active{
    font-size: 30px;
    background-color: red;
}
.noactive{
    background-color: gray;
}

在將 v-bind 用於 classstyle 時,Vue.js 做了專門的增強。表示式結果的型別除了字串之外,還可以是物件或陣列。

繫結class樣式

陣列語法

我們可以藉助於v-bind指令來實現:

HTML:

<div id="app">
    <div v-bind:class="activeClass"></div>
    <div v-bind:class="errorClass"></div>
    <div v-bind:class="[activeClass, errorClass]"></div>
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script type="text/javascript">
    var app = new Vue({
        el: "#app",
        data: {
            activeClass: 'active',
            errorClass: ['text-danger', 'text-error']
        }
    })
</script>

渲染後的效果:

物件語法

我們可以傳給 v-bind:class 一個物件,以動態地切換 class:

<div v-bind:class="{ active: isActive }"></div>

上面的語法表示 active 這個 class 存在與否將取決於資料屬性 isActivetruthiness(所有的值都是真實的,除了false,0,“”,null,undefined和NaN)。

你可以在物件中傳入更多屬性來動態切換多個 class。此外,v-bind:class 指令也可以與普通的 class 屬性共存。如下模板:

<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>

和如下 data:

data: {
  isActive: true,
  hasError: false
}

結果渲染為:

<div class="static active"></div>

active樣式和text-danger樣式的存在與否,取決於isActive和hasError的值。本例中isActive為true,hasError為false,所以active樣式存在,text-danger不存在。

繫結style樣式

陣列語法

陣列語法可以將多個樣式物件應用到同一個元素上:

<div v-bind:style="[baseStyles, overridingStyles]"></div>

資料:

data: {
    baseStyles: {'background-color': 'red'},
    overridingStyles: {border: '1px solid black'}
}

渲染後的結果:

<div style="background-color: red; border: 1px solid black;"></div>

物件語法

v-bind:style 的物件語法十分直觀——看著非常像 CSS,但其實是一個 JavaScript 物件。CSS 屬性名可以用駝峰式 (camelCase) 或短橫線分隔 (kebab-case,記得用單引號括起來) 來命名:

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px', backgroundColor: 'red' }"></div>

資料:

data: {
  activeColor: 'red',
  fontSize: 30
}

效果:

<div style="color: red; font-size: 30px;backgroundColor: red"></div>

簡寫

v-bind:屬性名可以簡寫為:屬性名

計算屬性

在插值表示式中使用js表示式是非常方便的,而且也經常被用到。

但是如果表示式的內容很長,就會顯得不夠優雅,而且後期維護起來也不方便,例如下面的場景,我們有一個日期的資料,但是是毫秒值:

data:{
    birthday:88888888888 // 毫秒值
}

我們在頁面渲染,希望得到yyyy-MM-dd的樣式:

<h1>您的生日是:{{
    new Date(birthday).getFullYear() + '-'+ new Date(birthday).getMonth()+ '-' + new Date(birthday).getDay()
    }}
</h1>

雖然能得到結果,但是非常麻煩。

Vue中提供了計算屬性,來替代複雜的表示式:

data:{
    birthday:88888888888 // 毫秒值
}
computed: {
    birth(){
        const date=new Date(this.birthday);
        return date.getFullYear()+"年"+(date.getMonth()+1)+"月"+date.getDay()+"日";
    }
}
<p>你的年齡:{{birth}}</p>

我們可以將同一函式定義為一個方法而不是一個計算屬性。兩種方式的最終結果確實是完全相同的。然而,不同的是計算屬性是基於它們的依賴進行快取的。計算屬性只有在它的相關依賴發生改變時才會重新求值。這就意味著只要birthday還沒有發生改變,多次訪問 birthday 計算屬性會立即返回之前的計算結果,而不必再次執行函式。

computed裡面定義的方法一定要有返回值

執行該方法後,會像v-model一樣,生成一個屬性 方法名:返回值。所以直接使用該屬性就可以了,而不必呼叫方法

watch

watch可以讓我們監控一個值的變化。從而做出相應的反應。

示例:

html:

<!--watch-->
<input type="text" v-model="message"/>

js:

data: {
    message: "",
},
watch: {
    message(newval,oldval){
        console.log(newval,oldval);
    }
}

效果

元件

在大型應用開發的時候,頁面可以劃分成很多部分。往往不同的頁面,也會有相同的部分。例如可能會有相同的頭部導航。

但是如果每個頁面都獨自開發,這無疑增加了我們開發的成本。所以我們會把頁面的不同部分拆分成獨立的元件,然後在不同頁面就可以共享這些元件,避免重複開發。

在vue裡,所有的vue例項都是元件

註冊元件的基本步驟

使用Vue.extend()方法建立元件構造器
呼叫Vue.component()方法註冊元件
在Vue例項的作用範圍內使用元件

1.Vue.extend():
呼叫Vue.extend()建立的是一個元件構造器。
通常在建立元件構造器時,傳入template代表我們自定義元件的模板。
該模板就是在使用到元件的地方,要顯示的HTML程式碼。
事實上,這種寫法在Vue2.x的文件中幾乎已經看不到了,它會直接使用下面我們會講到的語法糖,但是在很多資料還是會提到這種方式,而且這種方式是學習後面方式的基礎。

2.Vue.component():
呼叫Vue.component()是將剛才的元件構造器註冊為一個元件,並且給它起一個元件的標籤名稱。
所以需要傳遞兩個引數:1、註冊元件的標籤名 2、元件構造器

3.元件必須掛載在某個Vue例項下,否則它不會生效。

全域性元件

全域性註冊,就意味著即便以後你不再使用這個元件,它依然會隨著Vue的載入而載入。

我們通過Vue的component方法來定義一個全域性元件。

<script src="node_modules/vue/dist/vue.js"></script>
<script>
    Vue.component("counter",{
    template: "<button @click='add'>瞅我呀,你瞅了{{num}}幾次</button>",
    data(){
        return {
            num: 0
        }
    },
    methods: {
        add(){
            this.num++;
        }
    }
})

const app=new Vue({
    el: "#app",
    data: {

    }
});
</script>

使用定義好的全域性元件,標籤名就是定義的元件名

<div id="app">
    <counter></counter>
    <counter></counter>
</div>
  • 元件其實也是一個Vue例項,因此它在定義時也會接收:data、methods、生命週期函式等

  • 不同的是元件不會與頁面的元素繫結,否則就無法複用了,因此沒有el屬性。

  • 但是元件渲染需要html模板,所以增加了template屬性,值就是HTML模板

  • 全域性元件定義完畢,任何vue例項都可以直接在HTML中通過元件名稱來使用元件了。

  • data必須是一個函式,不再是一個物件,在該函式中返回資料物件。

  • 元件和app是父子關係,元件標籤的使用必須在app中

元件的複用

定義好的元件,可以任意複用多次:

<div id="app">
    <counter></counter>
    <counter></counter>
    <counter></counter>
</div>

你會發現每個元件互不干擾,都有自己的count值。怎麼實現的?

元件的data屬性必須是函式

當我們定義元件時,它的data 並不是像之前直接提供一個物件:

data: {
    count: 0
}

取而代之的是,一個元件的 data 選項必須是一個函式,因此每個例項可以維護一份被返回物件的獨立的拷貝:

data: function () {
    return {
        count: 0
    }
}

如果 Vue 沒有這條規則,點選一個按鈕就會影響到其它所有例項!

區域性元件

一旦全域性註冊,就意味著即便以後你不再使用這個元件,它依然會隨著Vue的載入而載入。

因此,對於一些並不頻繁使用的元件,我們會採用區域性註冊。

我們先在外部定義一個物件,結構與建立全域性元件時傳遞的第二個引數一致

然後在Vue中使用它:

js:

const hello={
    template: "<div>hello,我是{{name}}</div>",
    data(){
        return {
            name: "張學友"
        }
    }
};

const app=new Vue({
    el: "#app",
    data: {

    },
    components: {
        hello1: hello  //hello1為到時候使用的標籤名,hello是自己定義的元件
    }
});

html:

<div id="app">
    <hello1></hello1>
</div>
  • components就是當前vue物件子元件集合。
    • 其key就是子元件名稱
    • 其值就是元件物件名
  • 效果與剛才的全域性註冊是類似的,不同的是,這個元件只能在當前的Vue例項中使用

模板的分離寫法

script

template

編譯作用域

插槽

元件的插槽:

  • 元件的插槽也是為了讓我們封裝的元件更加具有擴充套件性。

  • 讓使用者可以決定元件內部的一些內容到底展示什麼。

栗子:移動網站中的導航欄。

  • 移動開發中,幾乎每個頁面都有導航欄。
  • 導航欄我們必然會封裝成一個外掛,比如nav-bar元件。
  • 一旦有了這個元件,我們就可以在多個頁面中複用了。

如何去封裝這類的元件呢?

  • 它們也很多區別,但是也有很多共性。

  • 如果,我們每一個單獨去封裝一個元件,顯然不合適:比如每個頁面都返回,這部分內容我們就要重複去封裝。

  • 但是,如果我們封裝成一個,好像也不合理:有些左側是選單,有些是返回,有些中間是搜尋,有些是文字,等等。

如何封裝合適呢?抽取共性,保留不同。

  • 最好的封裝方式就是將共性抽取到元件中,將不同暴露為插槽。
  • 一旦我們預留了插槽,就可以讓使用者根據自己的需求,決定插槽中插入什麼內容。
  • 是搜尋框,還是文字,還是選單。由呼叫者自己來決定。

具名插槽

在子元件中,使用特殊的元素<slot>就可以為子元件開啟一個插槽。

<slot name='myslot'></slot>,在slot中加個name屬性就可以成為具名插槽

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <div id="app">
            <cpn><span slot="left">返回</span></cpn>
            <cpn><span slot="middle">標題</span></cpn>
        </div>
    </body>
    
    <template id="cpnC">
        <div>
            <slot name="left"><span>左邊</span></slot>
            <slot name="middle"><span>中間</span></slot>
            <slot name="right"><span>右邊</span></slot>
        </div>
    </template>
    
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>

        const cpn={
            template: "#cpnC"
        }
        const app=new Vue({
            el:"#app",
            components:{
                cpn:cpn
            }
        })
    </script>
</html>

作用域插槽

資料在子元件中

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <div id="app">
            <!--    預設的格式-->
            <cpn></cpn>
            <!--    以逗號分割-->
            <cpn>
                <template slot-scope="data">
                    <ul>
                        <li v-for="language in data" style="list-style: none">{{language.join(",")}} </li>
                    </ul>
                </template>
            </cpn>

        </div>

    </body>
    <template id="cpnC">
        <div>
            <slot :data="Clanguage">
                <ul>
                    <li v-for="language in Clanguage">{{language}}</li>
                </ul>
            </slot>
        </div>
    </template>

    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
        const cpn={
            template: '#cpnC',
            data(){
                return {
                    Clanguage: ['java','c#','python','php','c++','javascript']
                }
            }
        }


        const app=new Vue({
            el: "#app",
            components: {
                cpn,
            }
        })

    </script>

</html>

元件通訊

通常一個單頁應用會以一棵巢狀的元件樹的形式來組織:

  • 頁面首先分成了頂部導航、左側內容區、右側邊欄三部分
  • 左側內容區又分為上下兩個元件
  • 右側邊欄中又包含了3個子元件

各個元件之間以巢狀的關係組合在一起,那麼這個時候不可避免的會有元件間通訊的需求。

props(父向子通訊)

  1. 父元件使用子元件時,自定義屬性(屬性名任意,屬性值為要傳遞的資料)
  2. 子元件通過props接收父元件資料,通過自定義屬性的屬性名

例,修改開始的全域性元件

在使用元件的時候給元件傳遞num1引數

<div>
    <counter :num1="num"></counter>  
</div>

子元件中通過props來接收一個父元件傳遞的屬性

Vue.component("counter",{
    template: "<button @click='add'>瞅我呀,你瞅了{{num}}幾次</button>",
    props: ["num1","num2"],
    data(){
        return {
            num: 0
        }
    },
    methods: {
        add(){
            this.num++;
        }
    }
});

//父元件
const app=new Vue({
    el: "#app",
    data: {
        num: 12
    },
});

props驗證

我們定義一個區域性元件,並接收復雜資料:

//全域性元件
Vue.component("counter",{
    template: "<button @click='add'>瞅我呀,你瞅了{{num}}-{{title}}幾次</button>",
    //props: ["num1","num2"],
    props: {
        num1: {
            type: Number,
            default: 0,
            required: false
        },
        title: {
            type: Number,
        }
    },
    data(){
        return {
            num: 0
        }
    },
    methods: {
        add(){
            this.num++;
        }
    }
});
  • 這個子元件可以對 items 進行迭代,並輸出到頁面。
  • props:定義需要從父元件中接收的屬性
    • num1:是要接收的屬性名稱
      • type:限定父元件傳遞來的必須是數字
      • default:預設值
      • required:是否必須

當 prop 驗證失敗的時候,(開發環境構建版本的) Vue 將會產生一個控制檯的警告。

type 可以是下列原生建構函式中的一個:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

動態靜態傳遞

給 prop 傳入一個靜態的值:

 <counter  title="num"></counter>  

給 prop 傳入一個動態的值: (通過v-bind從資料模型中,獲取title的值)

 <counter  :title="num"></counter>  

靜態傳遞時,我們傳入的值都是字串型別的,但實際上任何型別的值都可以傳給一個 props。

 <counter :num1="num" title="num"></counter>  <!--動靜態繫結,靜態的預設都是字串,結果為:0-num-->

子向父通訊:$emit

vue提供了一個內建的this.$emit()函式,用來呼叫父元件繫結的函式,可以傳遞引數

this.$emit(‘函式名’,引數…)

來看這樣的一個案例:

我們可以通過v-on指令將父元件的函式繫結到子元件上:

html

<div>
    <counter :num1="num" title="num" @incre="incr()"></counter>  
</div>

js

//全域性元件
Vue.component("counter",{
    template: "<button @click='subIncr'>瞅我呀,你瞅了{{num1}}-{{title}}幾次</button>",
    //props: ["num1","num2"],
    props: {
        num1: {
            type: Number,
            default: 0,
            required: false
        },
        title: {
            type: Number,
        }
    },
    data(){
        return {
            num: 0
        }
    },
    methods: {
        add(){
            this.num++;
        },
        subIncr(){   //定義子元件方法
            this.$emit("incre");
        }
    }
});

//父元件
const app=new Vue({
    el: "#app",
    data: {
        num: 12
    },
    components: {
        hello1: hello  //hello1為到時候使用的標籤名,hello是自己定義的元件
    },
    methods: {
        incr(){  //定義父元件方法
            this.num++;
        }
    }
});

父子元件的訪問方式

$children

$ref

$parent

模組化

匿名函式方式

CommonJS

Export

import

webpack

什麼是webpack

At its core, webpack is a static module bundler for modern JavaScript applications.
從本質上來講,webpack是一個現代的JavaScript應用的靜態模組打包工具
但是它是什麼呢?用概念解釋概念,還是不清晰。
我們從兩個點來解釋上面這句話:模組打包

模組化

目前使用前端模組化的一些方案:AMD、CMD、CommonJS、ES6。

在ES6之前,我們要想進行模組化開發,就必須藉助於其他的工具,讓我們可以進行模組化開發。

並且在通過模組化開發完成了專案後,還需要處理模組間的各種依賴,並且將其進行整合打包。

而webpack其中一個核心就是讓我們可能進行模組化開發,並且會幫助我們處理模組間的依賴關係。

而且不僅僅是JavaScript檔案,我們的CSS、圖片、json檔案等等在webpack中都可以被當做模組來使用(在後續我們會看到)。

打包

理解了webpack可以幫助我們進行模組化,並且處理模組間的各種複雜關係後,打包的概念就非常好理解了。

就是將webpack中的各種資源模組進行打包合併成一個或多個包(Bundle)。

並且在打包的過程中,還可以對資源進行處理,比如壓縮圖片,將scss轉成css,將ES6語法轉成ES5語法,將TypeScript轉成JavaScript等等操作。

但是打包的操作似乎grunt/gulp也可以幫助我們完成,它們有什麼不同呢?

和grunt和gulp的對比

grunt/gulp的核心是Task

  • 我們可以配置一系列的task,並且定義task要處理的事務(例如ES6、ts轉化,圖片壓縮,scss轉成css)

  • 之後讓grunt/gulp來依次執行這些task,而且讓整個流程自動化。

  • 所以grunt/gulp也被稱為前端自動化任務管理工具。

我們來看一個gulp的task

  • 下面的task就是將src下面的所有js檔案轉成ES5的語法。

  • 並且最終輸出到dist資料夾中。

什麼時候用grunt/gulp呢?

  • 如果你的工程模組依賴非常簡單,甚至是沒有用到模組化的概念。

  • 只需要進行簡單的合併、壓縮,就使用grunt/gulp即可。

  • 但是如果整個專案使用了模組化管理,而且相互依賴非常強,我們就可以使用更加強大的webpack了。

所以,grunt/gulp和webpack有什麼不同呢?

  • grunt/gulp更加強調的是前端流程的自動化,模組化不是它的核心。
  • webpack更加強調模組化開發管理,而檔案壓縮合並、預處理等功能,是他附帶的功能。

安裝和使用

安裝webpack依賴(記得加上版本號)

npm install webpack@3.6.0 -g  #全域性安裝
npm install webpack@3.6.0 --save-dev  #本地安裝

為什麼全域性安裝後,還需要區域性安裝呢?

  • 在終端直接執行webpack命令,使用的全域性安裝的webpack
  • 當在package.json中定義了scripts時,其中包含了webpack命令,那麼使用的是區域性webpack

建立一個指令碼

建立一個webpack.config.js檔案

const path=require('path')

module.exports={
    entry: './src/main.js', //指定入口
    output:{
        path: path.resolve(__dirname,'dist'),   //動態獲取路徑
        filename: 'bundle.js'  //打包的檔名
    }
}

使用區域性的webpack進行打包

npm run build   #使用區域性的webpack進行打包

loader

使用步驟

  步驟一:通過npm安裝需要使用的loader

  步驟二:在webpack.config.js中的modules關鍵字下進行配置

style-loader和css-loader

引入依賴

npm install style-loader --save-dev
npm install css-loader --save-dev

配置

module: {
    rules: [
        {
            test: /\.css$/,  //正規表示式進行匹配
            //css-loacder只負責將css檔案進行載入
            //style-loader負責將樣式新增到DOM中
            //使用多個
            use: [ 'style-loader','css-loader' ]
        },
    ]
}

less和less-loader

引入less打包依賴(記得加上版本號)

npm install less@3.9.0 less-loader@5.0.0 --save-dev

配置

module: {
    rules: [
        {
            test: /\.css$/,
            //css-loacder只負責將css檔案進行載入
            //style-loader負責將樣式新增到DOM中
            //使用多個
            use: [ 'style-loader','css-loader' ]
        },
        {
            test: /\.less$/,
            use: [{
                loader: "style-loader" // creates style nodes from JS strings
            }, {
                loader: "css-loader" // translates CSS into CommonJS
            }, {
                loader: "less-loader" // compiles Less to CSS
            }]
        }
    ]

url-loader和file-loader

引入依賴

npm install --save-dev url-loader  

進行配置

{
    test: /\.(png|jpg|gif)$/,
        use: [
            {
                loader: 'url-loader',
                options: {
                    //當載入的圖片,小於limit時,會將圖片編譯成base64字串形式,不會形成一個新的檔案
                    //當大於限制,要使用file-loader,會形成一個新檔案
                    limit: 8192,
                    name: "img/[name].[hash:8].[ext]"  //指定生成的資料夾和命名
                }
            }
        ]
}

字串形式請求的url

當圖片的大小大於限制的時候,就不是編譯成Base64字串的形式放到打包js檔案中了,而是重新生成一個檔案。此時要引入file-loader依賴

引入file-loader依賴

npm install --save-dev file-loader  #引入file-loader依賴

問題一:路徑不對

圖片形成一個檔案後放到dist資料夾中,但是請求的路徑不包含dist

解決:指定一個字首

問題二:圖片的命名

形成一個圖片後,預設是放在dist資料夾下,而且是用32位的hash值進行儲存的,太亂了

解決:指定命名規則

檔案格式請求的url

ES6語法處理

引入依賴

npm install --save-dev babel-loader@7 babel-core babel-preset-es2015

配置

{
    test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
            use: {
                loader: 'babel-loader',
                    options: {
                        presets: ['es2015']
                    }
            }
}

引入Vue的打包

在js中使用vue

//使用vue進行開發
import Vue from "vue"

const app=new Vue({
    el: "#app",
    data: {
        message: "Hello world"
    }
})

打包後頁面報錯

這個錯誤說的是我們使用的是runtime-only版本的Vue,什麼意思呢?

這裡我只說解決方案:Vue不同版本構建,後續我具體講解runtime-only和runtime-compiler的區別。

resolve: {
    alias: {
        'vue$': 'vue/dist/vue.esm.js'
    }
}

runtime-only和runtime-compiler的區別

如果在之後的開發中,你依然使用template,就需要選擇Runtime-Compiler(左圖)

如果你之後的開發中,使用的是.vue資料夾開發,那麼可以選擇Runtime-only(有右圖)

我們需要先理解Vue應用程式是如何執行起來的。

Vue中的模板如何最終渲染成真實DOM。

我們來看下面的一幅圖。

使用Runtime-only直接省掉了前兩步,效能更高,程式碼更少

render的使用

Vue元件化開發引入

引入依賴

npm install vue-loader vue-template-compiler --save-dev

配置

一定要記得配置外掛

const path=require('path');
const { VueLoaderPlugin } = require('vue-loader')

module.exports={
    entry: './src/main.js',
    output:{
        path: path.resolve(__dirname,'dist'),   //動態獲取路徑
        filename: 'bundle.js',
        publicPath: 'dist/'
    },
    plugins: [
        // make sure to include the plugin for the magic
        new VueLoaderPlugin(),
    ],
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            }
        ]
    },
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.esm.js'
        }
    }
}

記得加上.vue字尾

Plugin

loader主要用於轉換某些型別的模組,它是一個轉換器。

plugin是外掛,它是對webpack本身的擴充套件,是一個擴充套件器。

新增版權的Plugin

效果

打包Html的Plugin

引入依賴

npm install html-webpack-plugin --save-dev

配置

此時可以將之前用於改正打包的圖片檔案的路徑寫的publicPath進行刪除了,因為此刻html也已經打包了。

此時也不需要引入任何的script檔案,因為打包後會自動引入

js壓縮的Plugin

引入依賴

npm install uglifyjs-webpack-plugin@1.1.1 --save-dev

配置外掛

效果

搭建本地伺服器

引入依賴

npm install --save-dev webpack-dev-server@2.9.1

配置檔案

devserver也是作為webpack中的一個選項,選項本身可以設定如下屬性:

  • contentBase:為哪一個資料夾提供本地服務,預設是根資料夾,我們這裡要填寫./dist

  • port:埠號

  • inline:頁面實時重新整理

  • historyApiFallback:在SPA頁面中,依賴HTML5的history模式

配置指令碼

open:是指執行後立馬開啟網頁

配置檔案的抽離

問題:

  • 我們的devServer只在開發時使用,而在部署時不需要這個配置

  • 而我們的js壓縮只在專案部署時使用,而在開發時不需要使用

下載依賴

下載webpack的配置檔案合併依賴

npm install webpack-merge

進行分離

建立build資料夾,裡面放三個配置檔案

  • base.config.js 基礎配置檔案

記得修改匯出的檔名,不是dist,而是../dist

const path=require('path');  //通過這個引數可以獲取該專案的路徑
const { VueLoaderPlugin } = require('vue-loader');  //可以使用VueLoaderPlugin
const webpack=require("webpack");  //可以使用版權外掛
const HtmlWebpackPlugin=require("html-webpack-plugin");  //html打包外掛
const UglifyjsWebpackPlugin=require("uglifyjs-webpack-plugin");  //壓縮js的外掛

module.exports={
    entry: './src/main.js',
    output:{
        path: path.resolve(__dirname,'../dist'),   //動態獲取路徑
        filename: 'bundle.js',
        // publicPath: 'dist/'
    },
    plugins: [
        // make sure to include the plugin for the magic
        new VueLoaderPlugin(),  //vue-template-compiler的外掛,必須要有
        new webpack.BannerPlugin("該版權歸codekiller所有"),  //版權的外掛
        new HtmlWebpackPlugin({
            template: 'index.html'   //以index.html為模板進行打包
        }),  //html打包外掛
        // new UglifyjsWebpackPlugin()  //壓縮js的外掛,開發階段不要使用
    ],
    module: {
        rules: [
            {
                test: /\.css$/,
                //css-loacder只負責將css檔案進行載入
                //style-loader負責將樣式新增到DOM中
                //使用多個
                use: [ 'style-loader','css-loader' ]
            },
            {
                test: /\.less$/,
                use: [{
                    loader: "style-loader" // creates style nodes from JS strings
                }, {
                    loader: "css-loader" // translates CSS into CommonJS
                }, {
                    loader: "less-loader" // compiles Less to CSS
                }]
            },
            {
                test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            //當載入的圖片,小於limit時,會將圖片編譯成base64字串形式,不會形成一個新的檔案
                            //當大於限制,要使用file-loader,會形成一個新檔案
                            limit: 8192,
                            name: "img/[name].[hash:8].[ext]"
                        }
                    }
                ]
            },
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['es2015']
                    }
                }
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            }
        ]
    },
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.esm.js'
        }
    },
    //開發階段使用,打包就不需要了
    // devServer: {
    //     contentBase: "./dist",  //內容的目錄
    //     inline: true,   //實時監聽
    //     // port: 8080  //預設在8080埠
    //     // historyApiFallback:  //在SPA頁面中,依賴HTML5的history模式
    // }
}
  • dev.config.js
const webpackMerge=require("webpack-merge");
const baseConfig=require("./base,config");

//進行合併
module.exports=webpackMerge(baseConfig,{
    //開發階段使用,打包就不需要了
    devServer: {
        contentBase: "./dist",  //內容的目錄
        inline: true,   //實時監聽
        // port: 8080  //預設在8080埠
        // historyApiFallback:  //在SPA頁面中,依賴HTML5的history模式
    }
})

  • prod.config.js
const UglifyjsWebpackPlugin=require("uglifyjs-webpack-plugin");  //壓縮js的外掛
const webpackMerge=require("webpack-merge");
const baseConfig=require("./base,config");


//進行合併
module.exports=webpackMerge(baseConfig,{
    plugins: [
        new UglifyjsWebpackPlugin()  //壓縮js的外掛,開發階段不要使用
    ]
})

修改配置檔案

vue-cli的使用

使用前提:

  • 安裝node環境
  • 安裝webpack
npm install webpack -g
  • 安裝腳手架

    npm install -g @vue/cli
    
  • 初始化專案

    vue init webpack my-project  #vue cli2
    vue create my-project #vue cli3/4
    

cli2詳解

vue init webpack my-project  #vue cli2

選擇配置

目錄結構

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-NvCC00Im-1595218176399)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20200512162306269.png)]

cli3詳解

vue create my-project #vue cli3/4

選擇配置

刪除預處理(preset)

刪掉就可以了

別名問題

https://blog.csdn.net/qq_44766883/article/details/106108615

路由和Vue-router

場景模擬

現在我們來實現這樣一個功能:

一個頁面,包含登入和註冊,點選不同按鈕,實現登入和註冊頁切換:

填寫父元件

為了讓接下來的功能比較清晰,我們先新建一個資料夾:src

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <div id="app">
            <span>登入</span>
            <span>註冊</span>
            <hr/>
            <login-frame></login-frame>
            <register-frame></register-frame>
        </div>
    </body>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script src="js/login.js"></script>
    <script src="js/register.js"></script>
    <script>
        const app=new Vue({
            el: "#app",
            components: {
                loginFrame,    //如果元件名和標籤名一樣,那麼可以省略
                registerFrame
            }
        });
    </script>
</html>

HTML 中的特性名是大小寫不敏感的,所以瀏覽器會把所有大寫字元解釋為小寫字元。這意味著當你使用 DOM 中的模板時,camelCase (駝峰命名法) 的命名需要使用其等價的 kebab-case (短橫線分隔命名) 命名

即本例中的loginFrame: login-frame

編寫登入及註冊元件

接下來我們來實現登入元件,以前我們都是寫在一個檔案中,但是為了複用性,開發中都會把元件放入獨立的JS檔案中,我們在src下新建一個user目錄以及在下面建立login.js及register.js:

編寫元件,這裡我們只寫模板,不寫功能。

login.js內容如下:

const loginFrame={  //元件template中只能有一個根標籤
    template:`   
      <div>
            <h1>登入頁</h1>
            使用者名稱:<input type="text"/><br/>
            密&emsp;碼:<input type="password"/><br/>
            <input type="button" value="登入"/>
      </div>  
    `,
}

register.js內容:

const registerFrame={
    template:`   //元件內只能有一個根標籤
      <div>
            <h1>註冊頁</h1>
            用&ensp;戶&ensp;名:<input type="text"/><br/>
            密&emsp;&emsp;碼:<input type="password"/><br/>
            確認密碼:<input type="password"/><br/>
            <input type="button" value="註冊"/>
      </div>  
    `,
}

&ensp和&emsp

&ensp;

它叫“半形空格”,全稱是En Space,en是字型排印學的計量單位,為em寬度的一半。根據定義,它等同於字型度的一半(如16px字型中就是8px)。名義上是小寫字母n的寬度。此空格傳承空格家族一貫的特性:透明的,此空格有個相當穩健的特性,就是其佔據的寬度正好是1/2箇中文寬度,而且基本上不受字型影響。

&emsp;

它叫“全形空格”,全稱是Em Space,em是字型排印學的計量單位,相當於當前指定的點數。例如,1 em在16px的字型中就是16px。此空格也傳承空格家族一貫的特性:透明的,此空格也有個相當穩健的特性,就是其佔據的寬度正好是1箇中文寬度,而且基本上不受字型影響。

問題

我們期待的是,當點選登入或註冊按鈕,分別顯示登入頁或註冊頁,而不是一起顯示。

但是,如何才能動態載入元件,實現元件切換呢?

雖然使用原生的Html5和JS也能實現,但是官方推薦我們使用vue-router模組。

vue-router簡介和安裝

使用vue-router和vue可以非常方便的實現 複雜單頁應用的動態路由功能。

官網:https://router.vuejs.org/zh-cn/

使用npm安裝:npm install vue-router

在index.html中引入依賴:

<script src="../node_modules/vue/dist/vue.js"></script>
<script src="../node_modules/vue-router/dist/vue-router.js"></script>

快速入門

新建vue-router物件,並且指定路由規則:

const router=new VueRouter({
    routes: [
        {
            path: "/login",  //路由路徑,必須以/開頭
            component: loginFrame  // 元件名稱

        },
        {
            path: "/register",
            component: registerFrame
        }
    ]
});

const app=new Vue({
    el: "#app",
    components: {
        loginFrame,    //如果元件名和標籤名一樣,那麼可以省略一個
        registerFrame
    },
    router  //引入router
});
  • 建立VueRouter物件,並指定路由引數

  • routes:路由規則的陣列,可以指定多個物件,每個物件是一條路由規則,包含以下屬性:

    • path:路由的路徑
    • component:元件名稱

記得在父元件中引入router物件

<div id="app">
    <span><router-link to="/login">登入</router-link></span>
    <span><router-link to="/register">註冊</router-link></span>
    <hr/>
    <router-view></router-view>
</div>
  • 通過<router-view>來指定一個錨點,當路由的路徑匹配時,vue-router會自動把對應元件放到錨點位置進行渲染
  • 通過<router-link>指定一個跳轉連結,當點選時,會觸發vue-router的路由功能,路徑中的hash值會隨之改變

注意

  • 單頁應用中,頁面的切換並不是頁面的跳轉。僅僅是地址最後的hash值變化。

  • 事實上,我們總共就一個HTML:index.html

  • 講道理,有了路由之後,其實父元件裡面都不需要引入元件了,直接通過路由就可以了

腳手架中使用路由

前端路由階段

  • 後端分離階段:

隨著Ajax的出現, 有了前後端分離的開發模式.

後端只提供API來返回資料, 前端通過Ajax獲取資料, 並且可以通過JavaScript將資料渲染到頁面中.

這樣做最大的優點就是前後端責任的清晰, 後端專注於資料上, 前端專注於互動和視覺化上.

並且當移動端(iOS/Android)出現後, 後端不需要進行任何處理, 依然使用之前的一套API即可.

目前很多的網站依然採用這種模式開發.

  • 單頁面富應用階段:

其實SPA最主要的特點就是在前後端分離的基礎上加了一層前端路由.

也就是前端來維護一套路由規則.

不重新整理改變url的幾種方式

hash方式

URL的hash也就是錨點(#), 本質上是改變window.location的href屬性.

我們可以通過直接賦值location.hash來改變href, 但是頁面不發生重新整理

pushState

history介面是HTML5新增的, 它有五種模式改變URL而不重新整理頁面.

history.pushState()

replaceState

history.replaceState()

go

history.go()

補充說明

因為 history.back() 等價於 history.go(-1)

history.forward() 則等價於 history.go(1)

這三個介面等同於瀏覽器介面的前進後退。

使用History模式

我們前面說過改變路徑的方式有兩種:

  • URL的hash
  • HTML5的history

預設情況下, 路徑的改變使用的URL的hash.

如果希望使用HTML5的history模式, 非常簡單, 進行如下配置即可

router-link

在前面的<router-link>中, 我們只是使用了一個屬性: to, 用於指定跳轉的路徑.

<router-link>還有一些其他屬性:

  • tag: tag可以指定<router-link>之後渲染成什麼元件, 比如上面的程式碼會被渲染成一個<li>元素, 而不是<a>

  • replace: replace不會留下history記錄, 所以指定replace的情況下, 後退鍵返回不能返回到上一個頁面中

  • active-class: 當<router-link>對應的路由匹配成功時, 會自動給當前元素設定一個router-link-active的class, 設定active-class可以修改預設的名稱.

在進行高亮顯示的導航選單或者底部tabbar時, 會使用到該類.

但是通常不會修改類的屬性, 會直接使用預設的router-link-active即可.

修改linkActiveClass

路由程式碼跳轉

有時候, 頁面的跳轉可能需要執行對應的JavaScript程式碼, 這個時候, 就可以使用第二種跳轉方式了

比如, 我們將程式碼修改如下:

動態路由

在某些情況下,一個頁面的path路徑可能是不確定的,比如我們進入使用者介面時,希望是如下的路徑:

/user/aaaa或/user/bbbb

除了有前面的/user之外,後面還跟上了使用者的ID

這種path和Component的匹配關係,我們稱之為動態路由(也是路由傳遞資料的一種方式)。

跳轉

編寫路由

跳轉所到介面。通過$route可以獲取後面的引數值

效果

路由的懶載入

官方給出瞭解釋:

  • 當打包構建應用時,Javascript 包會變得非常大,影響頁面載入。

  • 如果我們能把不同路由對應的元件分割成不同的程式碼塊,然後當路由被訪問的時候才載入對應元件,這樣就更加高效了

官方在說什麼呢?

  • 首先, 我們知道路由中通常會定義很多不同的頁面.

  • 這個頁面最後被打包在哪裡呢? 一般情況下, 是放在一個js檔案中.

  • 但是, 頁面這麼多放在一個js檔案中, 必然會造成這個頁面非常的大.

  • 如果我們一次性從伺服器請求下來這個頁面, 可能需要花費一定的時間, 甚至使用者的電腦上還出現了短暫空白的情況.

  • 如何避免這種情況呢? 使用路由懶載入就可以了.

路由懶載入做了什麼?

  • 路由懶載入的主要作用就是將路由對應的元件打包成一個個的js程式碼塊.

  • 只有在這個路由被訪問到的時候, 才載入對應的元件

方式一結合Vue的非同步元件和Webpack的程式碼分析.

結合Vue的非同步元件和Webpack的程式碼分析.

const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};

方式二AMD寫法

AMD寫法

const About = resolve => require(['../components/About.vue'], resolve);

方式三在ES6中, 我們可以有更加簡單的寫法來組織Vue非同步元件和Webpack的程式碼分割.

const Home = () => import('../components/Home.vue')

巢狀路由

巢狀路由是一個很常見的功能

比如在home頁面中, 我們希望通過/home/news和/home/message訪問一些內容.

一個路徑對映一個元件, 訪問這兩個路徑也會分別渲染兩個元件.

路徑和元件的關係如下:

定義兩個元件

編寫路由

路有點選

效果

引數的傳遞

params

配置路由格式: /router/:id

傳遞的方式: 在path後面跟上對應的值

傳遞後形成的路徑: /router/123, /router/abc

例子,跳轉

query

配置路由格式: /router, 也就是普通配置

傳遞的方式: 物件中使用query的key作為傳遞方式

傳遞後形成的路徑: /router?id=123, /router?id=abc

傳送請求

路由

跳轉的元件

效果

$router和​$route的區別

$route和$router是有區別的

$router為VueRouter例項,想要導航到不同URL,則使用$router.push方法

$route為當前router跳轉物件裡面可以獲取name、path、query、params等

其中params為params型別的請求引數

query為query型別的請求引數

vue的原型

一切元件都繼承自vue的原型

title問題

們來考慮一個需求: 在一個SPA應用中, 如何改變網頁的標題呢?

  • 網頁標題是通過<title>來顯示的, 但是SPA只有一個固定的HTML, 切換不同的頁面時, 標題並不會改變.

  • 但是我們可以通過JavaScript來修改<title>的內容.window.document.title = ‘新的標題’.

  • 那麼在Vue專案中, 在哪裡修改? 什麼時候修改比較合適呢?

普通的修改方式:

  • 我們比較容易想到的修改標題的位置是每一個路由對應的元件.vue檔案中.

  • 通過mounted宣告周期函式, 執行對應的程式碼進行修改即可.

  • 但是當頁面比較多時, 這種方式不容易維護(因為需要在多個頁面執行類似的程式碼).

有沒有更好的辦法呢? 使用導航守衛即可.

什麼是導航守衛?

  • vue-router提供的導航守衛主要用來監聽監聽路由的進入和離開的.

  • vue-router提供了beforeEachafterEach的鉤子函式, 它們會在路由即將改變前和改變後觸發.

使用前置守衛給每個路由在跳轉前加上title。

注意:因為有children,所以不要直接使用meta,而是使用matched[0].meta

導航守衛

全域性前置守衛

你可以使用 router.beforeEach 註冊一個全域性前置守衛:

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

當一個導航觸發時,全域性前置守衛按照建立順序呼叫。守衛是非同步解析執行,此時導航在所有守衛 resolve 完之前一直處於 等待中

每個守衛方法接收三個引數:

  • to: Route: 即將要進入的目標 路由物件
  • from: Route: 當前導航正要離開的路由
  • next: Function: 一定要呼叫該方法來 resolve 這個鉤子。執行效果依賴 next 方法的呼叫引數。
    • next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。
    • next(false): 中斷當前的導航。如果瀏覽器的 URL 改變了 (可能是使用者手動或者瀏覽器後退按鈕),那麼 URL 地址會重置到 from 路由對應的地址。
    • next('/') 或者 next({ path: '/' }): 跳轉到一個不同的地址。當前的導航被中斷,然後進行一個新的導航。你可以向 next 傳遞任意位置物件,且允許設定諸如 replace: truename: 'home' 之類的選項以及任何用在 router-linkto proprouter.push 中的選項。
    • next(error): (2.4.0+) 如果傳入 next 的引數是一個 Error 例項,則導航會被終止且該錯誤會被傳遞給 router.onError() 註冊過的回撥。

確保要呼叫 next 方法,否則鉤子就不會被 resolved

全域性後置鉤子

你也可以註冊全域性後置鉤子,然而和守衛不同的是,這些鉤子不會接受 next 函式也不會改變導航本身:

//後置鉤子(hook)
router.afterEach((to,from)=>{
  console.log('-----')
})

全域性解析守衛

在 2.5.0+ 你可以用 router.beforeResolve 註冊一個全域性守衛。這和 router.beforeEach 類似,區別是在導航被確認之前,同時在所有元件內守衛和非同步路由元件被解析之後,解析守衛就被呼叫。

路由獨享的守衛

  {
    path: '/about',
    component: ()=>import("../views/About"),
    meta: {
      title: '關於'
    },
    beforeEnter: (to,from,next)=>{
      console.log('beforeEnter')
      next()
    }

這些守衛與全域性前置守衛的方法引數是一樣的。

元件內的守衛

最後,你可以在路由元件內直接定義以下路由導航守衛:

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave
const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染該元件的對應路由被 confirm 前呼叫
    // 不!能!獲取元件例項 `this`
    // 因為當守衛執行前,元件例項還沒被建立
  },
  beforeRouteUpdate (to, from, next) {
    // 在當前路由改變,但是該元件被複用時呼叫
    // 舉例來說,對於一個帶有動態引數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
    // 由於會渲染同樣的 Foo 元件,因此元件例項會被複用。而這個鉤子就會在這個情況下被呼叫。
    // 可以訪問元件例項 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 導航離開該元件的對應路由時呼叫
    // 可以訪問元件例項 `this`
  }
}

beforeRouteEnter 守衛 不能 訪問 this,因為守衛在導航確認前被呼叫,因此即將登場的新元件還沒被建立。

不過,你可以通過傳一個回撥給 next來訪問元件例項。在導航被確認的時候執行回撥,並且把元件例項作為回撥方法的引數。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通過 `vm` 訪問元件例項
  })
}

注意 beforeRouteEnter 是支援給 next 傳遞迴調的唯一守衛。對於 beforeRouteUpdatebeforeRouteLeave 來說,this 已經可用了,所以不支援傳遞迴調,因為沒有必要了。

beforeRouteUpdate (to, from, next) {
  // just use `this`
  this.name = to.params.name
  next()
}

這個離開守衛通常用來禁止使用者在還未儲存修改前突然離開。該導航可以通過 next(false) 來取消。

beforeRouteLeave (to, from, next) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
    next()
  } else {
    next(false)
  }
}

導航解析流程

  1. 導航被觸發。
  2. 在失活的元件裡呼叫離開守衛。
  3. 呼叫全域性的 beforeEach 守衛。
  4. 在重用的元件裡呼叫 beforeRouteUpdate 守衛 (2.2+)。
  5. 在路由配置裡呼叫 beforeEnter
  6. 解析非同步路由元件。
  7. 在被啟用的元件裡呼叫 beforeRouteEnter
  8. 呼叫全域性的 beforeResolve 守衛 (2.5+)。
  9. 導航被確認。
  10. 呼叫全域性的 afterEach 鉤子。
  11. 觸發 DOM 更新。
  12. 用建立好的例項呼叫 beforeRouteEnter 守衛中傳給 next 的回撥函式。

keep-alive

keep-alive 是 Vue 內建的一個元件,可以使被包含的元件保留狀態,或避免重新渲染。

它們有兩個非常重要的屬性:

  • include - 字串或正則表達,只有匹配的元件會被快取

  • exclude- 字串或正規表示式,任何匹配的元件都不會被快取

    <keep-alive exclude="About,Home">
        <router-view/>
    </keep-alive>
    

router-view 也是一個元件,如果直接被包在 keep-alive 裡面,所有路徑匹配到的檢視元件都會被快取:

通過create宣告周期函式來驗證

不使用keep-alive,在兩個元件中跳轉,每次回到這個元件,就會重新建立一次,離開就銷燬一次

使用keep-alive,只建立一次

引發的問題

我如果想重新回到該元件,還是原來的路徑怎麼辦呢?因為我回到這個元件後,還是去到預設的元件

進行路徑記錄

path初始是預設路徑,在進入的時候跳轉到記錄的path,在離開的時候記錄當前的path

ps:鉤子函式一定要選擇正確

actived和deactived鉤子

使用看上方

這兩個函式只有使用了keep-alive才能使用

導航欄實踐

目錄結構

TabBar

TabBarItem

MainTabBar

App

index.js

Vuex

是什麼

官方解釋:Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。

  • 它採用 集中式儲存管理 應用的所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。

  • Vuex 也整合到 Vue 的官方除錯工具 devtools extension,提供了諸如零配置的 time-travel 除錯、狀態快照匯入匯出等高階除錯功能。

狀態管理到底是什麼?

  • 狀態管理模式、集中式儲存管理這些名詞聽起來就非常高大上,讓人捉摸不透。

  • 其實,你可以簡單的將其看成把需要多個元件共享的變數全部儲存在一個物件裡面。

  • 然後,將這個物件放在頂層的Vue例項中,讓其他元件可以使用。

  • 那麼,多個元件是不是就可以共享這個物件中的所有變數屬性了呢?

等等,如果是這樣的話,為什麼官方還要專門出一個外掛Vuex呢?難道我們不能自己封裝一個物件來管理嗎?

  • 當然可以,只是我們要先想想VueJS帶給我們最大的便利是什麼呢?沒錯,就是響應式。

  • 如果你自己封裝實現一個物件能不能保證它裡面所有的屬性做到響應式呢?當然也可以,只是自己封裝可能稍微麻煩一些。

  • 不用懷疑,Vuex就是為了提供這樣一個在多個元件間共享狀態的外掛,用它就可以了。

管理什麼狀態

但是,有什麼狀態時需要我們在多個元件間共享的呢?

  • 如果你做過大型開放,你一定遇到過多個狀態,在多個介面間的共享問題。

  • 比如使用者的登入狀態、使用者名稱稱、頭像、地理位置資訊等等。

  • 比如商品的收藏、購物車中的物品等等。

  • 這些狀態資訊,我們都可以放在統一的地方,對它進行儲存和管理,而且它們還是響應式的(待會兒我們就可以看到程式碼了,莫著急)。

OK,從理論上理解了狀態管理之後,讓我們從實際的程式碼再來看看狀態管理。

  • 畢竟,Talk is cheap, Show me the code.(來自Linus)

我們先來看看但介面的狀態管理吧.

單介面的狀態管理

我們知道,要在單個元件中進行狀態管理是一件非常簡單的事情

什麼意思呢?我們來看下面的圖片。

這圖片中的三種東西,怎麼理解呢?

  • State:不用多說,就是我們的狀態。(你姑且可以當做就是data中的屬性)

  • View:檢視層,可以針對State的變化,顯示不同的資訊。(這個好理解吧?)

  • Actions:這裡的Actions主要是使用者的各種操作:點選、輸入等等,會導致狀態的改變。

寫點程式碼,加深理解:

看下下方的程式碼效果, 肯定會實現吧?

在這個案例中,我們有木有狀態需要管理呢?沒錯,就是個數counter。

counter需要某種方式被記錄下來,也就是我們的State。

counter目前的值需要被顯示在介面中,也就是我們的View部分。

介面發生某些操作時(我們這裡是使用者的點選,也可以是使用者的input),需要去更新狀態,也就是我們的Actions

這不就是上面的流程圖了嗎?

多介面狀態管理

Vue已經幫我們做好了單個介面的狀態管理,但是如果是多個介面呢?

  • 多個試圖都依賴同一個狀態(一個狀態改了,多個介面需要進行更新)

  • 不同介面的Actions都想修改同一個狀態(Home.vue需要修改,Profile.vue也需要修改這個狀態)

也就是說對於某些狀態(狀態1/狀態2/狀態3)來說只屬於我們某一個試圖,但是也有一些狀態(狀態a/狀態b/狀態c)屬於多個

試圖共同想要維護的

  • 狀態1/狀態2/狀態3你放在自己的房間中,你自己管理自己用,沒問題。

  • 但是狀態a/狀態b/狀態c我們希望交給一個大管家來統一幫助我們管理!!!

  • 沒錯,Vuex就是為我們提供這個大管家的工具。

全域性單例模式(大管家)

  • 我們現在要做的就是將共享的狀態抽取出來,交給我們的大管家,統一進行管理。

  • 之後,你們每個試圖,按照我規定好的規定,進行訪問和修改等操作。

  • 這就是Vuex背後的基本思想。

Vuex狀態管理圖

一起在來看一副官方給出的圖片

當要修改state的狀態時,不建議直接修改,應該經過Mutations進行修改

如果有非同步請求,還要經過Actions

簡單案列

定義狀態和方法

使用狀態並進行修改

效果

getters

有時候,我們需要從store中獲取一些state變異後的狀態,比如下面的Store中:

獲取年齡大於22歲的

傳參

獲取大於指定年齡的

效果

Mutation

Mutation狀態更新

定義狀態和方法

使用狀態並進行修改

效果

傳參(兩種風格)

在通過mutation更新資料的時候, 有可能我們希望攜帶一些額外的引數

引數被稱為是mutation的載荷(Payload)

Mutation中的程式碼:

但是如果引數不是一個呢?

比如我們有很多引數需要傳遞.

這個時候, 我們通常會以物件的形式傳遞, 也就是payload是一個物件.

這個時候可以再從物件中取出相關的資訊.

響應式注意點

如果在物件中沒有定義並且初始化值的key普通操作是不能做到響應式的。

Action的非同步操作

我們強調, 不要再Mutation中進行非同步操作.

  • 但是某些情況, 我們確實希望在Vuex中進行一些非同步操作, 比如網路請求, 必然是非同步的. 這個時候怎麼處理呢?

  • Action類似於Mutation, 但是是用來代替Mutation進行非同步操作的.

Action的基本使用程式碼如下:

context是什麼?

  • context是和store物件具有相同方法和屬性的物件.

  • 也就是說, 我們可以通過context去進行commit相關的操作, 也可以獲取context.state等.

  • 但是注意, 這裡它們並不是同一個物件, 為什麼呢? 我們後面學習Modules的時候, 再具體說.

這樣的程式碼是否多此一舉呢?

  • 我們定義了actions, 然後又在actions中去進行commit, 這不是脫褲放屁嗎?

  • 事實上並不是這樣, 如果在Vuex中有非同步操作, 那麼我們就可以在actions中完成了.

使用promise

前面我們學習ES6語法的時候說過, Promise經常用於非同步操作.

在Action中, 我們可以將非同步操作放在一個Promise中, 並且在成功或者失敗後, 呼叫對應的resolve或reject.

OK, 我們來看下面的程式碼:

Modules

Module是模組的意思, 為什麼在Vuex中我們要使用模組呢?

  • Vue使用單一狀態樹,那麼也意味著很多狀態都會交給Vuex來管理.

  • 當應用變得非常複雜時,store物件就有可能變得相當臃腫.

  • 為了解決這個問題, Vuex允許我們將store分割成模組(Module), 而每個模組擁有自己的state、mutation、action、getters等

我們按照什麼樣的方式來組織模組呢?

  • 我們來看左邊的程式碼

案列:

注意:

  • 在呼叫模組中的狀態時,要加上模組的名稱$store.state.moduleA.name

  • 在使用mutations,actions,getter時,可以直接呼叫,他預設是從根模組中尋找方法,找不到再到module中找

命名不要一樣

  • 看下程式碼中每一種方法的引數

最終目錄結構

網路請求

選擇什麼網路模組

選擇一: 傳統的Ajax是基於XMLHttpRequest(XHR)

為什麼不用它呢?

  • 非常好解釋, 配置和呼叫方式等非常混亂.

  • 編碼起來看起來就非常蛋疼.

  • 所以真實開發中很少直接使用, 而是使用jQuery-Aj

選擇二: 在前面的學習中, 我們經常會使用jQuery-Ajax

  • 相對於傳統的Ajax非常好用.

為什麼不選擇它呢?

  • 首先, 我們先明確一點: 在Vue的整個開發中都是不需要使用jQuery了.
  • 那麼, 就意味著為了方便我們進行一個網路請求, 特意引用一個jQuery, 你覺得合理嗎?
  • jQuery的程式碼1w+行.
  • Vue的程式碼才1w+行.
  • 完全沒有必要為了用網路請求就引用這個重量級的框架

選擇三: 官方在Vue1.x的時候, 推出了Vue-resource.

  • Vue-resource的體積相對於jQuery小很多.
  • 另外Vue-resource是官方推出的.

為什麼不選擇它呢?

  • 在Vue2.0退出後, Vue作者就在GitHub的Issues中說明了去掉vue-resource, 並且以後也不會再更新.
  • 那麼意味著以後vue-reource不再支援新的版本時, 也不會再繼續更新和維護.
  • 對以後的專案開發和維護都存在很大的隱患.

選擇四: axios

在說明不再繼續更新和維護vue-resource的同時, 作者還推薦了一個框架: axios為什麼不用它呢?

  • axios有非常多的優點, 並且用起來也非常方便.
  • 稍後, 我們對他詳細學習.

功能特點:

  • 在瀏覽器中傳送 XMLHttpRequests 請求

  • 在 node.js 中傳送 http請求

  • 支援 Promise API

  • 攔截請求和響應

  • 轉換請求和響應資料

  • 等等

支援多種請求方式:

  • axios(config)
  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.head(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])

安裝axios

npm install axios --save

傳送請求

axios({
    url: 'http://123.207.32.32:8000/home/data',
    params: {
        type: 'pop',
        page: 1
    }
}).then(res=>{
    console.log(res);
})

傳送併發請求

有時候, 我們可能需求同時傳送兩個請求

  • 使用axios.all, 可以放入多個請求的陣列.

  • axios.all([]) 返回的結果是一個陣列,使用 axios.spread 可將陣列 [res1,res2] 展開為 res1, res2

axios.all([
    axios({
        url: 'http://123.207.32.32:8000/home/multidata'
    }),
    axios({
        url: 'http://123.207.32.32:8000/home/data',
        params: {
            type: 'pop',
            page: 1
        }
    })
]).then(result=>{
    console.log(result[0]);
    console.log(result[1]);
})

//使用axios.spread
]).then(axios.spread((res1,res2)=> {
    console.log(res1);
    console.log(res2);
}))

全域性配置

在上面的示例中, 我們的BaseURL是固定的

事實上, 在開發中可能很多引數都是固定的.

這個時候我們可以進行一些抽取, 也可以利用axiox的全域性配置

axios.defaults.baseURL='http://123.207.32.32:8000'
axios.defaults.timeout=5000   //單位是毫秒
axios.defaults.withCredentials=true

axios的例項

為什麼要建立axios的例項呢?

  • 當我們從axios模組中匯入物件時, 使用的例項是預設的例項.

  • 當給該例項設定一些預設配置時, 這些配置就被固定下來了.

  • 但是後續開發中, 某些配置可能會不太一樣.

  • 比如某些請求需要使用特定的baseURL或者timeout或者content-Type等.

  • 這個時候, 我們就可以建立新的例項, 並且傳入屬於該例項的配置資訊.

axios的封裝

有瑕疵的封裝

其實我們的instance本身就是一個Promise

export function http(config) {
    const instance=axios.create({
        baseUrl: 'http://123.207.32.32:8000',
        timeout: 5000
    })
    return instance(config)
}

使用

攔截器

axios提供了攔截器,用於我們在傳送每次請求或者得到相應後,進行對應的處理。

如何使用攔截器呢?

攔截可以做什麼

請求攔截

  • config中一些資訊不符合伺服器的要求

  • 開發一個loading元件,每次傳送網路請求時,都希望在介面中顯示一個請求的圖示

  • 某些網路請求(比如:登入(token),必須攜帶一些特殊的資訊)

請求攔截中錯誤攔截較少,通常都是配置相關的攔截

可能的錯誤比如請求超時,可以將頁面跳轉到一個錯誤頁面中。

響應攔截

響應的成功攔截中,主要是對資料進行過濾。

響應的失敗攔截中,可以根據status判斷報錯的錯誤碼,跳轉到不同的錯誤提示頁面。

官網

 ?官網

相關文章