[TOC] #### 1. Vue 框架介紹 --- **一、什么是 Vue ?** Vue 官網(wǎng): [https://cn.vuejs.org](https://cn.vuejs.org) Vue 是一款用于構(gòu)建用戶界面的 JavaScript 框架,它基于 HTML,CSS 和 JavaScript 構(gòu)建,并提供了一套聲明式的、組件化的編程模型,幫助你高效的開發(fā)用戶界面 **二、Vue 是漸進(jìn)式框架 ?** 以前我們使用 html,css,js 開發(fā)項(xiàng)目,當(dāng)項(xiàng)目比較大,比較復(fù)雜的話,使用 js 來寫的話,是沒有問題的,但是會(huì)比較困難,任務(wù)量比較大。所以呢,出現(xiàn)了 Vue 這個(gè)框架,來幫助我們開發(fā)項(xiàng)目更加簡單,更加的方便。 假設(shè)以前我們使用的 js 開發(fā)的項(xiàng)目,現(xiàn)在想要使用 vue 進(jìn)行重構(gòu),如果一下子將項(xiàng)目改為 vue,工作量是非常大的。項(xiàng)目中有很多頁面,我們可以先在某些頁面中引入 vue,一點(diǎn)一點(diǎn)的使用 vue 重構(gòu),這就是 **漸進(jìn)式** 的概念 #### 2. Vue3 安裝方式 --- **一、通過 CDN 使用 Vue** 官方文檔: [https://cn.vuejs.org/guide/quick-start.html#using-vue-from-cdn](https://cn.vuejs.org/guide/quick-start.html#using-vue-from-cdn) 通過 CDN 使用 Vue 時(shí),不涉及“構(gòu)建步驟”。這使得設(shè)置更加簡單,并且可以用于增強(qiáng)靜態(tài)的 HTML 或與后端框架集成。但是,你將無法使用單文件組件 (SFC) 語法 ```html <!-- 借助 script 標(biāo)簽直接通過 CDN 來使用 Vue --> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> ``` 使用示例: 使用 `Vue.createApp()` 創(chuàng)建應(yīng)用,并且通過 `mount()` 掛載到 `#app` 上 ```html <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <div id="app">{{ message }}</div> <script> Vue.createApp({ data() { return { message: 'Hello Vue !' } } }).mount('#app') </script> ``` **二、使用 Vite 構(gòu)建 Vue 項(xiàng)目** 使用前提: 已安裝 16.0 或更高版本的 [Node.js](https://nodejs.org) 官方文檔 : [https://cn.vuejs.org/guide/quick-start.html#creating-a-vue-application](https://cn.vuejs.org/guide/quick-start.html#creating-a-vue-application) [Vite](https://vitejs.dev) 是一個(gè) web 開發(fā)構(gòu)建工具,類似于 webpack,比 webpack 更快,以閃電般的速度啟動(dòng)和編譯 在命令行中執(zhí)行以下命令: ``` # project-name 項(xiàng)目名稱, 可省略 npm init vue@latest <project-name> ``` 將會(huì)按照并執(zhí)行 [create-vue](https://github.com/vuejs/create-vue) ,它是 Vue 官方的項(xiàng)目腳手架工具,執(zhí)行中有交互操作,提示安裝一些依賴,一路回車即可 ![](https://img.itqaq.com/art/content/e63c7c625976473b40289a99ee250612.png) #### 3. Vue3 模板語法 --- Vue 使用一種基于 HTML 的模板語法,使我們能夠聲明式地將其組件實(shí)例的數(shù)據(jù)綁定到呈現(xiàn)的 DOM 上。Vue 會(huì)將模板編譯成高度優(yōu)化的 JavaScript 代碼。結(jié)合響應(yīng)式系統(tǒng),當(dāng)應(yīng)用狀態(tài)變更時(shí),Vue 能夠智能地推導(dǎo)出需要重新渲染的組件的最少數(shù)量,并應(yīng)用最少的 DOM 操作 **文本插值** 文本插值是最基本的數(shù)據(jù)綁定形式,使用的是 Mustache 語法(即雙大括號),是開發(fā)中使用最頻繁的模板語法之一 ```html <span>Message: {{ msg }}</span> ``` **原始 HTML** 文本插值會(huì)將數(shù)據(jù)渲染為純文本,所以數(shù)據(jù)中即使有 html 標(biāo)簽也不會(huì)被解析。若想插入 html,需要使用 `v-html` 指令: 這里遇到了一個(gè)新的概念: 指令,`v-html` 屬性被稱為一個(gè)指令。在 vue 中,以 `v-` 作為前綴的屬性,稱為 vue 的指令,表明它們是一些由 vue 提供的特殊屬性 ```html <div v-html="msg"></div> ``` **屬性綁定** 雙大括號不能在 HTML 屬性中使用,想要響應(yīng)式的綁定一個(gè)屬性,應(yīng)該使用 `v-bind` 指令: `v-bind` 指令將元素的 id 屬性和組件的 uid 屬性保持一致。綁定的值是 null 或 undefined,該屬性將會(huì)從渲染的元素上移除 ```html <div v-bind:id="uid"></div> ``` 因?yàn)?`v-bind` 在開發(fā)中使用非常頻繁,所以 Vue 官方提供了簡寫語法: ```html <div :id="uid"></div> ``` **布爾型屬性** 布爾型屬性根據(jù) `true/false` 值來決定屬性是否應(yīng)該存在于該元素上 當(dāng) isDisabled 的值為真值或空字符串時(shí),元素會(huì)包含 disabled 屬性 需要特別注意的是值為空字符串時(shí) disabled 屬性也存在,其他假值則 disabled 屬性不存在 ```html <button :disabled="isDisabled">Button</button> ``` **動(dòng)態(tài)綁定多個(gè)值** 如果有一個(gè)這樣包含多個(gè)屬性的 JS 對象 ```javascript const objectOfAttrs = { id: 'container', class: 'wrapper' } ``` 通過不帶參數(shù)的 `v-bind`,可以將它們綁定到單個(gè)元素上 ```html <div v-bind="objectOfAttrs"></div> ``` 頁面渲染后 Vue 將多個(gè)屬性添加到了元素上: ```html <div id="container" class="wrapper"></div> ``` **使用 JavaScript 表達(dá)式** Vue 數(shù)據(jù)綁定中都支持完整的 JavaScript 表達(dá)式 在 Vue 模板中,表達(dá)式可以被使用在 `文本插值(雙大括號)` 和 `任何 Vue 指令屬性(以v-開頭的特殊屬性)` 的值中 ``` {{ number + 10 }} {{ ok ? 'yes' : 'no' }} {{ array.join(',') }} <div :id="`list-${uid}`"></div> ``` **調(diào)用函數(shù)** 可以在表達(dá)式中使用組件暴露的方法 ```html <span :title="toTitleDate(date)"> {{ formatDate(date) }} </span> ``` **指令 Directives** Vue 指令是帶有 `v-` 前綴的特殊屬性。 Vue 提供了很多[內(nèi)置指令](https://cn.vuejs.org/api/built-in-directives.html) ,包含上面提到的 `v-bind` 和 `v-html` #### 4. 組件的 data 屬性 --- 組件的 data 選項(xiàng)必須是一個(gè)函數(shù),它的返回值必須是一個(gè)對象 Vue 在創(chuàng)建新組件實(shí)例的過程中調(diào)用此函數(shù),通過響應(yīng)式系統(tǒng)將其包裹起來 #### 5. 計(jì)算屬性和方法 --- **計(jì)算屬性 computed** 模板中的表達(dá)式雖然方便,但也只能用來做簡單的操作。如果在模板中寫太多邏輯,會(huì)讓模板變得臃腫,難以維護(hù)。此時(shí)可以使用**計(jì)算屬性**描述依賴響應(yīng)式狀態(tài)的復(fù)雜邏輯 ```javascript export default { data() { return { users: [{ id: 1, name: 'html' },{ id: 2, name: 'css' }] } }, computed: { getUsersName() { return this.users.reduce((total, item) => total += item.name, '') } } } ``` **方法 methods** 通過組件的 `methods` 選項(xiàng)向組件實(shí)例添加方法,它是一個(gè)包含所需方法的對象,在對象中定義方法 需要注意的是 methods 中的方法不要定義為剪頭函數(shù),因?yàn)榧^函數(shù)中沒有 this。如果是普通函數(shù),Vue 自動(dòng)為 methods 綁定 this,并且 this 始終指向 vue 實(shí)例 ```javascript export default { data() { return { count: 1, } }, methods: { add() { this.count++ }, sub(num) { this.count -= num } } } ``` #### 6. 偵聽器的使用 --- 在有些情況下,我們需要在狀態(tài)變化后執(zhí)行一些操作,例如: 更改 DOM,或根據(jù)異步操作的結(jié)果去修改另一處的狀態(tài) 在選項(xiàng)式 API 中,我們可以使用 `watch()` 選項(xiàng)監(jiān)聽響應(yīng)式數(shù)據(jù),發(fā)生變化時(shí)觸發(fā)一個(gè)函數(shù) ```javascript export default { data() { return { msg: "你好嗎?", count: 0, user: { name: "liang", age: 18, gender: 1, }, }; }, created() { setTimeout(() => { this.count = 10; }, 1000); }, // 監(jiān)聽數(shù)據(jù)的變化 watch: { // 當(dāng)msg發(fā)生變化時(shí),觸發(fā)這個(gè)函數(shù) // newVal,oldVal 修改前和修改后的值 msg(newVal, oldVal) { console.log("msg:", { newVal, oldVal }); // 可以執(zhí)行異步操作,或復(fù)雜代碼 // 實(shí)際開發(fā)中經(jīng)常在 watch 中調(diào)用 methods 方法 this.showMsg(); }, // 即時(shí)回調(diào)的偵聽器 count: { // 初始化的時(shí)候調(diào)用 immediate: true, // oldVal 是可選參數(shù),可省略不寫 handler(newVal) { console.log("count:", { newVal }); }, }, // 深層偵聽器(監(jiān)聽對象中的所有屬性) // user: { // // 深度偵聽 // // 深度偵聽會(huì)一層層的向下遍歷,給每個(gè)對象屬性都加上偵聽器 // deep: true, // handler(newVal) { // console.log("user: ", { newVal }); // }, // }, // 深層偵聽器(監(jiān)聽對象中的某個(gè)屬性) // 使用字符串的形式進(jìn)行優(yōu)化,只會(huì)單獨(dú)監(jiān)聽對象中對應(yīng)的屬性 "user.name": { deep: true, handler(newVal) { console.log("user.name: ", { newVal }); }, }, }, methods: { showMsg() { return this.msg; // how are you ? }, changeCount() { setTimeout(() => { this.count += 10; }, 1000); }, }, }; ``` #### 7. class 類名綁定對象 --- class 值 active 是否存在,取決于組件的 data 選項(xiàng)中 isActive 的真假值 ```html <div :class="{ active: isActive }">liang</div> ``` 可以在對象中寫多個(gè)字段來操作多個(gè) class。補(bǔ)充: 當(dāng) class 的名稱不是 js 合法屬性名時(shí),需要使用引號包裹 ```html <div :class="{ active: isActive, 'text-danger': hasError }">liang</div> ``` 綁定的對象并不一定需要寫成內(nèi)聯(lián)字面量的形式,也可以直接綁定一個(gè)對象: ```javascript data() { return { custom: { nav: true, 'text-success': true } } } ``` ```html <!-- 模板語法 --> <div :class="custom">liang</div> <!-- 頁面渲染 --> <div class="nav text-success">liang</div> ``` 也可以綁定一個(gè)返回對象的計(jì)算屬性,這是項(xiàng)目開發(fā)中很常見且很有用的技巧 ```javascript data() { return { isActive: true, error: null } }, computed: { classObject() { return { active: this.isActive && !this.error, 'text-danger': this.error && this.error.type === 'fatal' } } } ``` ```html <div :class="classObject"></div> ``` 如果本身有 class 屬性,又動(dòng)態(tài)綁定了 class 屬性也是可以的,那么 vue 會(huì)將動(dòng)態(tài)綁定的和本身的合并 ```html <div class="nav" :class="classObject">liang</div> ``` #### 8. class 類名綁定數(shù)組 --- 我們可以給 `:class` 綁定一個(gè)數(shù)組來渲染多個(gè) css 類名【實(shí)際開發(fā)中綁定數(shù)組用的不多】 ```javascript data() { return { activeClass: 'active', errorClass: 'text-danger' } } ``` ```html <!-- 模板語法 --> <div :class="[activeClass, errorClass]">liang</div> <!-- 頁面渲染 --> <div class="active text-danger">liang</div> ``` 數(shù)組和對象結(jié)合: ```javascript data() { return { isActive: true, textClass: 'text-info' } } ``` ```html <!-- 模板語法 --> <div :class="[textClass, { active: isActive }]">liang</div> <!-- 頁面渲染 --> <div class="text-info active">liang</div> ``` #### 9. style 樣式綁定對象 --- `:style` 支持綁定一個(gè)對象值,對應(yīng)的是 html 的 style 屬性值 ```javascript data() { return { activeColor: 'red', fontSize: 30 } } ``` ```html <!-- 模板語法 --> <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">liang</div> <!-- 頁面渲染 --> <div style="color: red; font-size: 30px;">liang</div> ``` 眾所周知,css 有很多 kebab-cased 命名法的屬性,比如 `font-size`,這種屬性名在綁定樣式時(shí)**要么使用引號包裹**,**要么使用 camelCase 命名代替 kebab-cased 命名** 下面兩種寫法都是可以的,Vue 官方推薦使用 camelCase 命名形式 ```html <!-- camelCase 命名法【vue官方推薦】 --> <div :style="{ fontSize: fontSize + 'px' }">liang</div> <!-- kebab-cased 命名法 --> <div :style="{ 'font-size': fontSize + 'px' }">liang</div> ``` 直接綁定一個(gè)樣式對象通常是一個(gè)好方式,這樣可以使模板更加簡潔: ```javascript data() { return { styleObject: { color: 'red', fontSize: '13px' } } } ``` ```html <div :style="styleObject">liang</div> ``` 同 class 綁定,若樣式對象需要復(fù)雜的邏輯,可以使用返回對象的計(jì)算屬性【下面樣式對象邏輯并不復(fù)雜,只為演示用法】 ```javascript data() { return { color: 'blue', isActive: true } }, computed: { getStyles() { return this.isActive ? { color: this.color } : {} } } ``` ```html <div :style="getStyles">liang</div> ``` #### 10. style 樣式綁定數(shù)組 --- 還可以給 `:style` 綁定一個(gè)數(shù)組,數(shù)組元素是包含多個(gè)樣式的對象,這些對象會(huì)被合并,然后再進(jìn)行渲染 ```javascript data() { return { stylesObject1: { color: 'red' }, stylesObject2: { fontSize: '25px' }, } } ``` ```html <!-- 模板語法 --> <div :style="[stylesObject1, stylesObject2]">liang</div> <!-- 頁面渲染 --> <div style="color: red; font-size: 25px;">liang</div> ``` `style` 和 `:style` 都存在時(shí),樣式會(huì)進(jìn)行合并,如果相同的樣式,誰在后面誰生效,也就是屬性靠右的生效 ```html <div style="color: blue" :style="stylesObject2">liang</div> <div style="color: blue" :style="[stylesObject1, stylesObject2]">liang</div> ``` #### 11. 條件渲染 v-if 和 v-show --- `v-if` 用于條件性的渲染一塊內(nèi)容,當(dāng)表達(dá)式為真值時(shí)才被渲染 ```html <div v-if="score >= 60">及格</div> <div v-if="score >= 60">及格</div> <div v-else>不及格</div> <div v-if="score > 90">優(yōu)秀</div> <div v-else-if="score >= 60">及格</div> <div v-else>不及格</div> ``` 在包裝器元素 `<template>`上使用 `v-if` 條件渲染分組 因?yàn)?v-if 是一個(gè)指令,他必須依附于某個(gè)元素。 但如果想要切換的不止一個(gè)元素呢?這種情況下可以使用不可見的包裝器元素 `<template>` 將多個(gè)元素包裹起來 ```html <template v-if="true"> <view></view> <view></view> </template> <template v-else> <view></view> <view></view> </template> ``` v-show 也可以按照條件來決定是否顯示一個(gè)元素,用法和 v-if 基本一樣 ``` <h1 v-show="ok">Hello!</h1> ``` 經(jīng)典問題: v-if 和 v-show 有什么區(qū)別 ? 當(dāng)條件為假值時(shí),`v-if` 并不會(huì)在 dom 渲染保留元素,而 `v-show` 會(huì)渲染元素,只是設(shè)置 css 屬性了 `display: none;` ```html <!-- 模板語法 --> <view v-if="false">liang</view> <view v-show="false">itqaq</view> <!-- 頁面渲染 --> <!--v-if--> <view style="display: none;">itqaq</view> ``` #### 12. 列表渲染 v-for --- **v-for 指令基于一個(gè)數(shù)組來渲染一個(gè)列表??梢杂糜诒闅v數(shù)組和對象** ```javascript data() { return { object: { name: 'liang', age: 18 }, array: [{ message: 'Foo' }, { message: 'Bar' }], } } ``` ```html <!-- array 源數(shù)據(jù)數(shù)組 item 迭代項(xiàng)的別名,即數(shù)組元素 index 數(shù)據(jù)下標(biāo) --> <li v-for="item in array"> {{ item.message }} </li> <!-- object 源數(shù)據(jù)對象 value 屬性值 key 屬性 index 下標(biāo) --> <li v-for="(value, key, index) in object"> {{ key }} : {{ value }} </li> ``` **在 v-for 中使用范圍值** v-for 可以接收一個(gè)整數(shù)值。在這種用例中,該模板基于 `1...n` 的取值范圍重復(fù)多次 ```html <!-- n 從 1 開始,而不是 0 --> <span v-for="n in 10">{{ n }}</span> ``` `<template>` 上的 `v-for` 可以使用包裝器元素 `<template>` 包裹多個(gè)元素的塊,用 v-for 進(jìn)行遍歷,這樣可以使代碼更加直觀 ```html <template v-for="n in 10"> <div></div> <div></div> <div></div> </template> ``` #### 13. Vue3 的組合式 API --- Vue3 組件可以按照兩種不同的 API 風(fēng)格書寫: **選項(xiàng)式 API** 和 **組合式 API** **什么是選項(xiàng)式 API ?** **選項(xiàng)式 API** 又稱為 **聲明式渲染** 選項(xiàng)式 API 就是 Vue2 中的編寫風(fēng)格,使用 data,methods,computed,watch 等選項(xiàng)的 API 風(fēng)格。 **什么是組合式 API ?** 組合式 API 優(yōu)點(diǎn): 將同一個(gè)邏輯關(guān)注點(diǎn)相關(guān)代碼收集在一起 組合式 API 是一系列 API 的集合,使我們可以使用函數(shù)而不是聲明選項(xiàng)式的方式書寫 Vue 組件 setup() 鉤子 : [https://cn.vuejs.org/api/composition-api-setup.html](https://cn.vuejs.org/api/composition-api-setup.html) ```html <div id="app"> <div>{{ msg }}</div> <div>{{ user }}</div> <button @click="updateMsg">修改 Msg</button> <button @click="updateUser">修改 User</button> </div> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <script> const { ref, reactive, toRefs } = Vue // 使用 ref,reactive 包裹數(shù)據(jù)是為了讓變量具有響應(yīng)式 // 基本數(shù)據(jù)類型使用 ref 方法包裹 // 引用數(shù)據(jù)類型使用 reactive 方法包裹 // toRefs 讓解構(gòu)后的數(shù)據(jù)具有響應(yīng)式(使用...解構(gòu)出來的屬性不是響應(yīng)式的) Vue.createApp({ // 組件被創(chuàng)建之前執(zhí)行,不需要使用 this setup() { /* msg 邏輯代碼 */ let msg = ref('hello vue') function updateMsg() { msg.value = 'waterflosserreview.com' } /* user 邏輯代碼 */ const user = reactive({ name: 'liang', age: 18 }) function updateUser() { user.name = 'wang' } return { msg, updateMsg, user, updateUser, ...toRefs(user) } } }).mount('#app') </script> ``` **在 setup 中使用 watch() 偵聽器**