vue文檔

一、課程介紹

https://vuejs.lipengzhou.com/

內(nèi)容

使用 Vue.js 系列技術(shù)棧進(jìn)行網(wǎng)站應(yīng)用開發(fā)

具體知識(shí)點(diǎn):

  • ECMAScript 6
  • Vue.js
  • Vue Router
  • Vuex
  • Ajax
  • Vue CLI
  • axios
  • webpack
  • Vue SSR
  • RESTful

前置知識(shí)

  • HTML
  • CSS
  • JavaScript
  • 基本的 ECMAScript 6 語法
  • 基本的 Ajax 使用
  • node命令行操作
  • npm命令
  • 基本的 Git 操作

二敌厘、項(xiàng)目實(shí)戰(zhàn)

賣座移動(dòng)端Vue

  • 使用vue-cli腳手架搭建項(xiàng)目

  • 導(dǎo)入有贊UI組件和使用

  • 字體圖標(biāo)使用

  • 搭建底部路由切換部分

  • vue路由切換總是默認(rèn)添加一個(gè)class router-link-exact-active

三汹桦、ES6常用的基礎(chǔ)知識(shí)點(diǎn)

ES6介紹

ECMAScript 6.0(以下簡稱 ES6)是 JavaScript 語言的下一代標(biāo)準(zhǔn),已經(jīng)在 2015 年 6 月正式發(fā)布了也叫ECMAScript 2015准验。它的目標(biāo)赎线,是使得 JavaScript 語言可以用來編寫復(fù)雜的大型應(yīng)用程序,成為企業(yè)級(jí)開發(fā)語言

let 和 const 命令

let

ES6 新增了let命令糊饱,用來聲明變量垂寥。它的用法類似于var,但是所聲明的變量另锋,只在let命令所在的代碼塊內(nèi)有效滞项。

{
  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 和let不存在變量提升

const

const聲明一個(gè)只讀的常量。一旦聲明砰蠢,常量的值就不能改變蓖扑。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

-------------------------------------------------
const foo = {};

// 為 foo 添加一個(gè)屬性,可以成功
foo.prop = 123;
foo.prop // 123

// 將 foo 指向另一個(gè)對(duì)象台舱,就會(huì)報(bào)錯(cuò)
foo = {}; // TypeError: "foo" is read-only

變量的解構(gòu)賦值

數(shù)組的解構(gòu)

let a = 1;
let b = 2;
let c = 3;

ES6 允許寫成下面這樣律杠。
let [a, b, c] = [1, 2, 3];

對(duì)象的解構(gòu)賦值

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}`;

對(duì)象的擴(kuò)展

屬性的簡潔表示法

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}

除了屬性簡寫,方法也可以簡寫竞惋。

const o = {
  method() {
    return "Hello!";
  }
};
// 等同于
const o = {
  method: function() {
    return "Hello!";
  }
};

------------------------------------------
let birth = '2000/01/01';
const Person = {
  name: '張三',
  //等同于birth: birth
  birth,
  // 等同于hello: function ()...
  hello() { console.log('我的名字是', this.name); }
};

函數(shù)的擴(kuò)展

函數(shù)的擴(kuò)展 ES6 允許使用“箭頭”(=>)定義函數(shù)柜去。

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};

如果箭頭函數(shù)的代碼塊部分多于一條語句,就要使用大括號(hào)將它們括起來拆宛,并且使用return語句返回嗓奢。

var sum = (num1, num2) => { return num1 + num2; }

箭頭函數(shù)有幾個(gè)使用注意點(diǎn)

  • 函數(shù)體內(nèi)的this對(duì)象,就是定義時(shí)所在的對(duì)象浑厚,而不是使用時(shí)所在的對(duì)象
  • 不可以當(dāng)作構(gòu)造函數(shù)股耽,也就是說根盒,不可以使用new命令,否則會(huì)拋出一個(gè)錯(cuò)誤
  • 不可以使用arguments對(duì)象物蝙,該對(duì)象在函數(shù)體內(nèi)不存在炎滞。如果要用,可以用 rest 參數(shù)代替诬乞。
  • 第一點(diǎn)尤其值得注意册赛。this對(duì)象的指向是可變的,但是在箭頭函數(shù)中震嫉,它是固定的森瘪。

Promise

Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大票堵。它由社區(qū)最早提出和實(shí)現(xiàn)扼睬,ES6 將其寫進(jìn)了語言標(biāo)準(zhǔn),統(tǒng)一了用法换衬,原生提供了Promise對(duì)象痰驱。

基本用法 ES6 規(guī)定,Promise對(duì)象是一個(gè)構(gòu)造函數(shù)瞳浦,用來生成Promise實(shí)例担映。下面代碼創(chuàng)造了一個(gè)Promise實(shí)例。

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

錯(cuò)誤捕獲

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

promise.all() promise.race()

async/ awite

ES2017 標(biāo)準(zhǔn)引入了 async 函數(shù)叫潦,使得異步操作變得更加方便蝇完。async 函數(shù)是什么?一句話矗蕊,它就是 Generator 函數(shù)的語法糖短蜕。

基本用法

async函數(shù)返回一個(gè) Promise 對(duì)象,可以使用then方法添加回調(diào)函數(shù)傻咖。當(dāng)函數(shù)執(zhí)行的時(shí)候朋魔,一旦遇到await就會(huì)先返回,等到異步操作完成卿操,再接著執(zhí)行函數(shù)體內(nèi)后面的語句警检。

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 對(duì)象

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

第 1 章 Vue.js 介紹

前端技術(shù)發(fā)展歷程

  • 前端開發(fā)的三個(gè)時(shí)代
    • 純?cè)?JavaScript
    • jQuery(僅僅是提高了 DOM 操作的效率)
    • 框架時(shí)代
  • 前端交互越來越多扇雕,功能需求越來越復(fù)雜;舊瀏覽器逐漸淘汰窥摄,移動(dòng)端需求增加
  • 架構(gòu)由傳統(tǒng)的后臺(tái) MVC 向 REST API + MV* 發(fā)展
  • 前后端分離開發(fā)方式
  • 各種前端框架誕生(vue,react,angular)

Vue是什么

以下引自官網(wǎng)原話:

Vue (讀音 /vju?/镶奉,類似于 view) 是一套用于構(gòu)建用戶界面的漸進(jìn)式框架。與其它大型框架不同的是,Vue 被設(shè)計(jì)為可以自底向上逐層應(yīng)用哨苛。Vue 的核心庫只關(guān)注視圖層鸽凶,不僅易于上手,還便于與第三方庫或既有項(xiàng)目整合移国。另一方面吱瘩,當(dāng)與現(xiàn)代化的工具鏈以及各種支持類庫結(jié)合使用時(shí),Vue 也完全能夠?yàn)閺?fù)雜的單頁應(yīng)用提供驅(qū)動(dòng)迹缀。

  • 一款非常優(yōu)秀的前端漸進(jìn)式 JavaScript 框架
    • Vue 本身只是一個(gè)用于數(shù)據(jù)驅(qū)動(dòng)視圖更新的庫
    • 隨著生態(tài)的慢慢發(fā)展,如今已經(jīng)有了 Vue Router蜜徽、Vuex祝懂、Vue CLI唯笙、Vue Server Renderer 等功能庫尼荆,所以當(dāng) Vue 和這些核心功能庫結(jié)合到一起的時(shí)候,我們稱之為一個(gè)框架
    • 這些技術(shù)一般也稱之為 Vue 全家桶膝迎,所以學(xué)習(xí) Vue 實(shí)際上就是要學(xué)習(xí) Vue 全家桶才能發(fā)揮出 Vue 的威力
  • 由尤雨溪開發(fā)并于 2014 年 2 月開源于 Github(14w+ ??)
  • 可以輕松構(gòu)建 SPA 應(yīng)用程序
  • 通過 指令 擴(kuò)展了 HTML盆色,通過 表達(dá)式 綁定數(shù)據(jù)到 HTML,通過組件化開發(fā)極大的提高了開發(fā)的效率和可維護(hù)性
  • 最大程度上解放了 DOM 操作

發(fā)展歷史

Github 發(fā)布記錄

  • Vue.js 由尤雨溪個(gè)人正式發(fā)布于 2014 年 2 月灰蛙,并開源于 Github
  • 2015 年 10 月 27 日,正式發(fā)布 1.0
  • 2016 年 8 月 1 日隔躲,正式發(fā)布 2.0
  • 截止到 2019-6 目前最新版本為 2.6.10
  • 預(yù)計(jì)今年(2019)會(huì)發(fā)布 3.0 版本
  • 目前已在 Github 收獲 14w+ Star

Vue 核心思想

Vue 是為了克服 HTML 在構(gòu)建應(yīng)用上的不足而設(shè)計(jì)的摩梧。Vue 有著諸多特性,最為核心的是:

  • 數(shù)據(jù)驅(qū)動(dòng)
    • DOM 是數(shù)據(jù)的一種自然映射
    • 數(shù)據(jù)改變自動(dòng)驅(qū)動(dòng)視圖更新
    • 組件化
    • 擴(kuò)展 HTML 元素宣旱,封裝可重用代碼
      組件化.png

相關(guān)鏈接

關(guān)于作者

尤雨溪.jpg

第 2 章 Vue 基礎(chǔ)

Vue.js 不支持 IE8 及其以下版本,因?yàn)?Vue 使用了 IE8 無法模擬的 ECMAScript 5 特性仅父。但它支持所有兼容 ECMAScript 5 的瀏覽器。最新穩(wěn)定版本:2.6.10

安裝

  • 直接下載
  • CDN
    • <script src="https://cdn.jsdelivr.net/npm/vue"></script> 最新穩(wěn)定版
    • <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script> 指定版本
  • 使用 npm 下載(最好創(chuàng)建一個(gè) package.json 文件浑吟,用來存儲(chǔ)第三方包依賴信息)
    • npm install vue 最新穩(wěn)定版
    • npm install vue@版本號(hào) 指定版本

創(chuàng)建了第一個(gè) Vue 應(yīng)用

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <h1>{{ message }}</h1>
  </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>
</body>
</html>

聲明式渲染

需求:下面的數(shù)據(jù)如何顯示到頁面中笙纤?

const data = {
  message: 'Hello Vue.js!',
  user: {
    name: '張三',
    age: 18,
    gender: 0
  }
}
------------------------------------
<h1></h1>
<p>姓名:</p>
<p>年齡:</p>
<p>性別:</p>

方式一:操作 DOM

方式二:模板引擎

方式三:使用 Vue

<body>
    <div id="app">
        <h1>{{message}}</h1>
        <p>姓名:{{user.name}}</p>
        <p>年齡:{{user.age}}</p>
        <p>性別:{{user.gender}}</p>
    </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!',
                user: {
                    name: '張三',
                    age: 18,
                    gender: 0
                }
            }
        })
    </script>
</body>

實(shí)例選項(xiàng) - el

  • 類型:string | Element:
  • 詳細(xì): 提供一個(gè)在頁面上已存在的 DOM 元素作為 Vue 實(shí)例的掛載目標(biāo)∽榱Γ可以是 CSS 選擇器省容,也可以是一個(gè) HTMLElement 實(shí)例。在實(shí)例掛載之后燎字,元素可以用 vm.$el 訪問

el 不能是 html腥椒、body 節(jié)點(diǎn)

實(shí)例選項(xiàng) - data

  • 類型:Object | Function
new Vue({
    el: '#app',
    data(){
        return {message:"demo"}
    }
})
---------------------------
new Vue({
    el: '#app',
    data:{message:"demo"}
})

組件的定義只接受 function,模板中訪問的數(shù)據(jù)必須初始化到 data 中

模板語法 - 插值{{}}

  • 文本

    <p>{{ message }}</p>
    <span>{{ message }}</span>
    <strong>{{ message }}</strong>
    
  • JavaScript 表達(dá)式

    <p>{{ number + 1 }}</p>
    <p>{{ number + 1 > 10 ? 'number大于10' : 'number小于10' }}</p>
    <p>{{ arr }}</p>
    <p>{{ message.split('').reverse().join('') }}</p>
    

模板語法 - 指令

指令 (Directives) 是帶有 v- 前綴的特殊特性。指令特性的值預(yù)期是單個(gè) JavaScript 表達(dá)式 (v-for 是例外情況轩触,稍后我們?cè)儆懻?

常用指令:

  • v-if:

    v-if 指令用于條件性地渲染一塊內(nèi)容寞酿。這塊內(nèi)容只會(huì)在指令的表達(dá)式返回真值的時(shí)候被渲染。
    <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-if="type === 'C'">
    C
    </div>
    <div v-else>
    Not A/B/C
    </div>
    
  • v-show

    <h1 v-show="ok">Hello!</h1>
    
  • v-text

    <div v-text="text"></div> // 帶標(biāo)簽的內(nèi)容不會(huì)被解析
    
  • v-html

    <div v-text="text"></div> // 帶標(biāo)簽的內(nèi)容會(huì)被解析
    
  • v-model(表單輸入綁定)

    <input v-model="test"/> //這個(gè)指令是一個(gè)語法糖脱柱,一般只用于input標(biāo)簽
    
  • v-for (循環(huán)指令伐弹,后面講)

  • v-bind (綁定指令,后面講)

  • v-on (監(jiān)聽指令)

一般來說,v-if 有更高的切換開銷惨好,而 v-show 有更高的初始渲染開銷煌茴。因此,如果需要非常頻繁地切換日川,則使用 v-show 較好蔓腐;如果在運(yùn)行時(shí)條件很少改變,則使用 v-if 較好

列表渲染

  • v-for="item in todos"
  • v-for="(item, index) in todos"

數(shù)組數(shù)據(jù)渲染

對(duì)象數(shù)據(jù)渲染

字符串?dāng)?shù)據(jù)渲染

綁定指令v-bind

綁定指令能綁定我們?cè)厣系囊恍傩裕?code>eg:<a v-bind:href="url">...</a> v-bind 指令將該元素的 href 特性與變量 url 的值綁定龄句。

綁定指令能綁定元素上的任何屬性回论,常用的比如 id, style, class, src, ... 也能綁定自定義屬性(父子傳參使用,到時(shí)候再講)

操作元素的 class 列表和內(nèi)聯(lián)樣式是數(shù)據(jù)綁定的一個(gè)常見需求,因?yàn)樗鼈兌际菍傩苑中晕覀兛梢杂?v-bind 處理它們,在將 v-bind 用于 class 和 style 時(shí)傀蓉,Vue.js 做了專門的增強(qiáng)。表達(dá)式結(jié)果的類型除了字符串之外职抡,還可以是對(duì)象或數(shù)組

Class 綁定

  • 一般使用

    // class的值就是變量className的值
    <div v-bind:class="className"></div> 
    <div v-bind:class="className?'a':'b'"></div> 
    
  • 對(duì)象語法

// 如果變量isActive 值為真則有active這個(gè)類葬燎,否則沒有
<div v-bind:class="{ active: isActive }"></div>
-----------------------------------------------
數(shù)據(jù):
data: {
  isActive: true
},
-----------------------------------------------
渲染為:
<div class="active"></div>
  • 數(shù)組語法(使用偏少)
<div v-bind:class="[activeClass, errorClass]"></div>
---------------------------------------------------
數(shù)據(jù):
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
-----------------------------------------------
渲染為:
<div class="active text-danger"></div>

Style 綁定

  • 對(duì)象語法
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
-----------------------------
數(shù)據(jù):
data: {
  activeColor: 'red',
  fontSize: 30
}
-----------------------------
直接綁定到一個(gè)樣式對(duì)象通常更好,這會(huì)讓模板更清晰:
<div v-bind:style="styleObject"></div>
data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}
  • 數(shù)組語法(很少用缚甩,不介紹)

縮寫

<!-- 完整語法 -->
<a v-bind:href="url">...</a>

<!-- 縮寫 -->
<a :href="url">...</a>

監(jiān)聽指令v-on

監(jiān)聽事件

可以用 v-on 指令監(jiān)聽 DOM 事件谱净,并在觸發(fā)時(shí)運(yùn)行一些 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
  }
})

事件處理方法(常用寫法)

許多事件處理邏輯會(huì)更為復(fù)雜擅威,所以直接把 JavaScript 代碼寫在 v-on 指令中是不可行的壕探。因此 v-on 還可以接收一個(gè)需要調(diào)用的方法名稱

<div id="example-2">
  <!-- `greet` 是在下面定義的方法名 -->
  <button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
  el: '#example-2',
  data: {
    name: 'Vue.js'
  },
  // 在 `methods` 對(duì)象中定義方法
  methods: {
    greet: function (event) {
      // `this` 在方法里指向當(dāng)前 Vue 實(shí)例
      alert('Hello ' + this.name + '!')
      // `event` 是原生 DOM 事件
      if (event) {
        alert(event.target.tagName)
      }
    }
  }
})

事件傳參

<div id="example-3">
  <button v-on:click="say('hi')">Say hi</button>
  <!-- DOM的原生事件,可以用特殊變量 $event 把它傳入方法 -->
  <button v-on:click="say('what',$event)">Say what</button>
</div>
new Vue({
  el: '#example-3',
  methods: {
    say: function (message,ev) {
      alert(message)
      console.log(ev)
    }
  }
})

v-on 縮寫

<!-- 完整語法 -->
<a v-on:click="doSomething">...</a>

<!-- 縮寫 -->
<a @click="doSomething">...</a>

練習(xí)題

以下是幾個(gè)小練習(xí)裕寨,用來輔助大家增強(qiáng)體會(huì) Vue 的數(shù)據(jù)驅(qū)動(dòng)視圖思想(MVVM):

1浩蓉、姓名展示:兩個(gè)文本框,讓用戶分別輸入性和名宾袜,然后將數(shù)據(jù)實(shí)時(shí)展示到界面上 2捻艳、數(shù)字自動(dòng)增長:一個(gè)文本框用來呈遞數(shù)字,一個(gè)按鈕庆猫,用戶點(diǎn)擊按鈕认轨,文本框中的數(shù)字+1 3、購物車計(jì)價(jià)器:有商品價(jià)格月培,和商品數(shù)量嘁字,用戶可以點(diǎn)擊加減按鈕改變商品數(shù)量,將價(jià)格實(shí)時(shí)展示到界面上 4杉畜、數(shù)字計(jì)算器:實(shí)現(xiàn)加法計(jì)算器 5纪蜒、數(shù)字計(jì)算器:實(shí)現(xiàn)加減乘除 以上所有練習(xí),都不要著急寫代碼此叠,不要用以前 DOM 的思想去操作纯续, 利用 Vue 的數(shù)據(jù)驅(qū)動(dòng)視圖的思想,去考慮問題:

練習(xí)todoList(簡單版本)

todolist.gif
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        ul li {
            list-style: none;
        }
        .hasDone{
            text-decoration: line-through;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>TODO</h1>
        <div>
            <ul>
                <li v-for="item in todoList">
                    <input type="checkbox" @click="change(item)">
                    <span :class="{hasDone: item.complete}">{{item.describe}}</span>
                </li>
            </ul>
        </div>
        <div>
            <input type="text" v-model="todoText" placeholder="add new todo here">
            <button @click="addTodo">Add</button>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
    <script>
        new Vue({
            el: "#app",
            data() {
                return {
                    todoList: [{
                            describe: "the one something todo",
                            complete: false
                        },
                        {
                            describe: "the two something todo",
                            complete: false
                        }
                    ],
                    todoText: ''
                }
            },
            methods: {
                addTodo() {
                    if (this.todoText) {
                        this.todoList.push({
                            describe: this.todoText,
                            complete: false
                        })
                    }
                },
                change(todo){
                    todo.complete = !todo.complete
                }
            },
        })
    </script>
</body>

計(jì)算屬性(computed)

  • 計(jì)算屬性是根據(jù)源數(shù)據(jù)衍生出來的新數(shù)據(jù),既然是衍生的數(shù)據(jù)猬错,那只要源數(shù)據(jù)在data中聲明過即可窗看,同時(shí),計(jì)算屬性的試圖更新必須依賴于data屬性的更新倦炒,此外衍生出來的新數(shù)據(jù)不能在data中有定義
  • 計(jì)算屬性的名稱就是計(jì)算屬性處理函數(shù)的名稱显沈,使用時(shí)可以在模版中綁定計(jì)算屬性,綁定方法與data中的普通屬性一致
  • 計(jì)算結(jié)果并返回逢唤,只有當(dāng)被計(jì)算的值發(fā)生改變時(shí)才會(huì)觸發(fā)
// 基礎(chǔ)用法
data() {
    return {
        str: 'string'
    }
}
computed: {
    beautifyStr() {
        return this.str.split('');
    }
}

監(jiān)聽器(watch)

對(duì)data屬性的監(jiān)聽拉讯,說明屬性是在data中聲明過的屬性更新時(shí)調(diào)用監(jiān)聽函數(shù),可選參數(shù)分別為新值和舊值鳖藕,對(duì)屬性重新設(shè)置值只要跟原來的值相等就不會(huì)觸發(fā)函數(shù)調(diào)用遂唧,這一點(diǎn)跟計(jì)算屬性是相似的,監(jiān)聽某一個(gè)值吊奢,當(dāng)被監(jiān)聽的值發(fā)生變化時(shí),執(zhí)行對(duì)應(yīng)的操作纹烹,與computed的區(qū)別是页滚,watch更加適用于監(jiān)聽某一個(gè)值的變化并做對(duì)應(yīng)的操作,比如請(qǐng)求后臺(tái)接口等铺呵,而computed適用于計(jì)算已有的值并返回結(jié)果

// 基礎(chǔ)用法
new Vue({
    el: '#id',
    data: {
        firstName: 'Leo',
        lastName: 'Alan',
        obj1: {
            a: 0
        }
    },
    watch: {
        // 監(jiān)聽firstName,當(dāng)firstName發(fā)生變化時(shí)就會(huì)執(zhí)行該函數(shù)
        firstName (newName, oldName) {
            // 執(zhí)行需要的操作...
            // 注:初始化不會(huì)執(zhí)行裹驰,只有當(dāng)被監(jiān)聽的值(firstName)發(fā)生變化時(shí)才會(huì)執(zhí)行
        },

        // 監(jiān)聽lastName
        lastName: {
            handler (newName, oldName) {
                // 執(zhí)行需要的操作...
            },
            immediate: true // true: 初始化時(shí)就會(huì)先執(zhí)行一遍該監(jiān)聽對(duì)應(yīng)的操作    
        },

        obj1: {
            handler () {
                // 執(zhí)行需要的操作...
            },
            deep: true // 該屬性默認(rèn)值為false. 
            // 當(dāng)被監(jiān)聽的值是對(duì)象,只有deep為true時(shí),對(duì)應(yīng)屬性的值(obj1.a)發(fā)生變化時(shí)才能觸發(fā)監(jiān)聽事件,但是這樣非常消耗性能
        },

        // 監(jiān)聽對(duì)象具體的屬性片挂, deep就不需要設(shè)置為true了
        'obj1.a': {
            handler () {
                // 執(zhí)行需要的操作...
            }
        }
    }
})

加強(qiáng)版todoList

計(jì)算未做完的事
todolistPlus.gif
watch: {
    todoList:{
        handler(){
            var count = 0
            this.todoList.forEach(item=>{
                    if(!item.complete){
                        count +=1
                    }
            })
            this.count = count
        },
        immediate:true,
        deep:true
    }
},

第三章案例介紹

案例基本會(huì)涵蓋到以下內(nèi)容知識(shí)點(diǎn):

  • 數(shù)據(jù)綁定
  • 計(jì)算屬性 computed
  • 偵聽器 watch
  • Class 與 Style 樣式處理
  • 條件渲染
  • 列表渲染
  • 事件處理
  • 表單輸入雙向數(shù)據(jù)綁定

案例介紹

項(xiàng)目初始化

下載模板

1幻林、mkdir todomvc-vue
2、cd todomvc-vue
3音念、git clone https://github.com/tastejs/todomvc-app-template.git
4沪饺、npm install
5、安裝完成闷愤,打開 index.html 預(yù)覽模板整葡。
6、導(dǎo)入vue的js文件
7讥脐、開始項(xiàng)目功能開發(fā)

添加功能

  • 回車健添加到任務(wù)列表中
  • 不允許有非空數(shù)據(jù)
  • 添加完清空輸入框內(nèi)的文本

任務(wù)項(xiàng)操作

  • 點(diǎn)擊復(fù)選框完成todo遭居,
  • 點(diǎn)擊刪除實(shí)現(xiàn)刪除功能
  • 點(diǎn)擊全選/取消 完成全選/取消功能
  • 監(jiān)控是否全選,是否非全選顯示全選是否選中

底部按鈕的操作和顯示

  • 顯示當(dāng)前多少項(xiàng)未完成
  • all/active/complete 三個(gè)面板切換
  • 清除已經(jīng)完成的項(xiàng)
  • 編輯旬渠、修改title

完整代碼

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Template ? TodoMVC</title>
        <link rel="stylesheet" href="node_modules/todomvc-common/base.css">
        <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
        <!-- CSS overrides - remove if you don't need it -->
        <link rel="stylesheet" href="css/app.css">
    </head>
    <body>
        <section class="todoapp">
            <header class="header">
                <h1>todos</h1>
                <input class="new-todo" placeholder="What needs to be done?" autofocus v-model="needTodoText" v-on:keyup.enter="addTodo">
            </header>
            <!-- This section should be hidden by default and shown when there are todos -->
            <section class="main">
                <input id="toggle-all" class="toggle-all" type="checkbox" :checked="checkedAll" @change="selectAll">
                <label for="toggle-all"  >Mark all as complete</label>
                <ul class="todo-list">
                    <!-- These are here just to show the structure of the list items -->
                    <!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
                    <li :class="{completed:item.isComplete, editing:item == editedTodo}" v-for="(item,index) in filterData">
                        <div class="view">
                            <input class="toggle" type="checkbox" :checked="item.isComplete" @change="change(item)">
                            <label @dblclick="edit(item)">{{item.title}}</label>
                            <button class="destroy" @click="deleteTodo(index)"></button>
                        </div>
                        <input class="edit" v-model="item.title" 
                            @blur="doneEdit(item)"
                            @keyup.enter="doneEdit(item)"
                            @keyup.esc="cancelEdit(item)"
                        >
                    </li>
                </ul>
            </section>
            <!-- This footer should hidden by default and shown when there are todos -->
            <footer class="footer">
                <!-- This should be `0 items left` by default -->
                <span class="todo-count"><strong>{{unComplete}}</strong> item left</span>
                <!-- Remove this if you don't implement routing -->
                <ul class="filters">
                    <li>
                        <a :class="{selected:visibility=='all'}" href="#/" @click.prevent="showPanel('all')">All</a>
                    </li>
                    <li>
                        <a :class="{selected:visibility=='active'}" href="#/active" @click.prevent="showPanel('active')">Active</a>
                    </li>
                    <li>
                        <a :class="{selected:visibility=='completed'}" href="#/completed" @click="showPanel('completed')">Completed</a>
                    </li>
                </ul>
                <!-- Hidden if no completed items are left ↓ -->
                <button class="clear-completed" @click="ClearCompleted">Clear completed</button>
            </footer>
        </section>
        <footer class="info">
            <p>Double-click to edit a todo</p>
            <!-- Remove the below line ↓ -->
            <p>Template by <a >Sindre Sorhus</a></p>
            <!-- Change this out with your name and url ↓ -->
            <p>Created by <a >you</a></p>
            <p>Part of <a >TodoMVC</a></p>
        </footer>
        <!-- Scripts here. Don't remove ↓ -->
        <script src="node_modules/todomvc-common/base.js"></script>
        <script src="js/app.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
        <script>
             new Vue({
                 el:".todoapp",
                 data(){
                     return {
                         todoList:[
                             {title:"xx",isComplete:false},
                             {title:"xx",isComplete:false}
                         ],
                         needTodoText:'',
                         visibility:"all", // 通過這個(gè)屬性值的變化對(duì)數(shù)據(jù)進(jìn)行篩選
                         checkedAll:false,  // 全選按鈕
                         editedTodo:null
                     }
                 },
                 methods: {
                     // 添加todoList
                    addTodo(){
                        if(this.needTodoText){
                            this.todoList.push({
                                    title:this.needTodoText,
                                    isComplete:false
                            });
                            this.needTodoText = "";
                        }
                    },
                    // 刪除
                    deleteTodo(index){
                        this.todoList.splice(index,1)
                    },
                    showPanel(s){
                        this.visibility = s
                    },
                    change(item){
                        item.isComplete = !item.isComplete
                    },
                    ClearCompleted(){
                        this.todoList = this.todoList.filter(item=>{
                            return !item.isComplete
                        })
                    },
                    selectAll(){
                        if(this.checkedAll){
                             this.todoList.forEach(item=>{
                                  item.isComplete = false
                             })
                        }else{
                            this.todoList.forEach(item=>{
                                item.isComplete = true
                            })
                        }
                        this.checkedAll = !this.checkedAll
                    },
                    edit(todo){
                         console.log(todo)
                         this.editedTodo = todo
                         this.beforeEditCache = todo.title
                    },
                    doneEdit(todo){
                        if (!this.editedTodo) {
                            return
                        }
                        this.editedTodo = null
                        todo.title = todo.title.trim()
                        if (!todo.title) {
                            this.deleteTodo(todo)
                        }
                    },
                    cancelEdit(todo){
                        this.editedTodo = null
                  todo.title = this.beforeEditCache
                    }
                 },
                 watch: {
                        todoList: {
                            handler: function (todos) {
                                 var countAll = todos.length;
                                 var countComplete = 0
                                 todos.forEach(item=>{
                                      if(item.isComplete){
                                            countComplete++
                                        }
                                 })
                                 if(countComplete === countAll){
                                      this.checkedAll = true
                                 }else{
                                     this.checkedAll = false
                                 }
                            },
                            deep: true
                        }
                 },    
                 computed: {
                    unComplete(){
                        return this.todoList.filter(item=>{
                                return !item.isComplete
                        }).length
                    },
                    filterData(){
                        //過濾的時(shí)候有三種情況 all completed unCompleted
                        var filter = {
                                all:function(list){
                                        return list;
                                },
                                completed:function(list){
                                        return list.filter(item=>{
                                                return item.isComplete;
                                        })
                                },
                                active:function(list){
                                        return list.filter(item=>{
                                                return !item.isComplete;
                                        })
                                }
                        }
                        //如果找到了過濾函數(shù)俱萍,就返回過濾后的數(shù)據(jù),如果沒有找到就返回所有的數(shù)據(jù)
                        return filter[this.visibility]?filter[this.visibility](this.todoList):this.todoList;
                    },
                },
             })
        </script>
    </body>
</html>

第四章Vue-Cli腳手架

Vue CLI 是 Vue 的腳手架工具告丢,它可以幫助我們快速生成 Vue 基礎(chǔ)項(xiàng)目代碼枪蘑,提供開箱即用的功能特性。

  • 基礎(chǔ)代碼目錄結(jié)構(gòu)
  • 開發(fā)服務(wù)
  • 本地調(diào)試
  • 代碼部署
  • 熱加載
  • 單元測(cè)試 ....

Vue CLI 致力于將 Vue 生態(tài)中的工具基礎(chǔ)標(biāo)準(zhǔn)化。它確保了各種構(gòu)建工具能夠基于智能的默認(rèn)配置即可平穩(wěn)銜接腥寇,這樣你可以專注在撰寫應(yīng)用上成翩,而不必花好幾天去糾結(jié)配置的問題。與此同時(shí)赦役,它也為每個(gè)工具提供了調(diào)整配置的靈活性

安裝

依賴要求:Vue CLI 需要 Node.js 8.9 或更高版本 (推薦 8.11.0+)麻敌。

npm install -g @vue/cli

使用 vue --version 確認(rèn)是否安裝成功。
cli-01.png

創(chuàng)建項(xiàng)目

運(yùn)行以下命令來創(chuàng)建一個(gè)新項(xiàng)目

vue create my-project
微信截圖_20190710144811.png

選擇默認(rèn)按enter即可

啟動(dòng)開發(fā)模式:npm run serve

開發(fā)期間不要關(guān)閉命令窗口掂摔,如果關(guān)了术羔,就重新 npm run serve

瀏覽器中打開http://localhost:8080/看到vue初始頁面,則說明初始化創(chuàng)建成功了

目錄結(jié)構(gòu)

cli-02.png
名稱 說明
node_modules 第三方包存儲(chǔ)目錄
public 靜態(tài)資源乙漓,已被托管
src 源代碼
.gitignore git 忽略文件级历,暫時(shí)不關(guān)心,我們還沒有在項(xiàng)目中使用 git
babel.config.js 先不關(guān)心
package.json 包說明文件
README.md 項(xiàng)目說明文件
package-lock.json 包的版本鎖定文件

src 目錄結(jié)構(gòu)

src.png
名稱 說明
assets 資源目錄叭披,存儲(chǔ)靜態(tài)資源寥殖,例如圖片等
components 存儲(chǔ)其它組件的目錄
App.vue 根組件
main.js 入口文件

程序的啟動(dòng)

  • 找到 main.js 入口
  • 加載 Vue
  • 加載 App 組件
  • 創(chuàng)建 Vue 實(shí)例
  • 將 App 組件替換到入口節(jié)點(diǎn)

.vue 單文件組件

template

  • 作用:組件的模板

  • 注意:只能有一個(gè)根節(jié)點(diǎn)(template 本身不算)

    <template>
    <div id="app">
     <img alt="Vue logo" src="./assets/logo.png">
    </div>
    </template>
    

script

  • 作用:組件的 JavaScript ,用來配置組件的選項(xiàng)(data涩蜘、methods嚼贡、watch。同诫。粤策。)
<script>
  // 1. 使用一個(gè)普通對(duì)象配置組件的選項(xiàng)
  const componentOptions = {
    data() {
      return {}
    },
    methods: {}
  }
  // 2. 將這個(gè)對(duì)象導(dǎo)出(組件的選項(xiàng)對(duì)象必須顯式的導(dǎo)出,否則不會(huì)生效)
  export default componentOptions
</script>

為了方便误窖,我們可以直接在定義的同時(shí)直接導(dǎo)出

<script>
  export default {
    data() {
      return {}
    },
    methods: {}
  }
</script>

style

當(dāng) <style> 標(biāo)簽有 scoped 屬性時(shí)叮盘,它的 CSS 只作用于當(dāng)前組件中的元素。

<style scoped>
  .example {
    color: red;
  }
</style>

<template>
  <div class="example">hi</div>
</template>

單文件組件的定義和使用

創(chuàng)建單文件組件

  • 推薦把通用組件創(chuàng)建到 components 目錄中
  • 把視圖組件定義到 views 目錄中
  • 單文件組件只是承載組件的容器而已霹俺,既不是全局也不是局部柔吼,如果要使用這個(gè)單文件組件,必須 注冊(cè)
    • 全局注冊(cè)使用吭服,可以在任何組件中使用
    • 局部注冊(cè)使用嚷堡,只能在注冊(cè)的組件中被使用

建立一個(gè)單文件的組件

<template>
  <div>
    foo 組件
  </div>
</template>

<script>
  export default {
    data() {},
    methods: {}
    // ...
  }
</script>

<style></style>

全局注冊(cè)使用

在 main.js 文件中

...
import Vue from 'vue'
import Com1 from './components/Com1.vue'

...

Vue.component('Com1', Com1)
// 接下來就可以在任何組件中使用 Com1 組件了

局部注冊(cè)使用

在某個(gè)組價(jià)中局部注冊(cè)使用

<template>
  <div>
    <!-- 使用 Com2 組件 -->
    <Com2></Com2>
  </div>
</template>
<script>
  import Com2 from './components/Com2'

  export default {
    components: {
      Com2
    }
  }
</script>

ECMAScript 6 Module

歷史上,JavaScript 一直沒有模塊(module)體系艇棕,無法將一個(gè)大程序拆分成互相依賴的小文件蝌戒,再用簡單的方法拼裝起來。其他語言都有這項(xiàng)功能沼琉,比如 Ruby 的require北苟、Python 的import,甚至就連 CSS 都有@import打瘪,但是 JavaScript 任何這方面的支持都沒有友鼻,這對(duì)開發(fā)大型的傻昙、復(fù)雜的項(xiàng)目形成了巨大障礙。

在 ES6 之前彩扔,社區(qū)制定了一些模塊加載方案妆档,最主要的有 CommonJS 和 AMD(require.js 庫,專門用于在瀏覽器中進(jìn)行模塊化開發(fā)虫碉,幾乎已經(jīng)淘汰了) 兩種贾惦。前者用于服務(wù)器(Node.js),后者用于瀏覽器敦捧。ES6 在語言標(biāo)準(zhǔn)的層面上须板,實(shí)現(xiàn)了模塊功能,而且實(shí)現(xiàn)得相當(dāng)簡單兢卵,完全可以取代 CommonJS 和 AMD 規(guī)范习瑰,成為瀏覽器和服務(wù)器通用的模塊解決方案。

學(xué)習(xí)準(zhǔn)備環(huán)境

目前無論是 Node.js 還是瀏覽器都還無法直接原生支持 ECMAScript 6 模塊 API(import秽荤、export)甜奄。

但是對(duì)于瀏覽器來說我們可以使用構(gòu)建工具將模塊代碼轉(zhuǎn)換為瀏覽器能識(shí)別的代碼。

Node.js 中可以開啟實(shí)驗(yàn)功能來支持該功能窃款。

為了學(xué)習(xí)方便贺嫂,我們這里使用 Node 環(huán)境來學(xué)習(xí) ES6 模塊規(guī)則。

首先將你的文件后綴名定義為 .mjs雁乡,然后執(zhí)行這個(gè)腳本文件的時(shí)候加上 --experimental-modules 選項(xiàng)。

例如我有一個(gè) main.mjs糜俗,則執(zhí)行命令是:node --experimental-modules main.js

這里只是用于測(cè)試學(xué)習(xí)踱稍,不要在生產(chǎn)環(huán)境中使用這個(gè)模塊規(guī)則。

foo.js

// export 也用于導(dǎo)出
// 但是可以多次使用
export const a = 1
export const b = 2
export const c = 3
export const d = 4

function add(x, y) {
  return x + y
}

// 等價(jià)于 module.exports = add
// export default 只能使用一次
export default add

// 模塊中有多個(gè)成員悠抹,一般都使用 export xxx
// 如果只有一個(gè)成員珠月,那就 export default

main.js

// 加載 export default 導(dǎo)出的成員
// import foo from './foo'
// console.log(foo)

// 按需加載 export 導(dǎo)出的成員
// 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 成員

// 為了方便,先加載默認(rèn) 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))

相關(guān)命令

# 啟動(dòng)開發(fā)服務(wù)
npm run serve
# 項(xiàng)目打包
npm run build
# 代碼檢查
npm run lint

第五章vue中和服務(wù)端通信

概述

Vue 不像 jQuery 內(nèi)置了 ajax 請(qǐng)求函數(shù)啤挎,在 Vue 中沒有提供這樣的功能。所以當(dāng)我們需要在 Vue 中和服務(wù)端進(jìn)行通信的時(shí)候可選擇的方式會(huì)更靈活一些卵凑。

注意:Vue 不提供的原因是為了讓 Vue 本身更專注于視圖部分庆聘,保持其漸進(jìn)靈活的特性

所以 Vue 給了我們更多的選擇空間,例如我們可以使用下面的可選方案:

  • 原生的 XMLHttpRequest
  • 原生的 Fetch
  • 也可以結(jié)合使用 jQuery 自帶的 Ajax 請(qǐng)求函數(shù)
  • 早期大家開發(fā) Vue 應(yīng)用喜歡使用一個(gè)第三方插件:Vue Resource
  • 目前主流的方案是使用社區(qū)中知名的第三方庫 axios

axios介紹

axios 是一個(gè)基于 Promise 的第三方 HTTP 客戶端請(qǐng)求庫勺卢,可以用于瀏覽器或者 Node.js伙判。 axios 本身和 Vue 沒有一毛錢關(guān)系,只是簡單純粹的封裝了 HTTP 請(qǐng)求功能黑忱⊙绺В可以運(yùn)行在任何支持 JavaScript 環(huán)境的平臺(tái)勒魔。

axios 依賴原生的 ECMAScript 6 Promise 支持。如果瀏覽器不支持 ECMAScript 6 Promise菇曲,可以使用 es6-promise 進(jìn)行兼容處理

起步

npm install axios

執(zhí)行一個(gè) GET 請(qǐng)求

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);
  }
}

執(zhí)行一個(gè) POST 請(qǐng)求

axios
  .post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function(response) {
    console.log(response)
  })
  .catch(function(error) {
    console.log(error)
  })

執(zhí)行多個(gè)并發(fā)請(qǐng)求

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 API

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'))
})

第六章組件基礎(chǔ)

  • 了解組件的概念
  • 掌握組件的定義
  • 了解組件的組織方式
  • 掌握組件的聲明周期
  • 掌握組件通信

組件 (Component) 是 Vue.js 最強(qiáng)大的功能之一冠绢。組件可以擴(kuò)展 HTML 元素,封裝可重用的代碼常潮。
組件化.png

.vue 單文件組件

單文件組件是最常用的編寫組件的方式弟胀,我們90%以上都是這種寫法,還有其他寫法很少用蕊玷,暫時(shí)不用掌握

組件注冊(cè)方式

  • 全局組件
    • 定義在全局邮利,在任意組件中都可以直接使用
  • 局部組件
    • 定義在組件內(nèi)部,只能在當(dāng)前組件使用

注冊(cè)和使用方式清跳轉(zhuǎn)到 第四章的Vue-cli的 單文件組件的定義和使用 這一章節(jié)

組件的生命周期

生命周期

組件的有幾個(gè)階段

  • 初始化階段
  • 運(yùn)行中階段
  • 銷毀階段

初始化階段

  • beforeCreate
表示組件創(chuàng)建前的準(zhǔn)備工作垃帅, 為事件的發(fā)布訂閱 和 生命周期的開始做初始化
這個(gè)鉤子函數(shù)中,數(shù)據(jù)拿不到延届, 真實(shí)DOM也拿不到,這個(gè)鉤子在項(xiàng)目中我們沒有什么實(shí)際用途
 beforeCreate () { //表示組件創(chuàng)建前的準(zhǔn)備工作( 初始化事件和生命周期 )
        console.log('beforeCreate') 
        console.log(this.msg) //undefind
        console.log(document.querySelector('p')) //null 沒有真實(shí)DOM
}
  • created
表示組件創(chuàng)建結(jié)束,這個(gè)鉤子函數(shù)中,數(shù)據(jù)拿到了, 但是真實(shí)DOM沒有拿到
這個(gè)鉤子函數(shù)在項(xiàng)目一般數(shù)據(jù)請(qǐng)求贸诚,然后可以進(jìn)行一次默認(rèn)數(shù)據(jù)的修改
created () { // 組件創(chuàng)建結(jié)束
        console.log('created')
        console.log(this.msg) //(vue.js) 有數(shù)據(jù)
        console.log(document.querySelector('p')) //null 沒有真實(shí)DOM
        axios({
            url: './data.json'
        })
        .then( res => {
                this.msg = res
        })
        .catch( error => {
                throw error
        })
}
  • beforeMounte
表示組件裝載前的準(zhǔn)備工作,判斷 el選項(xiàng)有沒有方庭, 判斷 template選項(xiàng)有沒有 , 如果沒有酱固, 那么需要手動(dòng)裝載,如果有械念,那么通過render函數(shù)進(jìn)行模板的渲染
這個(gè)鉤子函數(shù)中,數(shù)據(jù)拿到了, 真實(shí)DOM沒有拿到,這個(gè)鉤子函數(shù)在項(xiàng)目中运悲,數(shù)據(jù)請(qǐng)求龄减, 它也可以進(jìn)行一次數(shù)據(jù)修
beforeMount () {
        console.log('beforeMount')
        console.log(this.msg) //(vue.js) 有數(shù)據(jù)
        console.log(document.querySelector('p')) //null 沒有真實(shí)DOM
        // axios({
        //     url: './data.json'
        // })
        //   .then( res => {
        //       this.msg = res
        //   })
        //   .catch( error => {
        //       throw error
        //   })
  }
  • mounted
表示組件裝載結(jié)束, 就是我們可以在視圖中看到了,這個(gè)鉤子函數(shù)中,數(shù)據(jù)拿到了班眯, 真實(shí)DOM也拿到了,
這個(gè)鉤子函數(shù)在項(xiàng)目DOM操作就可以進(jìn)行了希停, 第三方庫的實(shí)例化
mounted () {
        console.log('mount')
        console.log(this.msg) //(vue.js) 有數(shù)據(jù)
        console.log(document.querySelector('p')) //有真實(shí)DOM
        axios({
                url: './data.json'
        })
        .then( res => {
                this.msg = res
        })
        .catch( error => {
                throw error
        })
}

總結(jié): 由上對(duì)比,我們可以知道署隘, 數(shù)據(jù)請(qǐng)求越提前越好一些宠能, created常用于數(shù)據(jù)的請(qǐng)求和數(shù)據(jù)的修改, 第三方庫的實(shí)例化常在mounted中進(jìn)行書寫

運(yùn)行中階段

  • beforeUpdate
表示數(shù)據(jù)更新前的準(zhǔn)備工作,這個(gè)鉤子不主動(dòng)執(zhí)行磁餐,當(dāng)數(shù)據(jù)修改了违崇, 才會(huì)執(zhí)行,這個(gè)鉤子函數(shù)中數(shù)據(jù)拿到了, 并且拿到的是修改后的數(shù)據(jù),DOM也輸出了诊霹。這個(gè)鉤子函數(shù)更多的工作內(nèi)容為:生成新的 VDOM , 然后通過diff算法進(jìn)行兩次VDOM 對(duì)比羞延。這個(gè)鉤子在項(xiàng)目中。因?yàn)樗饕龅氖虑槭莾?nèi)部進(jìn)行的脾还, 所以對(duì)我們而言沒有太多的操作意義
  • updated
表示數(shù)據(jù)更新結(jié)束肴楷, 通過render函數(shù)渲染真實(shí)DOM。這個(gè)鉤子函數(shù)的執(zhí)行也是荠呐, 當(dāng)數(shù)據(jù)修改的時(shí)候才執(zhí)行赛蔫。這個(gè)鉤子函數(shù)中數(shù)據(jù)拿到了砂客, DOM也拿到了。

總結(jié): 數(shù)據(jù)更新呵恢, 也要進(jìn)行DOM操作那么鞠值, 我們使用update這個(gè)鉤子

銷毀階段

  • beforeDestroy
  • destroyed
這兩個(gè)鉤子無差別,在項(xiàng)目中做善后工作 , 手動(dòng)清除一些計(jì)時(shí)器渗钉, 和一些方法彤恶, 還有第三方實(shí)例化出來的對(duì)象
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這個(gè)方法來清除組件, 那么我們必須手動(dòng)清除這個(gè)組件的外殼
        document.querySelector('#app div').remove()
    }
})
new Vue({
    el: '#app',
    data: {
        flag: true
    }
})

組件通信

組件就像零散的積木鳄橘,我們需要把這些積木按照一定的規(guī)則拼裝起來声离,而且要讓它們互相之間能進(jìn)行通訊,這樣才能構(gòu)成一個(gè)有機(jī)的完整系統(tǒng)瘫怜。

在真實(shí)的應(yīng)用中术徊,組件最終會(huì)構(gòu)成樹形結(jié)構(gòu),就像人類社會(huì)中的家族樹一樣

在樹形結(jié)構(gòu)里面鲸湃,組件之間有幾種典型的關(guān)系:父子關(guān)系赠涮、兄弟關(guān)系、沒有直接關(guān)系暗挑。

相應(yīng)地笋除,組件之間有以下幾種典型的通訊方案:

直接的父子關(guān)系

  • 父組件通過 this.$refs 訪問子組件
  • 子組件 this.$parent 訪問其父組件

直接父子關(guān)系

  • 父組件通過 Props 給子組件下發(fā)數(shù)據(jù)
  • 子組件通過事件方式給父組件發(fā)送消息

沒有直接關(guān)系

  • 簡單場景:借助于事件機(jī)制進(jìn)行通訊
  • 復(fù)雜場景:使用狀態(tài)管理容器(例如 Vue 生態(tài)中的 Vuex、React 生態(tài)中的 Redux炸裆、Mobx 等)

利用 sessionStorage 和 localStorage 進(jìn)行通訊

無論你使用什么前端框架垃它,組件之間的通訊都離開不以上幾種方案,這些方案與具體框架無關(guān)烹看。

父子組件通信

組件設(shè)計(jì)初衷就是要配合使用的嗤瞎,最常見的就是形成父子組件的關(guān)系:組件 A 在它的模板中使用了組件 B。它們之間必然需要相互通信:父組件可能要給子組件下發(fā)數(shù)據(jù)听系,子組件則可能要將它內(nèi)部發(fā)生的事情告知父組件。然而虹菲,通過一個(gè)良好定義的接口來盡可能將父子組件解耦也是很重要的靠胜。這保證了每個(gè)組件的代碼可以在相對(duì)隔離的環(huán)境中書寫和理解,從而提高了其可維護(hù)性和復(fù)用性毕源。 在 Vue 中浪漠,父子組件的關(guān)系可以總結(jié)為 prop 向下傳遞揽乱,事件向上傳遞群叶。父組件通過 prop 給子組件下發(fā)數(shù)據(jù)掰派,子組件通過事件給父組件發(fā)送消息慕匠∠Γ看看它們是怎么工作的腐缤。

父傳子(Props Down)

我們可以用 v-bind 來動(dòng)態(tài)地將 prop 綁定到父組件的數(shù)據(jù)债查。每當(dāng)父組件的數(shù)據(jù)變化時(shí)毁嗦,該變化也會(huì)傳導(dǎo)給子組件:

父組件
<div>
  <input v-model="parentMsg">
  <br>
  <child :myMessage="parentMsg"></child>
    <!-- <child v-bind:my-message="parentMsg"></child> -->
</div>

子組件通過prop屬性接受,prop中的變量名不能在data中定義

<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
  name: 'app',
    props:['myMessage']
  components: {
    HelloWorld
  }
}
</script>

單向數(shù)據(jù)流

Prop 是單向綁定的:當(dāng)父組件的屬性變化時(shí),將傳導(dǎo)給子組件娘纷,但是反過來不會(huì)嫁审。這是為了防止子組件無意間修改了父組件的狀態(tài),來避免應(yīng)用的數(shù)據(jù)流變得難以理解赖晶。

另外律适,每次父組件更新時(shí),子組件的所有 prop 都會(huì)更新為最新值遏插。這意味著你不應(yīng)該在子組件內(nèi)部改變 prop捂贿。如果你這么做了,Vue 會(huì)在控制臺(tái)給出警告胳嘲。

另外厂僧,每次父組件更新時(shí),子組件的所有 prop 都會(huì)更新為最新值胎围。這意味著你不應(yīng)該在子組件內(nèi)部改變 prop吁系。如果你這么做了,Vue 會(huì)在控制臺(tái)給出警告白魂。

  • 1汽纤、Prop 作為初始值傳入后,子組件想把它當(dāng)作局部數(shù)據(jù)來用
  • 2福荸、Prop 作為原始數(shù)據(jù)傳入蕴坪,由子組件處理成其它數(shù)據(jù)輸出

對(duì)這兩種情況,正確的應(yīng)對(duì)方式是:

  1. 定義一個(gè)局部變量敬锐,并用 prop 的值初始化它:

    props: ['initialCounter'],
    data: function () {
    // var a = 1
    // var b = a
    // a = 123
    // b ?
    // b 456
    // a ?
    return { counter: this.initialCounter }
    }
    

    2.定義一個(gè)計(jì)算屬性背传,處理 prop 的值并返回:

    // ...
    props: ['size'],
    computed: {
    normalizedSize: function () {
     return this.size.trim().toLowerCase()
    }
    },
    

    注意在 JavaScript 中對(duì)象和數(shù)組是引用類型,指向同一個(gè)內(nèi)存空間台夺,如果 prop 是一個(gè)對(duì)象或數(shù)組径玖,在子組件內(nèi)部改變它會(huì)影響父組件的狀態(tài)。即便引用類型可以颤介,也不要利用這個(gè)特性梳星,記住一個(gè)原則:組件的數(shù)據(jù)狀態(tài)在組件內(nèi)部管理維護(hù),不要在其他位置去修改它

Prop 驗(yàn)證(這個(gè)我們當(dāng)前用不到滚朵,有多余時(shí)間了解的可以學(xué)冤灾,不講)

我們可以為組件的 prop 指定驗(yàn)證規(guī)則。如果傳入的數(shù)據(jù)不符合要求辕近,Vue 會(huì)發(fā)出警告韵吨。這對(duì)于開發(fā)給他人使用的組件非常有用。 要指定驗(yàn)證規(guī)則移宅,需要用對(duì)象的形式來定義 prop归粉,而不能用字符串?dāng)?shù)組:

export default {
    data(){
        return {}
    },
  props: {
    // 基礎(chǔ)類型檢測(cè) (`null` 指允許任何類型)
    propA: Number,
    // 可能是多種類型
    propB: [String, Number],
    // 必傳且是字符串
    propC: {
      type: String,
      required: true
    },
    // 數(shù)值且有默認(rèn)值
    propD: {
      type: Number,
      default: 100
    },
    // 數(shù)組/對(duì)象的默認(rèn)值應(yīng)當(dāng)由一個(gè)工廠函數(shù)返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定義驗(yàn)證函數(shù)
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
}

type 可以是下面原生構(gòu)造器:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol type 也可以是一個(gè)自定義構(gòu)造器函數(shù)椿疗,使用 instanceof 檢測(cè)。當(dāng) prop 驗(yàn)證失敗盏浇,Vue 會(huì)拋出警告 (如果使用的是開發(fā)版本)变丧。 注意 prop 會(huì)在組件實(shí)例創(chuàng)建之前進(jìn)行校驗(yàn),所以在 default 或 validator 函數(shù)里绢掰,諸如 data痒蓬、computed 或 methods 等實(shí)例屬性還無法使用。

子傳父(Events Up)

我們知道滴劲,父組件使用 prop 傳遞數(shù)據(jù)給子組件攻晒。但子組件怎么跟父組件通信呢?這個(gè)時(shí)候 Vue 的自定義事件系統(tǒng)就派得上用場了班挖。

1鲁捏、在子組件中調(diào)用 $emit() 方法發(fā)布一個(gè)事件

methods: {
    incrementCounter: function () {
        this.counter += 1
        // 發(fā)布一個(gè)名字叫 increment 的事件
        this.$emit('increment')
        // this.$emit('update:foo', "傳遞過去的參數(shù)");
    }
},

2、在父組件中提供一個(gè)子組件內(nèi)部發(fā)布的事件處理函數(shù)

new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
});
  1. 在使用子組件的模板的標(biāo)簽上訂閱子組件內(nèi)部發(fā)布的事件

    <div id="counter-event-example">
    <p>{{ total }}</p>
    <!--
     訂閱子組件內(nèi)部發(fā)布的 increment 事件
     當(dāng)子組件內(nèi)部 $commit('increment') 發(fā)布的時(shí)候萧芙,就會(huì)調(diào)用到父組件中的 incrementTotal 方法
    -->
    <button-counter v-on:increment="incrementTotal"></button-counter>
    </div>
    

非父子組件通信

簡單場景:Event Bus (少用)

有時(shí)候给梅,非父子關(guān)系的兩個(gè)組件之間也需要通信。在簡單的場景下双揪,可以使用一個(gè)空的 Vue 實(shí)例作為事件總線:

var bus = new Vue();
// 觸發(fā)組件 A 中的事件
bus.$emit('id-selected', 1);

// 在組件 B 創(chuàng)建的鉤子中監(jiān)聽事件
bus.$on('id-selected', function (id) {
  // ...
});

復(fù)雜場景:Vuex(最常用)

在復(fù)雜的情況下动羽,我們應(yīng)該考慮使用專門的狀態(tài)管理模式

第七章Vuex

組件通信

  • 父子通信
    • Props Down
    • Events Up
  • 非父子怎么辦渔期?
    • Vuex
    • 全局的 Global Bus

Vuex 是什么

Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式运吓。它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化疯趟。Vuex 也集成到 Vue 的官方調(diào)試工具 devtools extension拘哨,提供了諸如零配置的 time-travel 調(diào)試、狀態(tài)快照導(dǎo)入導(dǎo)出等高級(jí)調(diào)試功能
vuex.png

什么情況下我應(yīng)該使用 Vuex

Vuex 可以幫助我們管理共享狀態(tài)信峻,并附帶了更多的概念和框架倦青。這需要對(duì)短期和長期效益進(jìn)行權(quán)衡。

如果您不打算開發(fā)大型單頁應(yīng)用盹舞,使用 Vuex 可能是繁瑣冗余的产镐。確實(shí)是如此——如果您的應(yīng)用夠簡單,您最好不要使用 Vuex矾策。一個(gè)簡單的 store 模式就足夠您所需了。但是峭沦,如果您需要構(gòu)建一個(gè)中大型單頁應(yīng)用贾虽,您很可能會(huì)考慮如何更好地在組件外部管理狀態(tài),Vuex 將會(huì)成為自然而然的選擇吼鱼。

基本用法

xx

在SPA單頁面組件的開發(fā)中 Vue的vuex和React的Redux 都統(tǒng)稱為同一狀態(tài)管理蓬豁,個(gè)人的理解是全局狀態(tài)管理更合適绰咽;簡單的理解就是你在state中定義了一個(gè)數(shù)據(jù)之后,你可以在所在項(xiàng)目中的任何一個(gè)組件里進(jìn)行獲取地粪、進(jìn)行修改取募,并且你的修改可以得到全局的響應(yīng)變更。下面咱們一步一步地剖析下vuex的使用:

1蟆技、安裝vuex

npm install vuex --save

2玩敏、創(chuàng)建倉庫 然后 在src文件目錄下新建一個(gè)名為store的文件夾,為方便引入并在store文件夾里新建一個(gè)index.js,里面的內(nèi)容如下: 如果腳手架生成有vuex那直接跳到3步质礼。

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store();

export default store;

接下來旺聚,在 main.js里面引入store,然后再全局注入一下眶蕉,這樣一來就可以在任何一個(gè)組件里面使用this.$store了:

import store from './store'//引入store
new Vue({
    store
  render: h => h(App),
}).$mount('#app')

state:狀態(tài)

設(shè)置倉庫要保存的值和操作那些值的方法 回到store文件的index.js里面砰粹,我們先聲明一個(gè)state變量,并賦值一個(gè)空對(duì)象給它造挽,里面隨便定義兩個(gè)初始屬性值碱璃;然后再在實(shí)例化的Vuex.Store里面?zhèn)魅胍粋€(gè)空對(duì)象,并把剛聲明的變量state仍里面:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
 const state={//要設(shè)置的全局訪問的state對(duì)象
     showFooter: true,
     changableNum:0
     //要設(shè)置的初始屬性值
   };
 const store = new Vuex.Store({
       state
    });
export default store;

getters:計(jì)算屬性

現(xiàn)在可以用this.store.state.showFooter或this.store.state.changebleNum在任何一個(gè)組件里面獲取showfooter和changebleNum定義的值了饭入,但這不是理想的獲取方式嵌器;vuex官方API提供了一個(gè)getters,和vue計(jì)算屬性computed一樣圣拄,來實(shí)時(shí)監(jiān)聽state值的變化(最新狀態(tài))嘴秸,并把它也仍進(jìn)Vuex.Store里面,具體看下面代碼:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
 const state={   //要設(shè)置的全局訪問的state對(duì)象
     showFooter: true,
     changableNum:0
     //要設(shè)置的初始屬性值
   };
const getters = {   //實(shí)時(shí)監(jiān)聽state值的變化(最新狀態(tài))
    isShow(state) {  //方法名隨意,主要是來承載變化的showFooter的值
       return state.showFooter
    },
    getChangedNum(){  //方法名隨意,主要是用來承載變化的changableNum的值
       return state.changebleNum
    }
};
const store = new Vuex.Store({
       state,
       getters
});
export default store;

mutations:更改狀態(tài)

光有定義的state的初始值庇谆,不改變它不是我們想要的需求岳掐,接下來要說的就是mutations了,mutattions也是一個(gè)對(duì)象饭耳,這個(gè)對(duì)象里面可以放改變state的初始值的方法串述,具體的用法就是給里面的方法傳入?yún)?shù)state或額外的參數(shù),然后利用vue的雙向數(shù)據(jù)驅(qū)動(dòng)進(jìn)行值的改變,同樣的定義好之后也把這個(gè)mutations扔進(jìn)Vuex.Store里面寞肖,如下:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
 const state={   //要設(shè)置的全局訪問的state對(duì)象
     showFooter: true,
     changableNum:0
     //要設(shè)置的初始屬性值
   };
const getters = {   //實(shí)時(shí)監(jiān)聽state值的變化(最新狀態(tài))
    isShow(state) {  //承載變化的showFooter的值
       return state.showFooter
    },
    getChangedNum(){  //承載變化的changebleNum的值
       return state.changableNum
    }
};
const mutations = {
    show(state) {   //自定義改變state初始值的方法纲酗,這里面的參數(shù)除了state之外還可以再傳額外的參數(shù)(變量或?qū)ο?;
        state.showFooter = true;
    },
    hide(state) {  //同上
        state.showFooter = false;
    },
    newNum(state,sum){ //同上,這里面的參數(shù)除了state之外還傳了需要增加的值sum
       state.changableNum+=sum;
    }
};
 const store = new Vuex.Store({
       state,
       getters,
       mutations
});
export default store;

actions:異步操作

這時(shí)候你完全可以用 this.store.commit('show') 或 this.store.commit('hide') 以及 this.store.commit('newNum',6) 在別的組件里面進(jìn)行改變showfooter和changebleNum的值了新蟆,但這不是理想的改變值的方式觅赊;因?yàn)樵?Vuex 中,mutations里面的方法 都是同步事務(wù)琼稻,意思就是說:比如這里的一個(gè)this.store.commit('newNum',sum)方法,兩個(gè)組件里用執(zhí)行得到的值吮螺,每次都是一樣的,這樣肯定不是理想的需求

vuex官方API還提供了一個(gè)actions,這個(gè)actions也是個(gè)對(duì)象變量鸠补,最大的作用就是里面的Action方法 可以包含任意異步操作萝风,這里面的方法是用來異步觸發(fā)mutations里面的方法,actions里面自定義的函數(shù)接收一個(gè)context參數(shù)和要變化的形參紫岩,context與store實(shí)例具有相同的方法和屬性规惰,所以它可以執(zhí)行context.commit(' '),然后也不要忘了把它也扔進(jìn)Vuex.Store里面:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
 const state={   //要設(shè)置的全局訪問的state對(duì)象
     showFooter: true,
     changableNum:0
     //要設(shè)置的初始屬性值
   };
const getters = {   //實(shí)時(shí)監(jiān)聽state值的變化(最新狀態(tài))
    isShow(state) {  //承載變化的showFooter的值
       return state.showFooter
    },
    getChangedNum(){  //承載變化的changebleNum的值
       return state.changableNum
    }
};
const mutations = {
    show(state) {   //自定義改變state初始值的方法,這里面的參數(shù)除了state之外還可以再傳額外的參數(shù)(變量或?qū)ο?;
        state.showFooter = true;
    },
    hide(state) {  //同上
        state.showFooter = false;
    },
    newNum(state,sum){ //同上泉蝌,這里面的參數(shù)除了state之外還傳了需要增加的值sum
       state.changableNum+=sum;
    }
};
 const actions = {
    hideFooter(context) {  //自定義觸發(fā)mutations里函數(shù)的方法歇万,context與store 實(shí)例具有相同方法和屬性
        context.commit('hide');
    },
    showFooter(context) {  //同上注釋
        context.commit('show');
    },
    getNewNum(context,num){   //同上注釋,num為要變化的形參
        context.commit('newNum',num)
     }
};
  const store = new Vuex.Store({
       state,
       getters,
       mutations,
       actions
});
export default store;

this.store.commit('increment') 觸發(fā)mutations this.store.store.dispatch('increment') 觸發(fā) actions

// 以載荷形式分發(fā) actions
this.$store.dispatch('incrementAsync', {
  amount: 10
})
// 以對(duì)象形式分發(fā) actions
this.$store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

而在外部組件里進(jìn)行全局執(zhí)行actions里面方法的時(shí)候梨与,你只需要用執(zhí)行this.$store.dispatch('hideFooter')this.$store.dispatch('showFooter') 以及this.$store.dispatch('getNewNum'堕花,6)//6要變化的實(shí)參,這樣就可以全局改變改變showfooter或changebleNum的值了

不要濫用 Vuex,推薦方式一粥鞋,能父子的就父子缘挽,非父子的使用 Vuex。

總結(jié):

  • 把需要共享的狀態(tài)放到 Vuex 容器中進(jìn)行管理呻粹,不需要共享的還是放到組件內(nèi)部去管理
  • 容器也支持計(jì)算屬性 getters
  • 修改容器的狀態(tài)務(wù)必通過 mutation 函數(shù)
    • 注意:不要在 mutation 函數(shù)中執(zhí)行異步操作去修改 state
  • 如果需要需要執(zhí)行異步操作修改 state
    • 定義 action
    • 在 action 中執(zhí)行異步操作
    • 在 action 中執(zhí)行異步操作結(jié)束只有通過提交 mutation 的方式來修改數(shù)據(jù)狀態(tài)

簡單一句話:非異步操作修改狀態(tài)使用 mutation壕曼,異步操作修改狀態(tài)使用 action 。其次我們使用 Vuex 并不意味著你需要將所有的狀態(tài)放入 Vuex等浊。雖然將所有的狀態(tài)放到 Vuex 會(huì)使?fàn)顟B(tài)變化更顯式和易調(diào)試腮郊,但也會(huì)使代碼變得冗長和不直觀。如果有些狀態(tài)嚴(yán)格屬于單個(gè)組件筹燕,最好還是作為組件的局部狀態(tài)轧飞。你應(yīng)該根據(jù)你的應(yīng)用開發(fā)需要進(jìn)行權(quán)衡和確定。

modules 模塊化 以及 組件中引入 mapGetters撒踪、mapActions 和 mapStates的使用,這些目前不講过咬,有精力的同學(xué)可以自行看

第八章 Vue-router

單頁面應(yīng)用程序:

  • 網(wǎng)易云音樂
  • CODING

單頁應(yīng)用(英語:single-page application,縮寫SPA):在傳統(tǒng)的網(wǎng)頁應(yīng)用中制妄,瀏覽器更多的是充當(dāng)一個(gè)展示層掸绞,路由處理、服務(wù)調(diào)用耕捞、頁面跳轉(zhuǎn)流程都由服務(wù)端來處理衔掸。即 MVC 都放在服務(wù)器端,SPA技術(shù)將邏輯從服務(wù)器轉(zhuǎn)移到了客戶端。這導(dǎo)致Web服務(wù)器發(fā)展為一個(gè)純數(shù)據(jù)API或Web服務(wù)俺抽。這種架構(gòu)的轉(zhuǎn)變?cè)谝恍┤ψ又斜环Q為“瘦服務(wù)器架構(gòu)”敞映,以強(qiáng)調(diào)復(fù)雜性已從服務(wù)端轉(zhuǎn)移到客戶端,并認(rèn)為這最終降低了系統(tǒng)的整體復(fù)雜性磷斧。

傳統(tǒng)的網(wǎng)站有以下特點(diǎn):

  • 重服務(wù)端振愿,由于 MVC 都存在于服務(wù)器上诗芜,因此這類應(yīng)用在開發(fā)資源和開發(fā)的重心都偏向后端,往往是后端工程師來主導(dǎo)整個(gè)項(xiàng)目開發(fā)埃疫;
  • 頁面頻繁刷新,由于瀏覽器端只是一個(gè)展現(xiàn)層孩哑,當(dāng)頁面功能有所變化的時(shí)栓霜,頁面就刷新,這會(huì)導(dǎo)致資源的浪費(fèi)横蜒,用戶需要花費(fèi)額外的時(shí)間等待頁面刷新胳蛮,用戶體驗(yàn)不佳。

單頁面應(yīng)用丛晌,只有一張Web頁面的應(yīng)用仅炊,是一種從Web服務(wù)器加載的富客戶端,單頁面跳轉(zhuǎn)僅刷新局部資源 澎蛛,公共資源(js抚垄、css等)僅需加載一次。

SPA優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  • 無刷新體驗(yàn)谋逻,這個(gè)應(yīng)該是最顯著的有點(diǎn)呆馁,由于路由分發(fā)直接在瀏覽器端完成,頁面是不刷新毁兆,對(duì)用戶的響應(yīng)非常及時(shí)浙滤,因此提升了用戶體驗(yàn)
  • 減輕服務(wù)器壓力,服務(wù)器只用出數(shù)據(jù)就可以气堕,不用管展示邏輯和頁面合成纺腊,吞吐能力會(huì)提高幾倍
  • 完全的前端組件化,前端開發(fā)不再以頁面為單位茎芭,更多地采用組件化的思想揖膜,代碼結(jié)構(gòu)和組織方式更加規(guī)范化,便于修改和調(diào)整
  • 良好的前后端分離開發(fā)骗爆,前端負(fù)責(zé)界面顯示次氨,后端負(fù)責(zé)數(shù)據(jù)存儲(chǔ)和計(jì)算,各司其職摘投,不會(huì)把前后端的邏輯混雜在一起

缺點(diǎn):

  • 不利于 SEO煮寡,單頁頁面,數(shù)據(jù)在前端渲染犀呼,就意味著沒有 SEO幸撕,或者需要使用變通的方案(不過目前可以配置服務(wù)端渲染來解決這個(gè)問題,但還不夠足夠成熟)
  • 初次加載耗時(shí)相對(duì)增多外臂,要在一個(gè)頁面上為用戶提供產(chǎn)品的所有功能坐儿,在這個(gè)頁面加載的時(shí)候,首先要加載大量的靜態(tài)資源,這個(gè)加載時(shí)間相對(duì)比較長
  • 較高的前端開發(fā)門檻貌矿,對(duì)開發(fā)人員技能水平要求較高炭菌,不再是『切切圖,畫畫頁面這么簡單』
  • 低版本瀏覽器兼容差

路由

前端路由目前主要有兩種方法:

  • 利用url的hash,就是常用的錨點(diǎn)(#)操作逛漫,類似頁面中點(diǎn)擊某小圖標(biāo)黑低,返回頁面頂部,JS通過hashChange事件來監(jiān)聽url的改變酌毡,IE7及以下需要輪詢進(jìn)行實(shí)現(xiàn)克握。一般常用框架的路由機(jī)制都是用的這種方法,例如Angualrjs自帶的ngRoute和二次開發(fā)模塊ui-router枷踏,react的react-route,vue-route…
  • 利用HTML5的History模式菩暗,使url看起來類似普通網(wǎng)站,以”/”分割旭蠕,沒有”#”停团,但頁面并沒有跳轉(zhuǎn),不過使用這種模式需要服務(wù)器端的支持掏熬,服務(wù)器在接收到所有的請(qǐng)求后客蹋,都指向同一個(gè)html文件,通過historyAPI孽江,監(jiān)聽popState事件讶坯,用pushState和replaceState來實(shí)現(xiàn)。

SPA 前端路由原理與實(shí)現(xiàn)方式:通常 SPA 中前端路由有2中實(shí)現(xiàn)方式

  • 修改 url 中 Hash
  • 利用 H5 中的 history

Vue-router

Vue-router 是 Vue.js 官方的路由管理器岗屏。它和 Vue.js 的核心深度集成辆琅,讓構(gòu)建單頁面應(yīng)用變得易如反掌

安裝

使用腳手架初始化的時(shí)候可以初始化安裝vue-router,如果沒有就手動(dòng)下載和使用

npm install vue-router

使用

新建router文件夾,然后建立一個(gè)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: [              //配置路由婉烟,這里是個(gè)數(shù)組
    {                    //每一個(gè)鏈接都是一個(gè)對(duì)象
      path: '/',         //鏈接路徑 當(dāng)路徑為 http://localhost:8080/#/顯示此組件
      name: 'Hello',     //路由名稱,
      component: Hello   //對(duì)應(yīng)要顯示的組件
        },
        {                    //每一個(gè)鏈接都是一個(gè)對(duì)象
      path: '/demo',     //鏈接路徑暇屋,當(dāng)路徑為 http://localhost:8080/#/demo顯示此組件
      name: 'Demo',     //路由名稱似袁,
      component: Demo   //對(duì)應(yīng)要顯示的組件
    }
  ]
})

在main.js中使用router

import Vue from 'vue'
import App from './App.vue'
import router from "./router/index.js" // 導(dǎo)入路由
Vue.config.productionTip = false

new Vue({
    router, // 使用路由
  render: h => h(App),
}).$mount('#app')

在App.js中要使用 router-view組件

<template>
  <div id="app">
        <img alt="Vue logo" src="./assets/logo.png">
        <!-- 路由出口 -->
      <!-- 路由匹配到的組件將渲染在這里 -->
        <router-view></router-view> 
  </div>
</template>

路由跳轉(zhuǎn)方式

  • 聲明式導(dǎo)航

    <template>
    <div id="app">
    <h1>Hello App!</h1>
    <p>
     <!-- 使用 router-link 組件來導(dǎo)航. -->
     <!-- 通過傳入 `to` 屬性指定鏈接. -->
     <!-- <router-link> 默認(rèn)會(huì)被渲染成一個(gè) `<a>` 標(biāo)簽 -->
     <router-link to="/">Go to root</router-link>
         <router-link to="/demo">Go to demo</router-link>
    </p>
    <!-- 路由出口 -->
    <!-- 路由匹配到的組件將渲染在這里 -->
    <router-view></router-view>
    </div>
    </template>
    
  • 編程式導(dǎo)航

除了使用 創(chuàng)建 a 標(biāo)簽來定義導(dǎo)航鏈接,我們還可以借助 router 的實(shí)例方法咐刨,通過編寫代碼來實(shí)現(xiàn)

前進(jìn)和后退this.router.go(-1) 和 this.router.go(1):功能跟我們?yōu)g覽器上的后退和前進(jìn)按鈕一樣昙衅,這在業(yè)務(wù)邏輯中經(jīng)常用到。比如條件不滿足時(shí)定鸟,我們需要后退而涉。

app.vue文件里加入一個(gè)按鈕,按鈕并綁定一個(gè)goback( )方法


<button @click="goback">后退</button>
<script>
export default {
  name: 'app',
  methods:{
    goback(){
      this.$router.go(-1);
    }
  }
}
</script>

編程式導(dǎo)航都作用就是跳轉(zhuǎn)联予,比如我們判斷用戶名和密碼正確時(shí)啼县,需要跳轉(zhuǎn)到用戶中心頁面或者首頁材原,都用到這個(gè)編程的方法來操作路由。this.$router.push

export default {
  name: 'app',
  methods:{
    goback(){
      this.$router.go(-1);
    },
    goHome(){
            // 字符串
      this.$router.push('/');
    }
  }
}

子路由

export default new Router({
  routes: [              //配置路由季眷,這里是個(gè)數(shù)組
    {                    //每一個(gè)鏈接都是一個(gè)對(duì)象
      path: '/',         //鏈接路徑
      name: 'Hello',     //路由名稱余蟹,
      component: Hello   //對(duì)應(yīng)要顯示的組件
        },
        {                    //每一個(gè)鏈接都是一個(gè)對(duì)象
      path: '/demo',     //鏈接路徑
      name: 'Demo',     //路由名稱,
      component: Demo   //對(duì)應(yīng)要顯示的組件
        },
        {
            // 對(duì)某個(gè)路由使用子路由子刮,那么需得在Hi1組件內(nèi)使用 <router-view></router-view>客叉,否則切換
            // 了子路由也無法顯示出來
            path:'/h1',
            name:'Hi1',
            component:Hi1,  // 當(dāng)訪問 /h1的時(shí)候顯示Hi1這個(gè)組件
            children:[
                {path:"/", component:Hi1}, // 當(dāng)訪問 /h1的時(shí)候<router-view></router-view>被這個(gè)組件替換,就會(huì)有2個(gè)Hi1組件內(nèi)容话告。一般不這么寫,一般這么寫{ path: '', component: Hi1 },
                {path:"h2", component:Hi2}, // 當(dāng) /h1/h2 匹配成功卵慰,Hi2會(huì)被渲染在 Hi1 的 <router-view> 中
                {path:"h3", component:Hi3}  // 當(dāng) /h1/h3 匹配成功沙郭,Hi3會(huì)被渲染在 Hi1 的 <router-view> 中
            ]
        }
  ]
})

動(dòng)態(tài)路由

  • 動(dòng)態(tài)路由的匹配和獲取值
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})
---------在User組件中可以通過下面方式去拿------------
{{$route.params.id}}
  • 動(dòng)態(tài)路由的跳轉(zhuǎn)
第一種聲明式動(dòng)態(tài)路由跳轉(zhuǎn)
<!-- 動(dòng)態(tài)路由通過命名路由跳轉(zhuǎn)  /user/123   -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
第二種編程式導(dǎo)航的動(dòng)態(tài)路由跳轉(zhuǎn)
const userId = '123'
// 對(duì)象,命名的動(dòng)態(tài)路由
router.push({ name: 'user', params: { userId }}) // -> /user/123

// 如果提供了 path裳朋,params 會(huì)被忽略
router.push({ path: `/user/${userId}` }) // -> /user/123
// 這里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

路由重定向

重定向也是通過 routes 配置來完成病线,下面例子是從 /a 重定向到 /b

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})

重定向的目標(biāo)也可以是一個(gè)命名的路由:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

路由守衛(wèi)

(路由的聲明周期鉤子函數(shù),進(jìn)階部分學(xué)習(xí))

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鲤嫡,一起剝皮案震驚了整個(gè)濱河市送挑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌暖眼,老刑警劉巖惕耕,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诫肠,居然都是意外死亡司澎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門栋豫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挤安,“玉大人,你說我怎么就攤上這事丧鸯「蛲” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵丛肢,是天一觀的道長围肥。 經(jīng)常有香客問我,道長蜂怎,這世上最難降的妖魔是什么虐先? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮派敷,結(jié)果婚禮上蛹批,老公的妹妹穿的比我還像新娘撰洗。我一直安慰自己,他們只是感情好腐芍,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布差导。 她就那樣靜靜地躺著,像睡著了一般猪勇。 火紅的嫁衣襯著肌膚如雪设褐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天泣刹,我揣著相機(jī)與錄音助析,去河邊找鬼。 笑死椅您,一個(gè)胖子當(dāng)著我的面吹牛外冀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掀泳,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼雪隧,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了员舵?” 一聲冷哼從身側(cè)響起脑沿,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎马僻,沒想到半個(gè)月后庄拇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡韭邓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年丛忆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仍秤。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡熄诡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诗力,到底是詐尸還是另有隱情凰浮,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響页眯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笛厦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望俺夕。 院中可真熱鬧裳凸,春花似錦贱鄙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至梦湘,卻和暖如春瞎颗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捌议。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工哼拔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓣颅。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓倦逐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弄捕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • vue文檔以及api閱讀整理 更新時(shí)間2019-12-20 雖然vue3.0馬上要發(fā)布了, 但是我認(rèn)為許多核心ap...
    情有千千節(jié)閱讀 2,701評(píng)論 0 4
  • vue 的讀音 view vue到底是什么导帝? 一個(gè)mvvm框架 vue和angular的區(qū)別: vue:簡單容易上...
    gtt21閱讀 2,068評(píng)論 0 1
  • 1, v-model 會(huì)忽略所有表單元素的 value守谓、checked、selected 特性的初始值而總是將 ...
    冬至_5b58閱讀 315評(píng)論 0 0
  • 基于Vue的一些資料 內(nèi)容 UI組件 開發(fā)框架 實(shí)用庫 服務(wù)端 輔助工具 應(yīng)用實(shí)例 Demo示例 element★...
    嘗了又嘗閱讀 1,142評(píng)論 0 1
  • Vue 3.0 性能提升主要是通過哪幾方面體現(xiàn)的您单? vue2在初始化的時(shí)候斋荞,對(duì)data中的每個(gè)屬性使用define...
    Smallbore閱讀 1,160評(píng)論 0 8