VUE-第二季-02

3.Vue組件化

3.1 什么是組件

(1) 傳統方式開發的應用

一個網頁通常包括三部分:結構(HTML)、樣式(CSS)、交互(JavaScript)

傳統應用存在的問題:
① 關系縱橫交織,復雜,牽一發動全身,不利于維護。
② 代碼雖然復用,但復用率不高。

(2) 組件化方式開發的應用

使用組件化方式開發解決了以上的兩個問題:
① 每一個組件都有獨立的js,獨立的css,這些獨立的js和css只供當前組件使用,不存在縱橫交錯。更加便于維護。
② 代碼復用性增強。組件不僅讓js css復用了,HTML代碼片段也復用了(因為要使用組件直接引入組件即可)。

(3) 什么是組件?

① 組件:實現應用中局部功能的代碼和資源的集合。凡是采用組件方式開發的應用都可以稱為組件化應用。
② 模塊:一個大的js文件按照模塊化拆分規則進行拆分,生成多個js文件,每一個js文件叫做模塊。凡是采用模塊方式開發的應用都可以稱為模塊化應用。
③ 任何一個組件中都可以包含這些資源:HTML CSS JS 圖片 聲音 視頻等。從這個角度也可以說明組件是可以包括模塊的。
(4) 組件的劃分粒度很重要,粒度太粗會影響復用性。為了讓復用性更強,Vue的組件也支持父子組件嵌套使用。

子組件由父組件來管理,父組件由父組件的父組件管理。在Vue中根組件就是vm。因此每一個組件也是一個Vue實例。

3.2 組件的創建、注冊和使用

(1) 創建組件

①	const userComponent = Vue.extend({這個配置項和創建Vue實例的配置項幾乎是一樣的,只是略有差異})
②	需要注意的是:
1)	el不能用。組件具有通用性,不特定為某個容器服務,它為所有容器服務。
2)	data必須使用函數形式:return {}
3)	使用template配置項配置頁面結構:HTML。

(2) 注冊組件

①	局部注冊
  1. 使用components配置項:components : {user : userComponent},user就是組件名。
    ② 全局注冊
  1. Vue.component(‘user’, userComponent)

(3) 使用組件

①	直接在頁面需要使用組件的位置:<user></user>②	也可以這樣使用:<user/> (不在腳手架環境中使用這種方式會出現后續元素不渲染的問題。)

(4) 創建組件對象也有簡寫形式:Vue.extend() 可以省略。直接寫:{}

(5) 組件的命名細節:

① 全部小寫
② 首字母大寫,后面全部小寫
③ kebab-case串式命名法
④ CamelCase駝峰式命名法(這種方式需要在腳手架環境中使用)
⑤ 不要使用HTML內置的標簽作為組件名稱。
⑥ 可以使用name配置項來指定Vue開發者工具中顯示的組件名。

編寫的第一個組件代碼

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="../../js/vue.js"></script></head><body><div id="app"><h1>{{msg}}</h1><!--3.使用組件 --><userlist></userlist><userlist></userlist></div><script>//1.創建組件const userCompont = Vue.extend({template : `<ul><li v-for="(user,index) in users" :key='user.id'>{{user.id}} : {{user.username}}</li></ul>`,//data必須是函數的形式data(){return {users : [{ id: '001', username: 'hax1' },{ id: '002', username: 'hax2' },{ id: '003', username: 'hax3' },]}}})const vm = new Vue({el: '#app',data: {msg: '第一個組件練習',// users: [// ? ? { id: '001', username: 'hax1' },// ? ? { id: '002', username: 'hax2' },// ? ? { id: '003', username: 'hax3' },// ]},//2.注冊組件components : {userlist : userCompont}})</script></body></html>

全局組件和各種細節

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>第一個組件</title><script src="../js/vue.js"></script></head><body><!--組件的使用分為三步:第一步:創建組件Vue.extend({該配置項和new Vue的配置項幾乎相同,略有差別})區別有哪些?1. 創建Vue組件的時候,配置項中不能使用el配置項。(但是需要使用template配置項來配置模板語句。)2. 配置項中的data不能使用直接對象的形式,必須使用function。第二步:注冊組件局部注冊:在配置項當中使用components,語法格式:components : {組件的名字 : 組件對象}全局注冊:Vue.component('組件的名字', 組件對象)第三步:使用組件小細節:1. 在Vue當中是可以使用自閉合標簽的,但是前提必須在腳手架環境中使用。2. 在創建組件的時候Vue.extend()可以省略,但是底層實際上還是會調用的,在注冊組件的時候會調用。3. 組件的名字第一種:全部小寫第二種:首字母大寫,后面都是小寫第三種:kebab-case命名法(串式命名法。例如:user-login)第四種:CamelCase命名法(駝峰式命名法。例如:UserLogin),但是這種方式只允許在腳手架環境中使用。不要使用HTML內置的標簽名作為組件的名字。在創建組件的時候,通過配置項配置一個name,這個name不是組件的名字,是設置Vue開發者工具中顯示的組件的名字。--><div id="app"><h1>{{msg}}</h1><!-- 3. 使用組件 --><userlogin></userlogin><userlist></userlist><userlist></userlist><userlist></userlist><userlogin></userlogin><!-- <userlogin/> --></div><div id="app2"><userlogin></userlogin><hello-world></hello-world><!-- <form></form> --></div><script>/* // 創建組件const abc = {template : `<h1>測試組件的名字????</h1>`}// 全局注冊組件Vue.component('HelloWorld', abc) */Vue.component('hello-world', {name : 'Xxxxx',template : `<h1>測試組件的名字%%%%%</h1>`})/* Vue.component('form', {template : `<h1>測試組件的名字%%%%%</h1>`}) */// 1.創建組件(結構HTML 交互JS 樣式CSS)/* const myComponent = Vue.extend({template : `<ul><li v-for="(user,index) of users" :key="user.id">{{index}},{{user.name}}</li></ul>`,data(){return {users : [{id:'001',name:'jack'},{id:'002',name:'lucy'},{id:'003',name:'james'}]}}}) */const myComponent = {template : `<ul><li v-for="(user,index) of users" :key="user.id">{{index}},{{user.name}}</li></ul>`,data(){return {users : [{id:'001',name:'jack'},{id:'002',name:'lucy'},{id:'003',name:'james'}]}}}// 1. 創建組件/* const userLoginComponent = Vue.extend({template : `<div><h3>用戶登錄</h3><form @submit.prevent="login">賬號:<input type="text" v-model="username"> <br><br>密碼:<input type="password" v-model="password"> <br><br><button>登錄</button></form></div>`,data(){return {username : '',password : ''}},methods: {login(){alert(this.username + "," + this.password)}},}) */const userLoginComponent = {template : `<div><h3>用戶登錄</h3><form @submit.prevent="login">賬號:<input type="text" v-model="username"> <br><br>密碼:<input type="password" v-model="password"> <br><br><button>登錄</button></form></div>`,data(){return {username : '',password : ''}},methods: {login(){alert(this.username + "," + this.password)}},}// 全局注冊Vue.component('userlogin', userLoginComponent)const vm2 = new Vue({el : '#app2'})// Vue實例const vm = new Vue({el : '#app',data : {msg : '第一個組件'},// 2. 注冊組件(局部注冊)components : {// userlist是組件的名字。myComponent只是一個變量名。userlist : myComponent,//userlogin : userLoginComponent}})/* let data = {counter : 1} */function data(){return {counter : 1}}let x = data();let y = data();</script></body></html>

3.3 組件嵌套

代碼學習:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>組件嵌套</title><script src="../../js/vue.js"></script></head><body><div id="root"></div><script>// 創建Y1組件const y1 = {template : `<div><h3>Y1組件</h3></div>`}// 創建X1組件const x1 = {template : `<div><h3>X1組件</h3></div>`}// 創建Y組件const y = {template : `<div><h2>Y組件</h2><y1></y1></div>`,components : {y1}}// 創建X組件const x = {template : `<div><h2>X組件</h2><x1></x1></div>`,components : {x1}}// 創建app組件const app = {template : `<div><h1>App組件</h1><x></x><y></y></div>`,// 注冊X組件components : {x,y}}// vmconst vm = new Vue({el : '#root',template : `<app></app>`,// 注冊app組件components : {app}})</script></body></html>

嵌套結構:這種結構更加貼切實際項目的開發

3.4 VueComponent & Vue

3.4.1 this

new Vue({})配置項中的this和Vue.extend({})配置項中的this他們分別是誰?

1.	<!DOCTYPE html>  
2.	<html lang="en">  
3.	<head>  
4.	    <meta charset="UTF-8">  
5.	    <title>vm與vc</title>  
6.	    <script src="../js/vue.js"></script>  
7.	</head>  
8.	<body>  
9.	    <div id="app">  
10.	        <mc></mc>  
11.	    </div>  
12.	    <script>  
13.	        const myComponent = Vue.extend({  
14.	            template : `<h1></h1>`,  
15.	            mounted(){  
16.	                console.log('vc', this)  
17.	            }  
18.	        })  
19.	  
20.	        const vm = new Vue({  
21.	            el : '#app',  
22.	            components : {  
23.	                mc : myComponent  
24.	            },  
25.	            mounted() {  
26.	                console.log('vm', this)  
27.	            },  
28.	        })  
29.	    </script>  
30.	</body>  
31.	</html> 

測試結果:

new Vue({})配置項中的this就是:Vue實例(vm)。
Vue.extend({})配置項中的this就是:VueComponent實例(vc)。
打開vm和vc你會發現,它們擁有大量相同的屬性。例如:生命周期鉤子、methods、watch等。

3.4.2 vm === vc ???

只能說差不多一樣,不是完全相等。
例如:
vm上有el,vc上沒有。
另外data也是不一樣的。vc的data必須是一個函數。
只能這么說:vm上有的vc上不一定有,vc上有的vm上一定有。

3.4.3 Vue.extend()方法做了什么?

每一次的extend調用返回的都是一個全新的VueComponent函數。
以下是Vue.extend()的源碼:
?

注意:是每一次都會返回一個全新的VueComponent構造函數。是全新的!!!

構造函數有了,什么時候會調用構造函數來實例化VueComponent對象呢?

Vue在解析<mc></mc>時會創建一個VueComponent實例,也就是:new VueComponent()

3.4.4 通過vc可以訪問Vue原型對象上的屬性

通過vc可以訪問Vue原型對象上的屬性:
1. Vue.prototype.counter = 100
2. console.log(vc.counter) // 100

為什么要這么設計?代碼復用。Vue原型對象上有很多方法,例如:mount(),對于組件VueComponent來說就不需要再額外提供了,直接使用vc調用mount(),代碼得到了復用。

Vue框架是如何實現以上機制的呢?
1.????? VueComponent.prototype.proto?=?Vue.prototype
測試:

1.	<!DOCTYPE html>  
2.	<html lang="en">  
3.	<head>  
4.	    <meta charset="UTF-8">  
5.	    <title>測試</title>  
6.	    <script src="../js/vue.js"></script>  
7.	</head>  
8.	<body>  
9.	    <div id="app"></div>  
10.	    <script>  
11.	        const userlist = Vue.extend({  
12.	            template : `<div><h1>用戶列表</h1></div>`,  
13.	        })  
14.	        const vm = new Vue({  
15.	            el : '#app',  
16.	            template : `<userlist></userlist>`,  
17.	            components : {userlist}  
18.	        })  
19.	        console.log(userlist.prototype.__proto__ === Vue.prototype) // true  
20.	    </script>  
21.	</body>  
22.	</html>  
3.4.4.1 回顧原型對象

prototype稱為:顯示的原型屬性,用法:函數.prototype,例如:Vue.prototype
proto__稱為:隱式的原型屬性,用戶:實例.proto,例如:vm.proto
無論是通過prototype還是__proto,獲取的對象都是同一個,它是一個共享的對象,稱為:XX的原型對象。
如果通過Vue.prototype獲取的對象就叫做:Vue的原型對象。
如果通過User.prototype獲取的對象就叫做:User的原型對象。
請看下圖:

3.4.4.2 原理剖析

VueComponent.prototype.proto = Vue.prototype

這樣做的話,最終的結果就是:Vue、vm、VueComponent、vc都共享了Vue的原型對象(并且這個Vue的原型對象只有一個)。

代碼學習:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>vm與vc</title><script src="../../js/vue.js"></script></head><body><div id="app"><h1>{{msg}}</h1><user></user><user></user><user></user></div><script>// 這個不是給Vue擴展counter屬性。// 這個是給“Vue的原型對象”擴展一個counter屬性。Vue.prototype.counter = 1000// 創建組件const user = Vue.extend({template : `<div><h1>user組件</h1></div>`,mounted(){// this是VueComponent實例// user是什么呢????是一個全新的構造函數 VueComponent構造函數。//console.log('vc', this === user)// 為什么要這樣設計?為了代碼復用。// 底層實現原理:// VueComponent.prototype.__proto__ = Vue.prototypeconsole.log('vc.counter', this.counter)// 這個訪問不了,因為msg是vm實例上的屬性。//console.log('vc.msg', this.msg)}})console.log('user.prototype.__proto__ === Vue.prototype' , user.prototype.__proto__ === Vue.prototype)console.log(user)// vmconst vm = new Vue({el : '#app',data : {msg : 'vm與vc'},components : {user},mounted() {// this是Vue實例console.log('vm', this)},})console.log('vm.counter', vm.counter)// 本質上是這樣的:console.log('vm.counter', vm.__proto__.counter)/* function test(){var Sub = function User(){this.name = 'admin'}return Sub}let a = test()// 通過構造函數創建對象console.log(new a()) *//* console.log(a)let b = test()console.log(b)console.log(a === b) */// prototype __proto__// 構造函數(函數本身又是一種類型,代表Vip類型)function Vip(){}// Vip類型/Vip構造函數,有一個 prototype 屬性。// 這個prototype屬性可以稱為:顯式的原型屬性。// 通過這個顯式的原型屬性可以獲取:原型對象// 獲取Vip的原型對象let x = Vip.prototype// 通過Vip可以創建實例let a = new Vip()/* let b = new Vip()let c = new Vip() */// 對于實例來說,都有一個隱式的原型屬性: __proto__// 注意:顯式的(建議程序員使用的)。隱式的(不建議程序員使用的。)// 這種方式也可以獲取到Vip的原型對象let y = a.__proto__/* b.__proto__c.__proto__ */// 原型對象只有一個,其實原型對象都是共享的。console.log(x === y) // true// 這個不是給Vip擴展屬性// 是在給“Vip的原型對象”擴展屬性Vip.prototype.counter = 1000// 通過a實例可以訪問這個擴展的counter屬性嗎?可以訪問。為什么?原理是啥?// 訪問原理:首先去a實例上找counter屬性,如果a實例上沒有counter屬性的話,會沿著__proto__這個原型對象去找。// 下面代碼看起來表面上是a上有一個counter屬性,實際上不是a實例上的屬性,是a實例對應的原型對象上的屬性counter。console.log(a.counter)//console.log(a.__proto__.counter)</script></body></html>

3.5 單文件組件

  1. 什么是單文件組件?
    (1) 一個文件對應一個組件(之前我們所學的是非單文件組件,一個html文件中定義了多個組件)
    (2) 單文件組件的名字通常是:x.vue,這是Vue框架規定的,只有Vue框架能夠認識,瀏覽器無法直接打開運行。需要Vue框架進行編譯,將x.vue最終編譯為瀏覽器能識別的html js css。
    (3) 單文件組件的文件名命名規范和組件名的命名規范相同:
    ① 全部小寫:userlist
    ② 首字母大寫,后面全部小寫:Userlist
    ③ kebab-case命名法:user-list
    ④ CamelCase命名法:UserList(我們使用這種方式,和Vue開發者工具呼應。)
  1. x.vue文件的內容包括三塊:
    (1) 結構:<template>HTML代碼</template>
    (2) 交互:<script>JS代碼</script>
    (3) 樣式:<style>CSS代碼</style>
  1. export和import,ES6的模塊化語法。
    (1) 使用export導出(暴露)組件,在需要使用組件的x.vue文件中使用import導入組件
    ① 默認導入和導出
    1) export default {}

2) import 任意名稱 from ‘模塊標識符’

② 按需導入和導出
1) export {a, b}
2) import {a, b} from ‘模塊標識符’
③ 分別導出
export var name = ‘zhangsan’
export function sayHi(){}
4. VSCode工具可以安裝一些插件,這樣在編寫x.vue的時候有提示。例如:vetur插件。
(1) 使用該插件之后,有高亮顯示,并且也可以通過輸入 <v 生成代碼。
5. 把之前“組件嵌套”的例子修改為單文件組件
![file-20250803120222466.png](https://gitee.com/javaxubo/work_note-images/raw/master//image/202508031202121.png

auto rename tag插件

這個?name: 'App'?表示該組件的名稱是 "App",在其他地方可以通過這個名稱來引用或識別該組件。如果沒有顯式設置?name,Vue 會使用文件名作為默認名稱(在單文件組件中)。

記住一個要領:不管是單文件組件還是非單文件組件,永遠都包括三步:創建組件、注冊組件、使用組件。

創建vm的代碼就不是一個組件了,這個js代碼寫到一個js文件中即可,一般這個起名:main.js。寓意:入口

還剩最后的一點HTML代碼,一般這個文件叫做index.html,代碼如下:

如上圖,注意引入順序。
代碼執行原理:

① 第一步:瀏覽器打開index.html頁面,加載容器
② 第二步:加載vue.js文件,有了Vue
③ 第三步:加載main.js1)	import App from ‘./App.vue’2)	import X from './X.vue'3)	import X1 from './X1.vue'4)	import Y from './Y.vue'5)	import Y1 from './Y1.vue'
這樣就完成了所有組件以及子組件的創建和注冊。
④ 第四步:創建Vue實例vm,編譯模板語句,渲染。
寫完之后不能直接運行,瀏覽器不認識.vue文件,不認識ES6的模塊化語法。需要安裝Vue腳手架。

3.6 Vue腳手架

3.6.1 確保npm能用(安裝Node.js)

Node.js的下載地址:
Node.js — Download Node.js?
安裝步驟參考地址:Node安裝 - 哩個啷個波 - 博客園

打開dos命令窗口,輸入npm命令。

3.6.2 Vue CLI(腳手架安裝)

  1. Vue的腳手架(Vue CLI: Command Line Interface)是Vue官方提供的標準化開發平臺。它可以將我們.vue的代碼進行編譯生成html css js代碼,并且可以將這些代碼自動發布到它自帶的服務器上,為我們Vue的開發提供了一條龍服務。腳手架官網地址:Vue CLI

注意:Vue CLI 4.x需要Node.js v8.9及以上版本,推薦v10以上。
2. 腳手架安裝步驟:
① 建議先配置一下npm鏡像:
1) npm config set registry https://registry.npm.taobao.org
2) npm config get registry 返回成功,表示設置成功
② 第一步:安裝腳手架(全局方式:表示只需要做一次即可)
1) npm install -g @vue/cli
如果這一步安裝出現問題 參考這里的文章解決 npm ERR! code CERT_HAS_EXPIRED:解決證書過期問題 - 哩個啷個波 - 博客園
2) 安裝完成后,重新打開DOS命令窗口,輸入vue命令可用表示成功了
③ 第二步:創建項目(項目中自帶腳手架環境,自帶一個HelloWorld案例)
1) 切換到要創建項目的目錄,然后使用 vue create vue_pro

這里選擇Vue2,
babel:負責ES6語法轉換成ES5。
eslint:負責語法檢查的。
回車之后,就開始創建項目,創建腳手架環境(內置了webpack loader),自動生成HelloWorld案例。

④ 第三步:編譯Vue程序,自動將生成html css js放入內置服務器,自動啟動服務。
1) dos命令窗口中切換到項目根:cd vue_pro
2) 執行:npm run serve,這一步會編譯HelloWorld案例

ctrl + c停止服務。
3) 打開瀏覽器,訪問:http://localhost:8080

3.6.3 認識腳手架結構

使用VSCode將vue_pro項目打開:

package.json:包的說明書(包的名字,包的版本,依賴哪些庫)。該文件里有webpack的短命令:
serve(啟動內置服務器)
build命令是最后一次的編譯,生成html css js,給后端人員
lint做語法檢查的。

3.6.4 分析HelloWorld程序

可以看到在index.html中只有一個容器。沒有引入vue.js,也沒有引入main.js
Vue腳手架可以自動找到main.js文件。(所以main.js文件名不要修改,位置也不要隨便移動)


接下來就是將之前寫的程序拷貝到腳手架中,進行測試。
需要拷貝過來的是:App.vue、X.vue、Y.vue、X1.vue、Y1.vue。
main.js和index.html都不需要了,因為腳手架中有。

只需要將App.vue中的路徑修改一下即可:

打開VSCode終端:ctrl + `, 注意這里命令沒有,只是因為md文件中才特意轉的
在終端中執行:npm run serve
報錯了:

導致這個錯誤的原因是:組件的名字應該由多單詞組成。這是eslint進行的es語法檢測。
解決這個問題有兩種方案:
第一種:把所有組件的名字修改一下。
第二種:在vue.config.js文件中進行腳手架的默認配置。配置如下:

在終端中ctrl + c 兩次,終止之前的服務,再次運行命令:npm run serve

3.6.5 腳手架默認配置

腳手架默認配置在vue.config.js文件中進行。
main.js、index.html等都是可以配置的。
配置項可以參考Vue CLI官網手冊,如下:

例如配置這兩項:
第一個:保存時不檢查語法 lintOnSave : false
第二個:配置入口

3.6.6 解釋main.js中的render函數

將render函數更換為:template配置項,你會發現它是報錯的。說明引入的Vue無法進行模板編譯。
原因:Vue腳手架默認引入的是精簡版的Vue,這個精簡版的Vue缺失模板編譯器。

實際引入的vue.js文件是:dist/vue.runtime.esm.js(esm版本是ES6模塊化版本)
為什么缺失模板編譯器?
Vue包含兩部分:一部分是Vue的核心,一部分是模板編譯器(模板編譯器可能占整個vue.js文件的一大部分體積)。程序員最終使用webpack進行打包的時候,顯然Vue中的模板編譯器就沒有存在的必要了。為了縮小體積,所以在Vue腳手架中直接引入的就是一個缺失模板編譯器的vue.js。
這樣就會導致template無法編譯(注意:<template>標簽可以正常編譯[package.json文件中進行了配置],說的是template配置項無法編譯),解決這個問題包括兩種方式:
第一種方式:引入一個完整的vue.js
第二種方式:使用render函數
關于render函數,完整寫法:

這個函數被vue自動調用,并且傳遞過來一個參數createElement。
簡寫形式可以使用箭頭函數:

3.7 props配置

使用props配置可以接收其他組件傳過來的數據,讓組件的數據變為動態數據,三種接收方式:
(1) 簡單接收
props : [‘name’,’age’,’sex’]
(2) 接收時添加類型限制
props : {
name : String
age : Number
sex : String
}
(3) 接收時添加類型限制,必要性限制,默認值
props : {
name : {
type : Number,
required : true
},
age : {
type : Number,
default : 10
},
sex : {
type : String,
default : ‘男’
}
}
其他組件怎么把數據傳過來?
<User name=”jack” age=”20” sex=”男”></User>
注意事項:
① 不要亂接收,接收的一定是其它組件提供的。
② props接收到的數據不能修改。(修改之后會報錯,但頁面會刷新。)可以找個中間變量來解決。
代碼學習:
Car.vue

<template><div><h3>品牌:{{brand}}</h3><h3>價格:{{cprice}}</h3><h3>顏色:{{color}}</h3><button @click="add">價格加1</button></div></template><script>export default {name : 'Car',data() {return {cprice : this.price}},methods : {add(){this.cprice++}},/* data() {return {brand : '寶馬520',price : 10,color : '黑色'}}, */// 在Car這個子組件當中使用props配置項進行數據的接收。// 第一種:簡單的接收方式,直接采用數組接收。//props : ['brand','color','price']// 第二種:添加類型限制/* props : {brand : String,color : String,price : Number} */// 第三種:添加類型限制,并且還可以添加默認值,還可以添加必要性// 避免直接更改prop,因為每當父組件重新渲染時,該值都會被覆蓋// 注意:不要修改prop中的數據。props : {brand : {type : String,required : true},color : {type : String,default : '紅色'},price : {type : Number,required : true}}}</script>

App.vue

<template><div><h1>{{msg}}</h1><!-- 在App這個父組件當中,找到子組件Car,然后給Car這個子組件傳數據:通過屬性的形式傳數據 --><Car brand="寶馬520" color="黑色" :price="10"></Car><hr><Car brand="比亞迪漢" color="紅色" :price="20"></Car></div></template><script>import Car from './components/Car.vue'export default {name : 'App',data() {return {msg : '汽車信息'}},components : {Car}}</script>

瀏覽器效果:

3.8 從父組件中獲取子組件

在組件上使用ref屬性進行標識:

`<User ref=”userJack”></User>`

在程序中使用$refs來獲取子組件:

this.$refs.userJack

訪問子組件的屬性:

this.$refs.userJack.name

訪問子組件的子組件屬性:

this.$refs.userJack.$refs.name

ref也可以使用在普通的HTML標簽上,這樣獲取的就是這個DOM元素:

`<input type=”text” ref=”username”>`
this.$refs.username

代碼演示

<template><div><h1 ref="hh">{{msg}}</h1><!-- 在App這個父組件當中,找到子組件Car,然后給Car這個子組件傳數據:通過屬性的形式傳數據 --><Car brand="寶馬520" color="黑色" :price="10" ref="car1"></Car><hr><Car brand="比亞迪漢" color="紅色" :price="20" ref="car2"></Car><hr><button @click="printCarInfo">打印汽車信息</button></div></template><script>import Car from './components/Car.vue'export default {name : 'App',data() {return {msg : '汽車信息'}},methods : {printCarInfo(){// 獲取子組件console.log(this.$refs.car1)console.log(this.$refs.car2)console.log(this.$refs.car1.brand)console.log(this.$refs.car1.color)console.log(this.$refs.car1.price)// 這個就不是組件了。console.log(this.$refs.hh.innerText)}},components : {Car}}</script>

3.9 mixins配置(混入)

運行效果:

可以看到以上Vip.vue和User.vue代碼中都有相同的methods,這個代碼可以復用嗎?可以使用mixins配置進行混入。實現步驟:

第一步:提取

單獨定義一個mixin.js(一般和main.js在同級目錄),代碼如下:

第二步:引入并使用

以上演示的是方法methods的混入,實際上混入時沒有限制,之前所學的配置項都可以混入。
混入時會產生沖突嗎?已經有一個方法a了,如果再混入一個a方法會怎樣?
?

通過測試,如果沖突了,會執行組件自身的,不會執行混入的。(這是原則:混入的意思就是不破壞)
但對于生命周期周期鉤子函數來說,混入時,會采用疊加方式:

執行結果:

通過測試得知:對于生命周期鉤子函數來說,都有的話,采用疊加,先執行混入的,再執行自己的。
以上的混入屬于局部混入,只混入到指定的組件當中。
全局混入:

執行結果:

一共四個組件,所以輸入四次:mixin mounted
main.js

// 等同于引入vue.js文件import Vue from 'vue'// 導入App組件(根組件)import App from './App.vue'import {mix1} from './mixin.js'import {mix2} from './mixin.js'import {mix3} from './mixin.js'// 全局混入Vue.mixin(mix1)Vue.mixin(mix2)Vue.mixin(mix3)// 關閉生產提示信息Vue.config.productionTip = false// 創建Vue實例new Vue({el : '#app',// 您正在使用Vue的僅運行時版本,其中模板編譯器不可用。// 目前使用的vue.js是一個缺失了模板編譯器的vue.js文件。// 怎么解決,兩種方案:// 第一種:使用完整版的vue.js: import Vue from 'vue/dist/vue.js'// 第二種:使用render函數。// 為什么采用缺失模板編譯器的vue.js?// 目的:減小體積。Vue.js包括兩塊:Vue的核心 + 模板編譯器(模板編譯器可能占用vue.js文件體積的三分之一。)// 將來程序員使用webpack進行打包處理之后,模板編譯器就沒有存在的必要了。//template : '<h1>render函數</h1>'// 看不懂,一會再說。//render: h => h(App),// render函數被自動調用// 這個函數被調用的時候會自動傳過來一個參數:createElement,createElement是一個函數。// createElement函數可以用來創建元素/* render(createElement){// 創建了一個div元素//return createElement('div', 'render函數')return createElement(App)} */render : h => h(App)})

mixins

export const mix1 = {methods: {printInfo(){console.log(this.name, ',' , this.age)}}}export const mix2 = {methods: {a(){console.log('mixin.js a.....')}},}export const mix3 = {mounted() {console.log('mixin.js mounted...')}}

User

<template><div><h1>{{msg}}</h1><h3>姓名:{{name}}</h3><h3>年齡:{{age}}</h3><button @click="printInfo">打印用戶信息</button><button @click="a">用戶a</button></div></template><script>// import {mix1} from '../mixin.js'// import {mix2} from '../mixin.js'// import {mix3} from '../mixin.js'export default {name : 'User',mounted() {console.log('User mounted...')},data() {return {msg : '用戶信息',name : '張三2',age : 20}},// mixins : [mix1,mix2, mix3],/* methods: {a(){console.log('user a....')}}, */}</script>

Vip

<template><div><h1>{{msg}}</h1><h3>姓名:{{name}}</h3><h3>年齡:{{age}}</h3><button @click="printInfo">打印會員信息</button><button @click="a">會員a</button></div></template><script>// import {mix1} from '../mixin.js'// import {mix2} from '../mixin.js'export default {name : 'Vip',data() {return {msg : '會員信息',name : '李四2',age : 21}},// mixins : [mix1,mix2],methods: {a(){console.log('vip a....')}},}</script>

App

<template><div><User></User><Vip></Vip></div></template><script>import User from './components/User.vue'import Vip from './components/Vip.vue'export default {name : 'App',components : {User, Vip}}</script>

3.10 plugins配置(插件)

給Vue做功能增強的。
怎么定義插件?以下是定義插件并暴露插件。插件是一個對象,對象中必須有install方法,這個方法會被自動調用。

插件一般都放到一個plugins.js文件中。
導入插件并使用插件:

插件對象的install方法有兩個參數:
第一個參數:Vue構造函數
第二個參數:插件使用者傳遞的數據
先學會用插件,后面我們做項目的時候會使用很多插件。到時再體會插件存在的意義。

3.11 局部樣式scoped

默認情況下,在vue組件中定義的樣式最終會匯總到一塊,如果樣式名一致,會導致沖突,沖突發生后,以后來加載的組件樣式為準。怎么解決這個問題?

另外vue組件的style樣式支持多種樣式語言,例如:css、less、sass等。如何選擇使用呢?

使用less注意安裝less-loader:npm i less-loader
App根組件中的樣式style不建議添加scoped。

App

<template><div><User></User><Vip></Vip></div></template><script>//如果不加scoped 并且兩個組件當中css樣式屬性名一樣,就會出現覆蓋的情況,并且是按照誰后引入就會覆蓋先引入的文件樣式import Vip from './components/Vip.vue'import User from './components/User.vue'export default {name : 'App',components : {User, Vip}}</script><style>/* 一般在App根組件當中樣式不會添加scoped,因為根組件的樣式還是希望采用全局的方式處理。 */</style>

User

<template><div class="s"><h1>{{msg}}</h1><h3>姓名:{{name}}</h3><h3>年齡:{{age}}</h3></div></template><script>export default {name : 'User',data() {return {msg : '用戶信息',name : '張三2',age : 20}}}</script><style scoped>.s {background-color: aquamarine;}</style>

Vip

<template><div class="s"><h1>{{msg}}</h1><h3>姓名:{{name}}</h3><h3>年齡:{{age}}</h3></div></template><script>export default {name : 'Vip',data() {return {msg : '會員信息',name : '李四2',age : 21}}}</script><style scoped>.s {background-color:bisque}</style>

樣式效果:

3.12 BugList案例

  1. 先使用靜態組件的方式把頁面效果實現出來。
    (1) App.vue
    (2) BugHeader.vue
    (3) BugList.vue
    (4) BugItem.vue
    (5) BugFooter.vue

動態的判斷單選框的值是否默認勾選寫法:

刪除按鈕顏色的寫法:


實現效果:

表格的寫法:


實現效果:

第一版代碼:

App.vue

<template><div><BugHeader></BugHeader><br><BugList></BugList><BugFooter></BugFooter></div></template><script>//如果不加scoped 并且兩個組件當中css樣式屬性名一樣,就會出現覆蓋的情況,并且是按照誰后引入就會覆蓋先引入的文件樣式import BugHeader from './components/BugHeader.vue'import BugList from './components/BugList.vue'import BugFooter from './components/BugFooter.vue'// import User from './components/User.vue'export default {name : 'App',components : {BugHeader,BugList,BugFooter}}</script><style>/* 一般在App根組件當中樣式不會添加scoped,因為根組件的樣式還是希望采用全局的方式處理。 *//* 共享 */.button{display: inline-block;*display: inline;zoom: 1;padding: 6px 20px;margin: 0;cursor: pointer;border: 1px solid #bbb;overflow: visible;font: bold 13px arial, helvetica, sans-serif;text-decoration: none;white-space: nowrap;color: #555;background-color: #ddd;background-image: -webkit-gradient(linear, to right top, to right bottom, from(rgba(255,255,255,1)), to(rgba(255,255,255,0)));background-image: -webkit-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));background-image: -moz-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));background-image: -ms-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));background-image: -o-linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));background-image: linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0));-webkit-transition: background-color .2s ease-out;-moz-transition: background-color .2s ease-out;-ms-transition: background-color .2s ease-out;-o-transition: background-color .2s ease-out;transition: background-color .2s ease-out;background-clip: padding-box; /* Fix bleeding */-moz-border-radius: 3px;-webkit-border-radius: 3px;border-radius: 3px;-moz-box-shadow: 0 1px 0 rgba(0, 0, 0, .3), 0 2px 2px -1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .3) inset;-webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, .3), 0 2px 2px -1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .3) inset;box-shadow: 0 1px 0 rgba(0, 0, 0, .3), 0 2px 2px -1px rgba(0, 0, 0, .5), 0 1px 0 rgba(255, 255, 255, .3) inset;text-shadow: 0 1px 0 rgba(255,255,255, .9);-webkit-touch-callout: none;-webkit-user-select: none;-khtml-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;}.button:active{background: #e9e9e9;position: relative;top: 1px;text-shadow: none;-moz-box-shadow: 0 1px 1px rgba(0, 0, 0, .3) inset;-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .3) inset;box-shadow: 0 1px 1px rgba(0, 0, 0, .3) inset;}.button.red{color: #fff;text-shadow: 0 1px 0 rgba(0,0,0,.2);background-image: -webkit-gradient(linear, to right top, to right bottom, from(rgba(255,255,255,.3)), to(rgba(255,255,255,0)));background-image: -webkit-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));background-image: -moz-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));background-image: -ms-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));background-image: -o-linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));background-image: linear-gradient(to bottom, rgba(255,255,255,.3), rgba(255,255,255,0));}.button.red{background-color: #ca3535;border-color: #c43c35;}.button.red:hover{background-color: #ee5f5b;}.button.red:active{background: #c43c35;}.button.green{background-color: #57a957;border-color: #57a957;}.button.green:hover{background-color: #62c462;}.button.green:active{background: #57a957;}</style>

BugHeader.vue

<template><div class="header" ><textarea rows="3" cols="60" v-model="bugInfo" place="請輸入bug的信息"></textarea><br></br><button class="small red button" @click="save">保存</button></div></template><script>export default {data(){return {bugInfo : '',}},methods:{save(){console.log("保存用戶輸入的bug信息",this.bugInfo);}}}</script><style scoped>/* header */.header {margin-bottom: 20px;margin-top: 20px;}</style>

BugList.vue

<template><div><table><thead><tr><th class="c1">全選<input type="checkbox"> </th><th>bug描述</th><th class="c2">操作</th></tr></thead><tbody><BugItem v-for="bug in bugList" :key="bug.id" :bug="bug"></BugItem></tbody></table></div></template><script>import BugItem from './BugItem.vue'export default {components : {BugItem},data() {return {bugList: [{ id: '0001', bugInfo: '問題1', isResolve: true },{ id: '0002', bugInfo: '問題2', isResolve: true },{ id: '0003', bugInfo: '問題3', isResolve: false },]}}}</script><style scope>/* list */table {width: 760px;border-collapse: collapse;}table caption {font-size: 1em;font-weight: bold;margin: 1em 0;}.c1,.c2 {width: 100px;}th {border: 1px solid #999;text-align: center;padding: 5px 0;}table thead tr {background-color: #008c8c;color: #fff;}</style>

BugItem.vue

<template><!-- <div></div> ?--><tr><td><input type="checkbox" :checked="bug.isResolve"></td><td><span ?class="desc">{{ bug.bugInfo }}</span></td><td><button class="small red button">刪除</button></td></tr></template><script>export default {props : ['bug'],}</script><style >/* item */table tbody tr:nth-child(odd){background-color: #eee;}table tbody tr:hover{background-color: #ccc;}table tbody tr td:first-child{color: #f40;}td{border: 1px solid #999;text-align: center;padding: 5px 0;}/* 鼠標指針變成手型(👆) */.desc {cursor: pointer;}</style>

BugFooter.vue

<template><div class="footer"><button class="small red button">清除已解決</button><span>當前BUG總量是{{ 2 }}個,已解決1個。</span></div></template><script>export default {}</script><style >/* footer */.footer{margin-top: 10px;}.footer span{font-size: 12px;}</style>

初步頁面效果:

  1. 在BugList.vue中提供bugList數據,實現動態數據展示。
  1. 保存bug:
    (1) 獲取用戶輸入的信息采用雙向數據綁定。
    ① 通過Date.now()獲取時間戳的方式來搞定id。
    (2) 將BugList.vue中的bugList數據提升到父組件App.vue中。
    (3) 父組件向子組件傳遞,采用 :bugList=”bugList”,在子組件當中使用props接收。
    (4) 子組件向父組件傳遞,父組件可以提前定義一個函數,將函數傳遞給子組件,在子組件中調用這個函數即可。
    (5) 該功能的小問題:
    ① 保存完成后自動清空。
    ② 輸入為空時不能保存(可以加trim去除空白),并且提示必須輸入。
  1. 修改bug的狀態
    (1) 勾選和取消勾選,會觸發click事件或者change事件。
    (2) 事件發生后,獲取bug的id,將id傳遞給App組件中的回調函數,遍歷數組,拿到要修改的bug對象,更改bug對象的resolved屬性值。
  1. 刪除bug
    (1) 刪除時可以調用數組的filter方法進行過濾,將過濾之后的新數組賦值給this.bugList
  1. 統計bug
    (1) 第一種:普通計數器統計。
    (2) 第二種:數組的reduce方法完成條件統計。
  1. 全選和取消全選
    (1) 全選復選框的狀態維護:
    ① 已解決的數量 === 總數量 時,勾選。
    ② 全部刪除后,應該取消勾選。
    (2) 全部刪除了可以將footer隱藏。v-show
    (3) 全選和取消全選
  1. 清除已解決
    (1) 調用數組的filter方法進行過濾,生成新數組,將其賦值給this.bugList
  1. 實現編輯功能
    (1) 功能描述
    ① 鼠標移動到描述信息上之后,光標變成小手。
    ② 點擊描述信息之后,將描述信息放入文本框。并且同時讓文本框獲得焦點。
    ③ 用戶開始修改描述信息(要注意避免將信息修改為空)
    ④ 輸入修改信息之后,文本框失去焦點,顯示修改后的描述信息。
    (2) 實現功能的核心技術:
    ① 給bug對象擴展一個具有響應式的editState屬性,如果是true表示處于編輯狀態,false表示處于未編輯狀態:this.$set(bug, ‘editState’, true)
    ② 獲得焦點的動作如何完成:
  1. 在文本框上添加ref=”inputDesc”,然后通過this.$refs.inputDesc獲取到dom元素,調用focus()讓其獲取焦點。
  1. 以上操作需要在下一次渲染DOM完成后執行:nextTick
    a. this.nextTick(function(){this.refs.inputDesc.focus()})

3.13 localStorage和sessionStorage

window.localStorage 瀏覽器關閉,數據還在。
getItem removeItem setItem clear
JSON.stringify
JSON.parse
存儲大小5mb
Window.sessionStorage 瀏覽器關閉清空存儲。
getItem的key不存在的話返回null。JSON.parse(null),結果還是null。
改造項目。用本地存儲來改造。使用監視屬性watch,并且要開啟深度監視。

3.14 使用本地存儲改造BugList案例

3.15 組件自定義事件

click、keydown、keyup,這些事件都是內置事件。
Vue也支持給組件添加自定義事件。
包括兩種方式:
第一種方式:直接在組件標簽上綁定事件
第二種方式:通過代碼來給組件綁定事件

3.15.1 直接在組件標簽上綁定事件

<Car @event1=”doSome”>

表示給Car這個組件vc實例綁定event1事件,當event1事件發生時,doSome方法執行。
事件綁定在誰的身上,誰就負責觸發這個事件,怎么觸發?在Car組件中定義methods:
methods : {
triggerEvent1(){
// 觸發事件并且給事件傳數據
this.$emit(‘event1’, this.name, this.age, this.gender)
}
}
然后,在Car的父組件中編寫doSome方法:
methods : {
doSome(name, age, gender){}
// 或者可以這樣
doSome(name, ...parameters){} // ...parameters表示采用一個數組接收參數
}
通過這種方式可以輕松完成子組件向父組件傳遞數據。
<Car @event1.once=”doSome”> 表示只觸發一次。 <Car @click.native=”doSome”> 使原生事件生效。

3.15.2 通過代碼給組件綁定事件

在父組件當中:

mounted(){ // 表示掛載完畢后給組件綁定事件。
// 這種方式更加靈活。例如:希望AJAX請求響應回來數據之后再給組件綁定事件。
this.refs.car.on(‘event1’, this.doSome)
}
this.refs.car.once(‘event1’, this.doSome) 表示只觸發一次。
綁定時要注意:
this.refs.car.on(‘event1’, function(){
//這里的this是子組件實例(Car組件實例)
})
this.refs.car.on(‘event1’, ()=>{
// 這里的this是父組件實例(App組件實例)
})
this.doSome這個回調函數寫成普通函數時:函數體中this是子組件實例。(Car組件實例)
this.doSome這個回調函數寫成箭頭函數時:函數體中this是父組件實例。(App組件實例)

3.15.3 解綁事件

哪個組件綁定的就找哪個組件解綁:
methods : {
unbinding(){
this.off(‘event1’) // 這種方式只能解綁一個事件。
this.off([‘event1’, ‘event2’]) // 這種方式解綁多個事件。
this.$off() // 解綁所有事件。
}
}
注意:vm和vc銷毀的時候,所有組件以及子組件當中的事件會全部解綁。

3.16 全局事件總線

原理:給項目中所有的組件找一個共享的vc對象。把這個共享的對象vc叫做全局事件總線。所有的事件都可以綁定到這個共享對象上。所有組件都通過這個全局事件總線對象來傳遞數據。這種方式可以完美的完成兄弟組件之間傳遞數據。這樣的共享對象必須具備兩個特征:
(1) 能夠讓所有的vc共享。
(2) 共享對象上有on、off、emit等方法。
第一種解決方案:
在main.js文件中:
// 獲取VueComponent構造函數
const VueComponentConstructor = Vue.extend({})
// 創建vc
const vc = new VueComponentConstructor()
// 讓所有的vc都能夠使用這個vc
Vue.prototype.bus = vc
第二種解決方案:建議的。
在main.js文件中:
new Vue({
el : '#app',
render : h => h(App),
beforeCreate(){
Vue.prototype.$bus = this
}
})
永遠需要記住的:A組件向B組件傳數據,應該在B組件中綁定事件(接)。應該在A組件中觸發事件(傳)。

數據發送方:觸發事件
methods : {
triggerEvent(){
this.bus.emit(‘eventx’, 傳數據)
}
}
數據接收發:綁定事件
mounted(){
this.bus.on(‘eventx’, this.doSome)
}
養成好習慣:組件實例被銷毀前,將綁定在bus上的事件解綁。
beforeDestroy(){
this.bus.off(‘eventx’)
}

3.17 BugList案例改造

3.17.1 使用組件自定義事件改造BugList案例

所有從父向子傳遞函數的位置,都可以修改為自定義事件方式。

父組件向子組件傳遞數據的寫法

主要改造子向父傳數據的功能。

3.17.2 使用全局事件總線改造BugList案例

主要改造爺孫之間數據的傳遞。
自定義事件在Vue開發者工具當中是可以在看到的。
組件銷毀的時候,記得把全局事件總線對象上綁定的事件解綁。

3.18 消息訂閱與發布

使用pubsub-js庫完成消息訂閱與發布。該庫可以在任意前端框架中實現消息的訂閱與發布。
安裝pubsub-js:npm i pubsub-js
程序中引入pubsub:import pubsub from ‘pubsub-js’
引入了一個pubsub對象,通過調用該對象的subscribe進行消息訂閱,調用publish進行消息發布。
訂閱:subscribe
mounted(){
this.pubsubId = pubsub.subscribe(‘message’, (messageName, data) => {
// 兩個參數:第一個是消息的名字。第二個參數是消息發布時傳過來的數據。
// 要使用箭頭函數。這樣才能保證this的使用。
})
}
beforeDestroy(){
pubsub.unsubscribe(this.pubsubId )
}
發布:publish
pubsub.publish(‘message’, ‘zhangsan’, 20)

組件間的通信方式總結:
① props:可以完成父向子傳數據
② 父向子傳一個函數:可以完成子向父傳數據
③ 組件自定義事件:可以完成子向父傳數據。
④ 全局事件總線
⑤ 消息訂閱與發布

3.19 使用消息訂閱與發布改造BugList案例

組件銷毀時,記得取消訂閱。

4.Vue與AJAX

4.1 回顧發送AJAX異步請求的方式
發送AJAX異步請求的常見方式包括:

  1. 原生方式,使用瀏覽器內置的JS對象XMLHttpRequest
    (1) const xhr = new XMLHttpRequest()
    (2) xhr.onreadystatechange = function(){}
    (3) xhr.open()
    (4) xhr.send()
  1. 原生方式,使用瀏覽器內置的JS函數fetch
    (1) fetch(‘url’, {method : ‘GET’}).then().then()
  1. 第三方庫方式,JS庫jQuery(對XMLHttpRequest進行的封裝)
    (1) .get()
    (2) .post()
  1. 第三方庫方式,基于Promise的HTTP庫:axios (對XMLHttpRequest進行的封裝)
    (1) axios.get().then()
    axios是Vue官方推薦使用的。
    4.2 回顧AJAX跨域
  1. 什么是跨域訪問?
    (1) 在a頁面中想獲取b頁面中的資源,如果a頁面和b頁面所處的協議、域名、端口不同(只要有一個不同),所進行的訪問行動都是跨域的。
    (2) 哪些跨域行為是允許的?
    ① 直接在瀏覽器地址欄上輸入地址進行訪問
    ② 超鏈接


    <link href=”其它網站的css文件是允許的”>
    <script src=”其它網站的js文件是允許的”>
    ⑥ ......
    (3) 哪些跨域行為是不允許的?
    ① AJAX請求是不允許的
    ② Cookie、localStorage、IndexedDB等存儲性內容是不允許的
    ③ DOM節點是不允許的
  1. AJAX請求無法跨域訪問的原因:同源策略
    (1) 同源策略是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到XSS、CSRF等攻擊。同源是指"協議+域名+端口"三者相同,即便兩個不同的域名指向同一個ip地址,也非同源。
    (2) AJAX請求不允許跨域并不是請求發不出去,請求能發出去,服務端能收到請求并正常返回結果,只是結果被瀏覽器攔截了。
  1. 解決AJAX跨域訪問的方案包括哪些
    (1) CORS方案(工作中常用的)
    ① 這種方案主要是后端的一種解決方案,被訪問的資源設置響應頭,告訴瀏覽器我這個資源是允許跨域訪問的:response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
    (2) jsonp方案(面試常問的)
    ① 采用的是<script src=””>不受同源策略的限制來實現的,但只能解決GET請求。
    (3) 代理服務器方案(工作中常用的)
    ① Nginx反向代理
    ② Node中間件代理
    ③ vue-cli(Vue腳手架自帶的8080服務器也可以作為代理服務器,需要通過配置vue.config.js來啟用這個代理)
    (4) postMesssage
    (5) websocket
    (6) window.name + iframe
    (7) location.hash + iframe
    (8) document.domain + iframe
    (9) ......
  1. 代理服務器方案的實現原理
    同源策略是瀏覽器需要遵循的標準,而如果是服務器向服務器請求就無需遵循同源策略的。

4.3 演示跨域問題
Vue腳手架內置服務器的地址:http://localhost:8080
我們可以額外再開啟一個其它的服務器,這個服務器隨意,例如:node server、Apache服務器、JBOSS服務器、WebLogic服務器、WebSphere服務器、jetty服務器、tomcat服務器......我這里選擇的是基于Java語言的一個服務器Tomcat,這個web服務器開啟了一個8000端口,提供了以下的一個服務,可以幫助我們獲取到一個Bug列表:
http://localhost:8000/bugs/

打開BugList案例的代碼,在mounted鉤子函數中發送ajax請求,獲取bug列表。
vue-cli安裝axios庫:npm i axios。使用時:import導入axios

以上的訪問表示:在8080服務器中發送AJAX請求訪問8000服務器,必然會出現AJAX跨域問題:

4.4 啟用Vue腳手架內置服務器8080的代理功能

  1. 簡單開啟
    vue.config.js文件中添加如下配置:
    devServer: {
    proxy: 'http://localhost:8000' // 含義:Vue腳手架內置的8080服務器負責代理訪問8000服務器
    }
    發送AJAX請求時,地址需要修改為如下:

原理:訪問地址是http://localhost:8080/bugs,會優先去8080服務器上找/bugs資源,如果沒有找到才會走代理。
另外需要注意的是:這種簡單配置不支持配置多個代理。
2. 高級開啟
支持配置多個代理。
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8000',
pathRewrite:{'^/api', ''},
ws: true, // 支持websocket
changeOrigin: true // true表示改變起源(讓目標服務器不知道真正的起源)
},
'/abc': {
target: 'http://localhost:9000',
pathRewrite:{'^/abc', ''},
ws: true, // 默認值true
changeOrigin: true // 默認值true
}
}
}
4.5 使用AJAX改造BugList案例
mounted鉤子中發送ajax請求即可。
4.6 Vue插件庫vue-resource發送AJAX請求

  1. 安裝:npm i vue-resource
  1. import vueResource from ‘vue-resource’
  1. 使用插件:Vue.use(vueResource)
  1. 使用該插件之后,項目中所有的vm和vc實例上都添加了:$http屬性。
  1. 使用辦法:
    (1) this.http.get(‘’).then() 用法和axios相同,只不過把axios替換成this.http
    4.7 天氣預報
  1. 實現效果
  1. 接口來自:https://openweathermap.org/
  1. 開發者進行注冊,獲取api key

  1. 獲取當前天氣的接口

  1. 根據城市名字獲取經度和緯度的接口
  1. 功能實現要點:
    (1) 首先實現靜態組件

    ② App根組件下有兩個子組件:Search、Weather
    (2) 根據城市名獲取經度緯度的接口:
    http://api.openweathermap.org/geo/1.0/direct?q=${this.cityName}&appid=${apiKey}
    ② 以上紅色字體采用了ES6的模板字符串。
    ③ 經度:const lon = response.data[0].lon
    ④ 緯度:const lat = response.data[0].lat
    (3) 獲取天氣信息的接口:
    https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}
    ② 天氣圖標:response.data.weather[0].icon
    ③ 溫度:response.data.main.temp
    ④ 濕度:response.data.main.humidity
    ⑤ 風力:response.data.wind.speed
    (4) 以上免費接口已經在服務端解決了跨域的問題,可以直接用。
    (5) 兄弟組件之間通信采用全局事件總線。
    (6) 以上接口返回的圖標的id,根據圖標id動態拼接圖片地址


    (7) 初次打開頁面的時候,不應該顯示天氣的任何信息,Weather組件應該是隱藏的。
    (8) 查詢天氣過程中,顯示“正在拉取天氣信息,請稍后...”
    (9) 如果訪問的城市不存在時,應該提示“對不起,請檢查城市名”
    (10) 如果出現其它錯誤一律顯示“網絡錯誤,請稍后再試”
    (11) 在Search組件中觸發事件的時候,將多個屬性封裝為對象的形式(語義化更明確)傳給Weather組件。
    ① 在Weather組件中可以采用對象的形式一次性接收。
    ② 注意結構中的代碼:

{{weather.windSpeed}} m/s

(12) ES6語法,合并對象
① this.weather = {...this.weather, ...weather}

5. Vuex

5.1 vuex概述

  1. vuex是實現數據集中式狀態管理的插件。數據由vuex統一管理。其它組件都去使用vuex中的數據。只要有其中一個組件去修改了這個共享的數據,其它組件會同步更新。一定要注意:全局事件總線和vuex插件的區別:
    (1) 全局事件總線關注點:組件和組件之間數據如何傳遞,一個綁定on,一個觸發emit。數據實際上還是在局部的組件當中,并沒有真正的讓數據共享。只是數據傳來傳去。

(2) vuex插件的關注點:共享數據本身就在vuex上。其中任何一個組件去操作這個數據,其它組件都會同步更新。是真正意義的數據共享。

  1. 使用vuex的場景是:
    (1) 多個組件之間依賴于同一狀態。來自不同組件的行為需要變更同一狀態。
    5.2 vuex環境搭建
  1. 安裝vuex
    (1) vue2安裝vuex3版本
    ① npm i vuex@3
    (2) vue3安裝vuex4版本
    ① npm i vuex@4
  1. 創建目錄和js文件(目錄和文件名不是必須叫這個)
    (1) 目錄:vuex
    (2) js文件:store.js
  1. 在store.js文件中創建核心store對象,并暴露
  1. 在main.js文件中關聯store,這一步很重要,完成這一步之后,所有的vm和vc對象上會多一個$store屬性

5.3 vuex實現一個最簡單的案例

  1. 使用vuex實現一個點我加1的簡單功能。
  1. 為什么這么折騰呢?
    (1) 通過以上案例,可以看出數據num可以被多個組件共享。(vuex可以管理多個組件共享的數據)
    (2) 通過on和emit這種全局事件總線不好嗎?可以。但如果組件多的話,并且涉及到讀和寫的操作會導致混亂。
  1. actions中的回調函數,參數context
    (1) 如果業務邏輯非常負責,需要多個actions中的方法聯合起來才能完成,可以在回調函數中使用context繼續調用dispatch方法觸發下一個action方法的執行。
    5.4 vuex工作原理

如果業務邏輯非常簡單,也不需要發送AJAX請求的話,可以不調用dispatch方法,直接調用commit方法也是可以的。
5.5 多組件數據共享
實現以下案例:

5.6 getters配置項

  1. 如果想將state中的數據進行加工計算,并且這個計算邏輯復雜,而且要在多個位置使用,建議使用getters配置項。
  1. 怎么用?
    (1)
    (2)
    (3) 類似于Vue當中的:data和computed的關系。
    5.7 mapState和mapGetters的使用(優化計算屬性)
  1. 組件中在使用state上的數據和getters上的數據時,都有固定的前綴:
    {{this.store.state.name}}
    {{this.store.getters.reverseName}}
    使用mapState和mapGetters進行名稱映射,可以簡化以上的寫法。
  1. 使用mapState和mapGetters的前提是先引入
    (1) import {mapState, mapGetters} from ‘vuex’
  1. mapState如何使用,在computed當中使用ES6的語法
    (1) 第一種方式:對象形式
    ① ...mapState({name:’name’})
    (2) 第二種方式:數組形式
    ① ...mapState([‘name’])
    (3) 插值語法就可以修改為:{{name}}
  1. mapGetters如何使用,在computed當中使用ES6的語法
    (1) 第一種方式:對象形式
    ① ...mapGetters({reverseName:’reverseName’})
    (2) 第二種方式:數組形式
    ① ...mapGetters([‘reverseName’])
    (3) 插值語法就可以修改為:{{reverseName}}
    5.8 mapMutations和mapActions的使用(優化methods)
    import {mapMutations, mapActions} from ‘vuex’
    methods : {
    // 對象寫法
    ...mapActions({add:’plusOne’,reverseName:’reverseName’})
    // 數組寫法(前提是:保證methods中的方法名和actions中的方法名一致)
    ...mapActions([‘plusOne’, ‘reverseName’])
    }
    5.9 vuex的模塊化開發
    5.9.1 未使用mapXxxx的模塊化開發
    a模塊

b模塊

c模塊

在store.js文件中引入各個模塊

A組件

b組件

將A組件和B組件在App組件中注冊

5.9.2 使用mapXxxx的模塊化開發
a模塊

b模塊

在store.js中引入a和b模塊

A組件

B組件

在APP組件中注冊A和B組件

當然,在action中也可以發送AJAX請求:

6. 路由route

6.1 傳統web應用vs單頁面web應用
傳統web應用
傳統web應用,又叫做多頁面web應用:核心是一個web站點由多個HTML頁面組成,點擊時完成頁面的切換,因為是切換到新的HTML頁面上,所以當前頁面會全部刷新。

單頁面web應用(SPA:Single Page web Application)
整個網站只有一個HTM頁面,點擊時只是完成當前頁面中組件的切換。屬于頁面局部刷新。

單頁應用程序 (SPA) 是加載單個HTML 頁面并在用戶與應用程序交互時動態更新該頁面的Web應用程序。瀏覽器一開始會加載必需的HTML、CSS和JavaScript,所有的操作都在這張頁面上完成,都由JavaScript來控制。單頁面的跳轉僅刷新局部資源。因此,對單頁應用來說模塊化的開發和設計顯得相當重要。
單頁面應用的優點:
1、提供了更加吸引人的用戶體驗:具有桌面應用的即時性、網站的可移植性和可訪問性。
2、單頁應用的內容的改變不需要重新加載整個頁面,web應用更具響應性和更令人著迷。
3、單頁應用沒有頁面之間的切換,就不會出現“白屏現象”,也不會出現假死并有“閃爍”現象
4、單頁應用相對服務器壓力小,服務器只用出數據就可以,不用管展示邏輯和頁面合成,吞吐能力會提高幾倍。
5、良好的前后端分離。后端不再負責模板渲染、輸出頁面工作,后端API通用化,即同一套后端程序代碼,不用修改就可以用于Web界面、手機、平板等多種客戶端
單頁面應用的缺點:
1、首次加載耗時比較多。
2、SEO問題,不利于百度,360等搜索引擎收錄。
3、容易造成CSS命名沖突。
4、前進、后退、地址欄、書簽等,都需要程序進行管理,頁面的復雜度很高,需要一定的技能水平和開發成本高。
單頁面和多頁面的對比

目前較為流行的是單頁面應用的開發。
如果想使用Vue去完成單頁面應用的開發,需要借助Vue當中的路由機制。
6.2 路由route與路由器router
路由:route
路由器:router
每一個路由都由key和value組成。
key1+value1===>路由route1
key2+value2===>路由route2
key3+value3===>路由route3
......
路由的本質:一個路由表達了一組對應關系。
路由器的本質:管理多組對應關系。
Vue中路由的工作原理:

6.3 使用路由

  1. 實現功能描述
  1. 根據靜態頁面提取兩個組件:Tea.vue和Fruit.vue

  1. vue-router也是一個插件,安裝vue-router
    (1) vue2要安裝vue-router3
    ① npm i vue-router@3
    (2) vu3要安裝vue-router4
    ① npm i vue-router@4
  1. main.js中引入并使用vue-router
    (1) 導入:import VueRouter from ‘vue-router’
    (2) 使用:Vue.use(VueRouter)
    (3) new Vue時添加新的配置項:一旦使用了vue-router插件,在new Vue的時候可以添加一個全新的配置項:router
  1. router路由器的創建一般放在一個獨立的js文件中,例如:router/index.js
    (1) 創建router目錄
    (2) 創建index.js,在index.js中創建路由器對象,并且將其暴露。然后在main.js文件中引入該路由器即可。
  1. 使用router-link標簽代替a標簽(App.vue中)

router-link標簽最終編譯之后的還是a標簽。vue-router庫幫助我們完成的。
7. 添加激活樣式
使用active-class屬性,在激活時添加樣式:selected

  1. 指定組件的最終顯示位置。
  1. 測試

注意事項:
① 路由組件一般會和普通組件分開存放,路由組件放到pages目錄,普通組件放到components目錄下。
② 路由組件在進行切換的時候,切掉的組件會被銷毀。
③ 路由組件實例比普通組件實例多兩個屬性:route和router
1) route:屬于自己的路由對象。
2) router:多組件共享的路由器對象。
6.4 多級路由

  1. 要實現的效果
  1. 主要實現

6.5 路由query傳參
為了提高組件的復用性,可以給路由組件傳參。
怎么傳?

怎么接?

6.6 路由起名字
可以給路由起一個名字,這樣可以簡化to的編寫。
怎么起名?

怎么使用?必須使用 :to=”{}” 的方式

6.7 路由params傳參
怎么接?

怎么傳?

需要注意的是,如果采用params傳參,使用:to的時候,只能用name,不能用path。
6.8 路由的props
props配置主要是為了簡化query和params參數的接收。讓插值語法更加簡潔。
第一種實現方式:

第二種實現方式:函數式

第三種實現方式:直接將params方式收到的數據轉化為props

6.9 router-link的replace屬性

  1. 棧數據結構:先進后出,后進先出原則。
  1. 瀏覽器的歷史記錄是存儲在棧這種數據結構當中的。包括兩種模式:
    (1) push模式(默認的)
    (2) replace模式
  1. 如何開啟replace模式:
    (1)
    (2)
    6.10 編程式路由導航
    需求中可能不是通過點擊超鏈接的方式切換路由,也就是說不使用如何實現路由切換。可以通過相關API來完成:
    (1) push模式:
    ① this.router.push({
    name : ‘’,
    query : {}
    })
    (2) replace模式:
    ① this.router.replace({
    name : ‘’,
    query : {}
    })
    (3) 前進:
    ① this.router.forward()
    (4) 后退:
    ① this.router.back()
    (5) 前進或后退幾步:
    ① this.router.go(2) 前進兩步
    ② this.router.go(-2) 后退兩步
    (6) 使用編程式路由導航的時候,需要注意:重復執行push或者replace的API時,會出現以下錯誤:

這個問題是因為push方法返回一個Promise對象,期望你在調用push方法的時候傳遞兩個回調函數,一個是成功的回調,一個是失敗的回調,如果不傳就會出以上的錯誤。所以解決以上問題只需要給push和replace方法在參數上添加兩個回調即可。
6.11 緩存路由組件
默認情況下路由切換時,路由組件會被銷毀。有時需要在切換路由組件時保留組件(緩存起來)。

?

這里的組件名稱指的是:

不寫include時:包含的所有路由組件全部緩存。
如何指定多個緩存路由,可以使用數組形式:

6.12 activated和deactivated
這是兩個生命周期鉤子函數。
只有“路由組件”才有的兩個生命周期鉤子函數。
路由組件被切換到的時候,activated被調用。
路由組件被切走的時候,deactivated被調用。
這兩個鉤子函數的作用是捕獲路由組件的激活狀態。
6.13 路由守衛
6.13.1 全局前置守衛
router/index.js文件中拿到router對象。
router.beforeEach((to, from, next)=>{ // 翻譯為:每次前(寓意:每一次切換路由之前執行。)
// to 去哪里(to.path、to.name)
// from 從哪來
// next 繼續:調用next( )
})

這種路由守衛稱為全局前置路由守衛。
初始化時執行一次,以后每一次切換路由之前調用一次。

如果路由組件較多。to.path會比較繁瑣,可以考慮給需要鑒權的路由擴展一個布爾值屬性,可以通過路由元來定義屬性:meta:{isAuth : true}

6.13.2 全局后置守衛
router/index.js文件中拿到router對象。
router.afterEach((to, from)=>{ // 翻譯為:每次后(寓意:每一次切換路由后執行。)
// 沒有 next
document.title = to.meta.title // 通常使用后置守衛完成路由切換時title的切換。
})
這種路由守衛稱為全局后置路由守衛。
初始化時執行一次,以后每一次切換路由之后調用一次。
該功能也可以使用前置守衛實現:

該功能使用后置守衛實現更好:

解決閃爍問題:

6.13.3 局部路由守衛之path守衛

注意:沒有afterEnter
6.13.4 局部路由守衛之component守衛

注意:只有路由組件才有這兩個鉤子。
6.13.5 前端項目上線

  1. 路徑中#后面的路徑稱為hash。這個hash不會作為路徑的一部分發送給服務器:
    (1) http://localhost:8080/vue/bugs/#/a/b/c/d/e (真實請求的路徑是:http://localhost:8080/vue/bugs)
  1. 路由的兩種路徑模式:
    (1) hash模式
    (2) history模式
  1. 默認是hash模式,如何開啟history模式
    (1) router/index.js文件中,在創建路由器對象router時添加一個mode配置項:
  1. 項目打包
    (1) 將Xxx.vue全部編譯打包為HTML CSS JS文件。
    (2) npm run build
  1. 這里服務器端選擇使用Java服務器:Tomcat,接下來教大家配置Tomcat服務器:
    (1) 下載JDK





    (2) 安裝JDK



    (3) 配置環境變量:JAVA_HOME





    ⑥ 注意:如果你安裝的路徑和我安裝的不一樣,只要能夠找到JDK bin目錄的上一級目錄即可。
  1. (4) 下載Tomcat



    (5) 安裝Tomcat
    ① 解壓就是安裝。我這里直接解壓到C盤的根目錄下
  1. (6) 配置環境變量:CATALINA_HOME


    (7) 配置環境變量:PATH



    (8) 啟動Tomcat
    ① 打開dos命令窗口,輸入startup.bat,看到以下窗口表示tomcat啟動成功(注意Tomcat服務器的端口號是8080,啟動Tomcat服務時最好先關閉vue腳手架,因為vue cli使用的端口也是8080,如果啟動了Tomcat服務器,再啟動vue腳手架的話,腳手架會另外開啟一個其他的端口。)
  1. 如果你啟動tomcat控制臺有亂碼也無所謂,如果實在看不下去,可以修改以下配置文件內容:
    a.
    b.
    (9) 關閉Tomcat
    ① ctrl + c 或者:dos命令窗口中輸入:shutdown.bat
    (10) 將前端項目部署到Tomcat。
    ① 找到tomcat服務器的webapps目錄,并找到webapps目錄下的ROOT目錄,清空ROOT目錄
    ② 將前端項目直接拷貝放到ROOT目錄下即可。
    (11) 訪問項目。
    ① 重啟tomcat,并訪問。
    ② http://localhost:8080


6. hash模式和history模式的區別與選擇
(1) hash模式
① 路徑中帶有#,不美觀。
② 兼容性好,低版本瀏覽器也能用。
③ 項目上線刷新地址不會出現404。
④ 第三方app校驗嚴格,可能會導致地址失效。
(2) history模式
① 路徑中沒有#,美觀。
② 兼容性稍微差一些。
③ 項目上線后,刷新地址的話會出現404問題。需要后端人員配合可以解決該問題。
7. 對于tomcat服務器來說,如何解決history帶來的404問題,這需要后端人員寫一段代碼:
(1) 在ROOT目錄下新建WEB-INF目錄。
(2) 在WEB-INF目錄下新建web.xml文件。
(3) 在web.xml文件中做如下配置:
?

404/index.html 7. Vue37.1 了解Vue31. vue3官網地址 (1) https://cn.vuejs.org/ 2. vue3發布時間 (1) 2020年9月18日。 翻譯: 今天,我們很自豪地宣布Vue.js 3.0“海賊王”正式發布。這個新的主要版本的框架提供了改進的性能、更小的捆綁包大小、更好的TypeScript集成、用于處理大規模用例的新API,以及為框架未來的長期迭代奠定了堅實的基礎。 3.0版本代表了兩年多的開發工作,包括30多個RFC、2600多個提交、來自99個貢獻者的628個拉取請求,以及核心回購之外的大量開發和文檔工作。我們要向我們的團隊成員表示最深切的感謝,感謝他們接受了這一挑戰,感謝我們提出的撤回請求,感謝我們的贊助商和支持者提供的財政支持,感謝廣大社區參與我們的設計討論并為預發布版本提供反饋。Vue是一個為社區創建并由社區支持的獨立項目,如果沒有您的持續支持,Vue 3.0是不可能實現的。 3. 版本迭代歷史 (1) https://github.com/vuejs/core/releases 4. vue3做了哪些改動 (1) 最核心的虛擬DOM算法進行了重寫。 (2) 支持tree shaking:在前端的性能優化中,es6 推出了tree shaking機制,tree shaking就是當我們在項目中引入其他模塊時,他會自動將我們用不到的代碼,或者永遠不會執行的代碼搖掉 (3) 最核心的響應式由Object.defineProperty修改為Proxy實現。 (4) 更好的支持TS(Type Script:TypeScript是微軟開發的一個開源的編程語言,通過在JavaScript的基礎上添加靜態類型定義構建而成。TypeScript通過TypeScript編譯器或Babel轉譯為JavaScript代碼,可運行在任何瀏覽器,任何操作系統。) (5) ...... 5. vue3比vue2好在哪 (1) (2) 翻譯: ① 與Vue 2相比,Vue 3在捆綁包大小(通過樹抖動可輕41%)、初始渲染(快55%)、更新(快133%)和內存使用(少54%)方面都有了顯著的性能改進。 6. Vue3的新特性 (1) 新的生命周期鉤子 ① (2) 鍵盤事件不再支持keyCode。例如:v-on:keyup.enter支持,v-on:keyup.13不支持。 (3) 組合式API(Composition API) ① (4) 新增了一些內置組件 ① (5) data必須是一個函數。 (6) ...... 7.2 Vue3工程的創建 7.2.1 vue-cli創建Vue3工程 1. 創建Vue3版本的工程,要求vue-cli最低版本4.5.0 (1) (2) 可以使用以下命令升級你的腳手架版本 ① npm install -g @vue-cli 2. 創建Vue3工程 (1) vue create vue3_pro 3. 啟動工程 (1) 切換到工程根目錄。 (2) npm run serve 7.2.2 vue-cli創建的vue3項目說明 1. 目錄結構以及文件和vue2相同。 2. main.js文件說明 3. 查看App.vue組件 vue3中template標簽下可以有多個根標簽了。 7.2.3 create-vue創建Vue3工程1. create-vue是什么? (1) 和vue-cli一樣,也是一個腳手架。 (2) vue-cli創建的是webpack+vue項目的腳手架工具。 (3) create-vue創建的是vite+vue項目的腳手架工具。 (4) webpack和vite都是前端的構建工具。 2. vite官網 (1) https://vitejs.cn/ 3. vite是什么?(vite被翻譯為:快) (1) vite是一個構建工具,作者尤雨溪。 (2) 前端構建工具有哪些? 4. vite和傳統構建工具的區別? (1) https://cn.vitejs.dev/guide/why.html 官方的說辭。 (2) 使用vite后,至少兩方面是提升了: ① 服務器啟動速度快。 ② 更新速度快了。 5. 使用create vue創建Vue3工程 (1) 官方指導:快速上手 | Vue.js (2) 安裝 create-vue腳手架并創建vue3項目:npm init vue@latest 執行時,如果檢測到沒有安裝create-vue腳手架時會安裝腳手架。如果檢測到已經安裝過腳手架,則直接創建項目。 一路選擇No: (3) cd vue3_pro (4) npm install (5) npm run dev (6) 訪問5173端口 7.2.4 create-vue創建的vue3工程說明1. 目錄結構 2. 和vue-cli腳手架創建的區別 (1) index.html文件不再放到public目錄下了。 ① vite官方的解釋是:讓index.html成為入口。(vue-cli腳手架生成的項目入口是:main.js) (2) vue-cli的配置文件vue.config.js。create-vue腳手架的配置文件:vite.config.js ① vite.config.js 能配置什么?可以參考vite官網:https://cn.vitejs.dev/config/ 例如配置代理服務器和以前就不太一樣了。 7.3 Proxy實現原理 Vue2的響應式核心:Object.defineProperty Vue3的響應式核心:Proxy 7.3.1 Object.defineProperty get做數據代理。set做數據劫持。在set方法中修改數據,并且渲染頁面,完成響應式。 Object.defineProperty(data, ‘name’, { get(){ // 數據代理 }, set(val){ // 數據劫持 } }) 存在的問題:1) 通過數組下標修改數據,這個操作是沒有響應式的。 2) 后期給對象新增屬性、刪除屬性,這些操作都是沒有響應式的。 導致以上問題最根本原因是:Object.defineProperty只能捕獲到讀和修改。 Vue2中怎么解決以上問題? 1) 對于數組來說調用數組的相關API。例如:push、shift、unshift.... 2) 新增屬性、刪除屬性等操作通過:Vue.set或者this.$set 7.3.2 Proxy Proxy是ES6新增特性。window.Proxy Proxy是一個構造函數,參數傳遞一個目標對象,可以為目標對象生成代理對象。 通過訪問代理對象上的屬性來間接訪問目標對象上的屬性。 訪問代理對象上的屬性時,讀屬性、修改屬性、刪除屬性、新增屬性。Proxy都可以捕獲到。 Vue3框架底層實際上使用了ES6的Reflect對象來完成對象屬性的訪問: 7.4 setup1. setup是一個函數,vue3中新增的配置項。 2. 組件中所用到的data、methods、computed、watch、生命周期鉤子....等,都要配置到setup中。 3. setup函數的返回值: (1) 返回一個對象,該對象的屬性、方法等均可以在模板語法中使用,例如插值語法。 (2) 返回一個渲染函數,從而執行渲染函數,渲染頁面。 ① import {h} from ‘vue’ (引入渲染函數) ② return ()=>{h(‘h2’, ‘歡迎大家學習Vue3’)} 4. vue3中可以編寫vue2語法,向下兼容的。但是不建議。更不建議混用。 5. setup中的this是undefined。所以setup中this是不可用的。 7.5 ref函數(實現響應式) 7.5.1 簡單數據的響應式 1. ref是一個函數。完成響應式的。 2. ref使用時需要import (1) import {ref} from ‘vue’ 3. 凡是要實現響應式的data,需要使用ref函數進行包裹 (1) let name = ref(‘李四’) (2) 輸出name,你會發現,它是RefImpl對象(引用實現的實例對象,簡稱引用對象),在這個引用對象當中有一個value屬性,value屬性的實現原理是:Object.defineProperty,通過它實現了響應式處理。 4. 修改數據的話必須這樣做:name.value = ‘王五’ 7.5.2 對象數據的響應式 重點:如果ref函數中是一個對象,例如:ref({}),底層會為這個對象{}生成一個Proxy代理對象。通過這個代理對象完成了響應式。 如果代碼是這樣寫,如下,實際上沒有用到Proxy,還是使用了Object.defineProperty完成的響應式: 如果代碼是這樣寫,如下,就是使用了Proxy完成的響應式: 7.6 Vue3中專門為對象做響應式的核心函數:reactive 以上代碼中的Proxy是怎么創建的?底層是通過調用reactive函數來完成的。 當然這個函數我們也可以自己使用。 注意:這個函數不能為簡單類型服務,只能為對象類型服務。 注意:reactive為響應式做了遞歸處理。對象中的對象中的對象的屬性,也是響應式的。 重點1: 并且數組如果使用reactive函數包裹的話,數組中的數據,在通過下標去修改的時候,也是支持響應式的。(這個在Vue2中是不支持的。) 重點2: 在開發中一般很少使用ref,一般會使用reactive。即使是簡單類型的數據,也會將其存放到一個對象中,使用reactive函數進行包括。 7.7 Vue3中的props 給組件User傳數據 User組件使用props接收數據 在setup函數中如何使用props? setup的第一個參數就是props。可以直接拿來用。 7.8 Vue3的生命周期 vue2的生命周期圖: vue3的生命周期圖: 1. vue2中 (1) beforeDestroy (2) destroyed 2. vue3中 (1) beforeUnmount (2) unmounted 3. vue3中仍然可以使用配置項方式提供生命周期鉤子函數。但也可以使用組合式API方式。如果采用組合式API方式,API名稱規律如下: (1) beforeCreate => setup (2) created => setup (3) beforeMount => onBeforeMount (4) mounted => onMounted (5) beforeUpdate => onBeforeUpdate (6) updated => onUpdated (7) beforeUnmount => onBeforeUnmount (8) unmounted => onUnmounted 4. 當然如果需要使用beforeCreate和created鉤子,可以采用vue2的語法:配置項形式。vue2和vue3語法可以共存,但不建議。 7.9 Vue3中的自定義事件 綁定事件 觸發事件 7.10 Vue3的全局事件總線1. 安裝mitt (1) npm i mitt 2. 封裝event-bus.js文件 (1) (2) 3. 綁定事件 4. 觸發事件 5. 移除所有事件和指定事件 7.11 vue3的計算屬性 import {computed} from ‘vue’ //computed是一個組合式的API。 setup(){ // 簡寫 let reversedName = computed(()=>{ }) // 完整寫法 let reversedName = computed({ get(){}, set(value){} }) } 計算屬性最重要的特征是:只要計算屬性關聯的數據發生變化,計算屬性的回調函數就會執行。所以計算屬性關聯的數據必須是具有響應式的。 7.12 自定義hook函數 將組合式API拿出來,封裝成一個函數,在需要復用的位置,使用這個hook函數。 一般創建一個hooks目錄,在hooks目錄當中放hook函數。 代碼復用。 在需要使用的位置導入: 7.13 vue3的監視屬性 import {watch} from ‘vue’ //watch就是組合式API。 1. 監視ref定義的一個響應式數據 (1) watch(數據, (newValue, oldValue)=>{}) 2. 監視ref定義的多個響應式數據 (1) watch(數據1, (newValue, oldValue)=>{}) (2) watch(數據2, (newValue, oldValue)=>{}) 或者 (3) watch([數據1,數據2], (newValue, oldValue)=>{}) 3. immediate在哪里寫? (1) 在watch的第三個參數位置上使用一個對象:{immediate : true, deep:true} 4. 監視reactive定義的響應式數據。注意:oldValue拿不到 (1) watch(數據, (newValue, oldValue)=>{}) 5. 如果監視的reactive定義的響應式數據。強制開啟了深度監視,配置deep無效。默認就是監視對象的全部屬性。 6. 如果要監視對象中的某個屬性怎么辦?被監視的數據必須是一個函數,將要監視的數據返回 (1) watch(()=>user.age, (newValue,oldValue)=>{}) 7. 如果要監視對象中的某些屬性怎么辦? (1) watch([()=>user.age, ()=>user.name], (newValue,oldValue)=>{}) 8. 如果要監視reactive定義的響應式數據中的某個屬性,這個屬性是一個對象形式,那么開啟deep:true是可以的。 (1) watch(()=>user.address, (newVal, oldVal)=>{}) 9. 關于ref包裹對象時的value (1) watch(name.value, (newVal, oldVal){}) //監視無效 (2) watch(user.value, (newVal, oldVal)=>{})//監視有效 (3) watch(user,(newVal, oldVal)=>{}, {deep:true}) // 深度監視生效 7.14 watchEffect函數1. watchEffect函數里面直接寫一個回調 (1) 回調中用到哪個屬性,當這個屬性發生變化的時候,這個回調就會重新執行。 (2) watchEffect(()=>{const a = data.counter1; const b = data.counter2 邏輯代碼......}) 當counter1和counter2被修改后,這個回調就會執行。 2. computed和watchEffect的區別? (1) computed注重返回結果。 (2) watchEffect注重邏輯處理。 3. 示例代碼 7.15 shallowReactive和shallowRef 淺層次的響應式。 shallowReactive:對象的第一層支持響應式,第二層就不再支持了。 shallowRef:只給基本數據類型添加響應式。如果是對象,則不會支持響應式。 以下是演示shallowRef 以下是演示shallowReactive 7.16 組合式API和選項式API對比 組合式API:Composition API 選項式API:Options API 選項式API:特點是 分散。 組合式API:特點是 集中 (一個hook是一個獨立的功能,一個hook中有自己的data、methods、computed、watch) 7.17 深只讀與淺只讀 組合式API:readonly與shallowReadonly 應用場景:其它組件傳遞過來的數據,如果不希望你修改,你最好加上只讀,以防以后不小心改了人家的數據。 深只讀: 淺只讀: 7.18 響應式數據的判斷 isRef:檢查某個值是否為 ref。 isReactive:檢查一個對象是否是由 reactive() 或 shallowReactive() 創建的代理。 isProxy:檢查一個對象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 創建的代理。 isReadonly:檢查傳入的值是否為只讀對象。 7.19 toRef和toRefs 通過以上寫法,可以看到模板語法中都有“data.”,這個是可以改善的。 大家可以看一下,下面的這個改善是否可以? 我們發現這樣修改是不行的。丟失了響應式。什么原因?主要原因是因為以上的這種寫法等同于: 顯然這種寫法和響應式對象data無關了。 再修改為以下這樣行不行? 我們發現功能實現了。但是存在的問題是:data不會變。例如: 怎么解決這個問題:toRef,它可以讓數據具有響應式,并且修改toRef生成的對象時,還能關聯更新data: 測試結果: 還有一個更簡單的:toRefs 運行結果: 7.20 轉換為原始&標記為原始 toRaw與markRaw toRaw:將響應式對象轉換為普通對象。只適用于reactive生成的響應式對象。 markRaw:標記某個對象,讓這個對象永遠都不具備響應式。比如在集成一些第三方庫的時候,比如有一個巨大的只讀列表,不讓其具備響應式是一種性能優化。 toRaw: markRaw: 7.21 Fragment組件 fragment翻譯為:碎片。片段。 在Vue2中每個組件必須有一個根標簽。這樣性能方面稍微有點問題,如果每一個組件必須有根標簽,組件嵌套組件的時候,有很多無用的根標簽。 在Vue3中每個組件不需要有根標簽。實際上內部實現的時候,最終將所有組件嵌套好之后,最外層會添加一個Fragment,用這個fragment當做根標簽。這是一種性能優化策略。 7.22 Teleport組件 teleport翻譯為:遠距離傳送。用于設置組件的顯示位置。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917771.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917771.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917771.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【OpenGL】LearnOpenGL學習筆記02 - 繪制三角形、矩形

上接: https://blog.csdn.net/weixin_44506615/article/details/149861824 完整代碼&#xff1a;https://gitee.com/Duo1J/learn-open-gl 一、渲染管線 在開始之前&#xff0c;我們先簡單了解一下圖形渲染管線 在渲染3D物體時&#xff0c;我們常用到的一種幾何結構為網格模型…

Mysql的事務是什么?

簡單來說&#xff0c;MySQL 實現事務的核心就像是給你的數據庫操作加了一套“保險和存檔”機制。它確保了你的操作要么全部成功&#xff0c;要么全部失敗&#xff0c;并且在面對多人同時操作、系統突然崩潰等情況時&#xff0c;數據依然可靠、準確。 為什么需要事務呢&#xff…

測試開發:Python+Django實現接口測試工具

【測試開發天花板】DjangoVuePyTest打造企業級自動化平臺&#xff5c;能寫進簡歷的硬核項目最近被幾個公司實習生整自閉了&#xff0c;沒有基礎&#xff0c;想學自動化又不知道怎么去學&#xff0c;沒有方向沒有頭緒&#xff0c;說白了其實就是學習過程中沒有成就感&#xff0c…

TFS-2022《A Novel Data-Driven Approach to Autonomous Fuzzy Clustering》

核心思想 這篇論文的核心思想是提出一種全新的、數據驅動的自主模糊聚類&#xff08;Autonomous Fuzzy Clustering, AFC&#xff09;算法。其核心創新在于&#xff0c;它巧妙地結合了模糊聚類的靈活性和基于中位數&#xff08;medoids&#xff09;聚類的可解釋性&#xff0c;并…

ELK是什么

ELK 是一個廣受歡迎的開源技術棧&#xff0c;用于實時采集、處理、存儲、搜索、分析和可視化海量的日志數據&#xff08;log&#xff09;和機器生成的數據&#xff08;machine data&#xff09;&#xff0c;尤其是在 IT 系統監控、應用故障排查、安全分析和業務智能等領域應用廣…

[硬件電路-123]:模擬電路 - 信號處理電路 - 常見的高速運放芯片、典型電路、電路實施注意事項

一、高速運放常見芯片型號及特性高速運放&#xff08;高速運算放大器&#xff09;通常指帶寬&#xff08;GBW&#xff09;超過10MHz、壓擺率&#xff08;SR&#xff09;高于10V/μs的器件&#xff0c;適用于視頻處理、通信系統、高速數據采集等場景。以下是典型芯片及其特性&am…

關于解決WinRiver項目動態XmlElement的序列化與反序列化的問題

關于解決WinRiver項目動態XmlElement的序列化與反序列化的問題 一、WinRiver項目流量匯總XML內容 1.1、索引可變,索引下 XmlElement 元素內容固定 1.2、如何將對象 BottomTrack 的動態內容序列化為 XML ? 1.3、如何將 XML 動態內容反序列化為對象 BottomTrack ? 二、XML 動態…

【力扣 Hot100】 刷題日記

D3 128.最長連續序列 錯解 class Solution {public int longestConsecutive(int[] nums) {Arrays.sort(nums);int maxCnt 0;int cnt 0;for (int i 0; i < nums.length - 1; i) {if(nums[i] ! nums[i 1] - 1){//如果不連續&#xff0c;取cnt與maxCnt較大值&#xff0c…

飛算JavaAI編程插件:以AI之力賦能Java開發,讓編碼效率再升級

你是否希望自己敲代碼的時候總有一位大佬在你背后幫你保駕護航。想象一下&#xff0c;當你對著Java編輯器敲代碼時&#xff0c;身后站了位“隱形大神”——你剛敲出for&#xff0c;它就預判到你要遍歷集合&#xff0c;自動補全帶泛型的循環邏輯&#xff1b;你手滑把equals寫成&…

機器學習通關秘籍|Day 03:決策樹、隨機森林與線性回歸

目錄 一、決策樹 1、概念 2、基于信息增益的決策樹的建立 &#xff08;1&#xff09;信息熵 &#xff08;2&#xff09;信息增益 &#xff08;3&#xff09;信息增益決策樹建立步驟 3、基于基尼指數的決策樹的建立 4、API 二、隨機森林 1、算法原理 2、API 三、線性…

C++進階—C++的類型轉換

第一章&#xff1a;C語言中的類型轉換在C語言中&#xff0c;如果賦值運算符左右兩側類型不同&#xff0c;或者形參與實參類型不匹配&#xff0c;或者返回值類型與接收返回值類型不一致時&#xff0c;就需要發生類型轉化&#xff0c;C語言中總共有兩種形式的類型轉換&#xff1a…

基于Flask的微博話題多標簽情感分析系統設計

基于Flask的微博話題情感分析系統設計與實現 一、項目概述 本項目是一個輕量化的微博話題情感分析系統&#xff0c;通過Flask框架整合情感分析模型&#xff0c;實現對微博話題及評論的情感標簽識別與結果展示。系統面向普通用戶和研究者&#xff0c;提供簡單易用的操作界面&…

TDengine 中 TDgpt 的模型評估工具

模型評估工具 TDgpt 在企業版中提供預測分析模型和異常檢測模型有效性評估工具 analytics_compare&#xff0c;該工具能夠使用 TDengine 中的時序數據作為 回測依據&#xff0c;評估不同預測模型或訓練模型的有效性。該工具在開源版本中不可用使用評估工具&#xff0c;需要在其…

【DL學習筆記】DataLoader類功能和參數說明

文章目錄一、Dataset 與 DataLoader 功能介紹抽象類Dataset的作用DataLoader 作用兩者關系二、torch.utils.data.DataLoader代碼示例常用參數圖示num_workers設置多少合適數據加載子進程如何并行的pin_memorysampler兩種sampler順序采樣 SequentialSampler隨機采樣 RandomSampl…

JVM中年輕代、老年代、永久代(或元空間)、Eden區和Survivor區概念介紹

在Java虛擬機&#xff08;JVM&#xff09;中&#xff0c;內存管理是自動化的&#xff0c;這主要通過垃圾回收機制實現。JVM將堆內存劃分為不同的區域&#xff0c;以便更高效地管理和回收對象。以下是關于年輕代、老年代、永久代&#xff08;或元空間&#xff09;、Eden區和Surv…

譯 | BBC Studios團隊:貝葉斯合成控制方法SCM的應用案例

來自上傳文件中的文章《Using Causal Inference for Measuring Marketing Impact: How BBC Studios Utilises Geo Holdouts and CausalPy》 本篇介紹了在傳統A/B測試不適用時&#xff0c;如何利用貝葉斯合成控制方法和地理區域保留來評估營銷活動效果。其亮點在于通過構建“反事…

Web開發-PHP應用TP框架MVC模型路由訪問模版渲染安全寫法版本漏洞

我們先使用/index.php/index/index/test&#xff0c;就是圖中的test()方法 /index.php/index/index/index&#xff0c;這個回顯就是111 http://127.0.0.1:83/index.php/index/index/test2?x123456 public function test2() {$x$_GET[x];return $x; } 這里再做一個案例更詳細一…

FreeRTOS列表系統深度解析

FreeRTOS列表系統深度解析 一、核心數據結構 1. 列表控制塊 (List_t) typedef struct xLIST {volatile UBaseType_t uxNumberOfItems; // 當前列表項數量ListItem_t * pxIndex; // 遍歷指針&#xff08;用于輪詢調度&#xff09;MiniListItem_t xListEnd; …

《Linux編譯器:gcc/g++食用指南》

堅持用 清晰易懂的圖解 代碼語言&#xff0c;讓每個知識點變得簡單&#xff01; &#x1f680;呆頭個人主頁詳情 &#x1f331; 呆頭個人Gitee代碼倉庫 &#x1f4cc; 呆頭詳細專欄系列 座右銘&#xff1a; “不患無位&#xff0c;患所以立。” 《Linux編譯器&#xff1a;GCC…

SparkKV轉換算子實戰解析

目錄 KV算子 parallelizePairs mapToPair mapValues groupByKey reduceByKey sortByKey 算子應用理解 reduceByKey和groupByKey的區別 groupByKeymapValues實現KV數據的V的操作 改進用reduceByKey groupby通過K和通過V分組的模板代碼 問題集錦 寶貴的經驗 這里會…