Vue#
1. 初識 Vue#
### 命令式與聲明式
- 命令式
每一步都直接使用js原生實現
- 聲明式
先列舉要做什麼,在編譯過程再自動生成邏輯
tip. 犧牲性能,提升編程體驗。
### 虛擬DOM
- 虛擬DOM是真實DOM的一個JS對象抽象。其中虛擬DOM即JS對象。
- 真實DOM的屬性眾多,直接操作DOM,對瀏覽器的檢索壓力太大,性能不足。因此使用虛擬DOM,再通過虛擬DOM的變化(diff)去映射操作真實DOM,以提高性能。
- 在Vue中,虛擬DOM的組成部分就是VNode(js對象),每一個VNode擁有children - VNode數組,這樣就構成一個樹形結構,這便是虛擬DOM樹。
- VNode.elm 指向了真實 Node, VNode.context 指向vue實例
### 運行時與編譯時
- 運行時
- 使用render渲染函數將虛擬DOM直接轉換為真實DOM
- 運行時 有一定性能支出
- 虛擬DOM與真實DOM結構類似,是直接的映射關係,無法分析用戶提供的內容。
- 編譯時
- 解析Template模板內容,轉化為真實DOM
- 編譯過程可以讀取模板語法,可以分析用戶內容。
- 性能較佳
- 運行時+編譯時 Vue
- 利用template模板,生成render函數
- 使用render函數將虛擬DOM轉化為真實DOM
### Vue UI
- 模板描述 聲明式
- render函數 命令式 render函數(h函數)返回值是一個虛擬DOM(VNode)
### 初始渲染器
- renderer 對象 使用函數 createRenderer 生成
- 參數: 虛擬DOM對象(VNode) 掛載容器(真實DOM元素)
- renderer 作用是將虛擬DOM掛載在真實DOM元素上
### 區分
- render是vue提供給用戶,操作虛擬DOM的途徑
- renderer 是vue將虛擬DOM轉化為真實DOM的工具
2. 響應系統#
### 副作用函數 與 響應式數據
1. 擁有一個全局變量 A,它被函數innerText與視圖關聯。
2. 當我們修改了變量A,關聯元素並不會更新
3. 只有再一次執行 innerText,視圖才會更新
- 我們稱 innerText 就是 副作用函數。
- 這個涉及視圖變化的變量A 就是響應式數據
### 核心邏輯
- 必須有 setter getter / 訪問器
- 類似高級語言中的 訪問器,虛擬DOM對象 以及 數據對象 都可以使用訪問器,來實現類似高級語言中的數據綁定模式。
### 基本原理
- getter: 響應式數據
當觸發讀取操作,則將該副作用函數列入“名單”
- setter: 響應式數據
當觸發設置操作,再次執行副作用函數以更新視圖
### 實現原理
- Vue2 中 使用 Object.defineProperty()
- Vue3 中 使用 Proxy() Reflect
### 響應性調度
- 當響應數據發生變化,副作用函數”名單“被執行,那麼控制”名單“的執行順序,就很重要。
- 響應性調度的關鍵在於 異步編程Promise 隊列模型jobQueue
- **懶性執行**
- **計算屬性** 也依賴於調度系統
- watch 監聽器,當監聽對象發生變化,執行回調。
- 當監聽器的回調中有異步操作,則可能導致執行結果不符合預期,使用 onInvalidate() 停止過期的異步請求。
### 實現
- 對象的響應性 Proxy + Reflect
- 非對象的響應性(基本類型) ref
3. 渲染#
4. 組件原理#
5. 編譯器#
### DSL 編譯器
- 特定語言編譯器
- 本質是 將模板 編譯為 render 渲染函數
1. parse 模板 -> AST對象
2. transform AST -> JavascriptAST
3. generate JsAST -> render函數
Vue - 基礎#
模板語法#
- 在標籤中插入響應式文本,使用
{{ msg }}
- 為
屬性
設定響應式數據,使用v-bind:id=""
或使用簡寫:
- 若
2
中綁定的數據是布爾類型,則 vue 會處理該屬性存在或不存在。 - 使用不帶參數的
v-bind=""
語法,可以將帶有多個屬性的 json 對象綁定在標籤上。 - Vue 的所有數據綁定都支持 js 表達式
- 可以使用一個組件暴露的方法,該方法會在組件每次更新時調用,因此不應產生任何副作用,例如改變數據和異步操作。
- ** 模版中的表達式只能訪問到有限的全局對象列表,該列表可以在
app.config.globalProperties
中顯示添加。
## 指令
- 是指帶有·v-·前綴的特殊屬性,
- 指令的期望值是 js表達式,用來在表達式發生變化時響應更新DOM
- `v-bind:href="xxx.api"`後可以帶 指令參數
- `:[attributeObj]="xxx.api"` 參數可以時動態的,但表達式需要用方括號包圍
- ** HTML 中的字符會自動處理為小寫,且不可以為空格 **
- ** 推薦使用 計算屬性**
響應式基礎#
** 使用組合式API **
創建一個響應式對象或數組#
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Vue3 使用 Javascript Proxy 來監聽響應式對象。
使用 setup
函數定義返回響應式對象,供給模板使用
import { reactive } from 'vue'
// 組件的默認行為
export default {
// setup 用於組合式 API 的鉤子函數
setup(){
// 定義 響應式對象
const state = reactive({ count: 0 })
// 定義 方法
function increment(){
state.count++
// 全局API 更新DOM的下個Tick
nextTick(()=>{
// xxx
})
}
// 暴露 state 到模板
return {state, increment}
}
}
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
使用 <script setup>
DOM 更新#
vue 會維護一個 DOM 更新序列,因此,數據的修改和 DOM 的更新並不嚴格同步。
若要保證更新完成,可以使用 nextTick()
深層響應性#
Vue 中所有狀態默認為深層響應式,無論多深層次的對象或數組,改動都能被檢測到。
代理#
使用 reactive()
處理後的對象,返回的是其原始對象的代理。
改變原始對象並不會出發響應性。
對同一個對象多次調用 reactive()
函數,會返回同一個代理
對代理調用 reactive()
函數,則會返回代理本身
⚠️注意
-
reactive()
只對 json 對象生效,對原始類型無效。 -
對響應式對象(代理)不應隨意替換(賦新值),或賦值 / 解構到本地變量(該變量不會有響應性),或作為參數傳入函數(不會具有響應性,等價於解構)
使用 ref()
#
ref(1)
創建了一個對象的響應式代理,該對象具有一個value
屬性,即為被引用的原始類型變量。
ref()
也可以接受一個對象,此時,對該引用的value
屬性訪問,會自動獲取到 reactive()
的結果。
ref()
在模版中作為頂層屬性訪問時,會自動解包,無需.value
<script setup>
import {ref} from 'vue'
const count = ref(0)
function increment(){
count.value++
}
const obj = { foo: ref(1) }
</script>
<template>
<button @click="increment">
{{ count }}
</button>
<button>
{{ obj.foo + 1 }} <!--[object:object]1 -->
</button>
</template>
響應性計算屬性#
- 示例 和使用說明
<script setup>
import { reactive, d } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2' - 'Advanced Guide',
'Vue 3' - 'Advanced Guide',
'Vue 4' - 'Advanced Guide',
]
})
// 響應式計算屬性
const publishedBooksMessage = computed(()=>{
return author.books.length > 0 ? 'Yes':'No'
})
</script>
<template>
<p>
Has Publiched books:
</p>
<span>{{ author.books.length > 0? 'Yes':'No'}}</span>
<span>{{ publishedBooksMessage }}</span>
</template>
comupted()
期望接受一個 getter 函數,返回一個 計算屬性 ref,Vue 會自動追蹤表達式依賴,任何變化都會實時更新。
-
計算屬性 Vs 函數
計算屬性會根據響應式依賴被緩存,而函數在組件重新渲染時被執行。
-
可寫計算屬性
<script setup> import {ref, computed} from 'vue' const firstName = ref('John') const lastName = ref('Doe') const fullName = computed({ get(){ return firstName.value + ' ' + lastName.value }, set(newValue){ [firstName.value, lastName.value] = newValue.split(' ') } }) </script>
-
⚠️注意
- Getter 不應有任何副作用,即異步請求或更改 DOM
- 計算屬性返回的是一個 “臨時快照”, 當源發生變化時,新的快照將會生成。更改計算結果時沒有意義的,計算屬性應該視為只讀。
Class 和 Style 綁定#
綁定 Class#
<script setup>
import { reactive, ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
const classObject = reactive({
active: true,
'text-danger': false
})
const classComput = computed(()=>({
active: isActive.value && !error.value,
'text-danger': error.value && error.value.type ==='fatal'
}))
</script>
<template>
<div class="static" :class="{ active: isActive, 'text-danger': hasError }"> 綁定ref元素 </div>
<div :class="classObject"> 綁定對象 </div>
<div :class="classComput"> 綁定計算屬性 </div>
<div :class="[isActive ? activeClass : '', errorClass]"> 綁定對象數組 </div>
</template>
綁定 Style 樣式#
<script setup>
import { reactive, ref } from 'vue'
const activeColor = ref('red')
const fontSize = ref(30)
const styleObject = reactive({
color: 'red',
fontSize: '13px'
})
</script>
<template>
<div :style="{ color: activeColor, fontSize: fontSize + 'px'}"> 駝峰命名的style元素 </div>
<div :style="{ 'font-size': fontSize + 'px' }"> 短橫風格的實際style-key名稱 </div>
<div :style="styleObject"> 綁定對象 </div>
<div :style="[baseStyles, overridingStyles]"> 綁定數組 </div>
</template>
Vue 會根據瀏覽器支持情況,自動添加樣式前綴,在一個屬性提供多個值時,會自動選擇適合當前環境的值。
條件渲染#
<script setup>
import { ref } from 'vue'
const awesome = ref(true)
</script>
<template>
<button @click="awesome = !awesome">toggle</button>
<template v-if="awesome">
<h1>Vue is awesome!</h1>
<h1>Oh no 😢</h1>
</template>
</template>
條件渲染 v-if 、v-else-if、v-else,支持在 template 標籤上使用。
v-show 會有類似效果,但是不支持在 template 標籤上使用。
v-show 只會改變 display 屬性,元素依然會存在 DOM 中。
v-if 有更高的切換開銷,v-show 有更高的初始渲染開銷。
⚠️ v-if 和 v-for 同時存在時,優先執行 v-if。不推薦。
循環渲染#
<script setup>
import { reactive, ref } from 'vue'
const items = ref([{message: 'foo'}, {message: 'bar'}])
const object = reactive({
title: "how to do lists in Vue",
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
</script>
<!-- v-for 在模板中可用 -->
<template v-for="it in 1">
<li v-for="n in 10">{{n}}</li>
<!-- 重複元素中,可以完整訪問父作用域的屬性和變量,item則只在內部 -->
<li v-for="item in items">{{item.message}}</li>
<!-- 遍歷對象,返回對象所有屬性,順序基於 Object.keys -->
<li v-for="value in object">{{value}}</li>
<!-- 遍歷對象,返回兩/三個參數 -->
<li v-for="(value, key, index) in object">{{key}}:{{value}}</li>
</template>
⚠️ Vue 默認 就地更新 以提高性能。此時,列表的順序的改變不會改變渲染順序。
通過為循環元素添加 :key
屬性,可以改變此行為。
⚠️ Vue 會侦測操作數組函數,來響應數組的變化。但一些函數不會改變原數組,而是返回新數組,此時 Vue 不會侦測到變化。
事件處理#
-
內聯事件處理器
事件被觸發時,執行的內聯 Javascript 語句
-
方法事件處理器
一個指向組件上定義的方法的屬性名或路徑
** 內聯事件處理 **
<script setup>
// ...
const count = ref(0)
</script>
<template>
<button @click="count++">
Add 1
</button>
<p>
Count is: {{ count }}
</p>
</template>
** 方法事件處理 **
<script setup>
const name = ref('world!')
function greet(event){
alert('Hello ${name.value}')
// event是DOM原生事件
if(even){
// 使用 event.target 獲取原生事件對象
alert(event.target.tagName)
}
}
</script>
<template>
<!-- greet -->
<button @click="greet">
Greet
</button>
</template>
⚠️ 搞不清楚內聯和方法事件的區別。
可以使用 $event
傳遞原生 DOM 事件
- 事件修飾符
.stop
.prevent
.self
.capture
.once
.passive
- 按鍵修飾符
- 系統按鍵修飾符
.exact
控制按鍵組合,多餘按鍵組合不會觸發
- 鼠標按鍵修飾符
表單處理#
** 輸入組件的數據綁定很麻煩 **
<input :value='text'
@input="event => text=event.target.value">
使用v-model
簡化#
<input v-model="text">
⚠️ v-model 會自動處理不同 html 標籤的鍵入屬性。
⚠️ v-model 會忽略拼字階段的鍵入,如果要取消忽略行為,請使用 input 事件處理方法
復選框的單一綁定和數組綁定#
<!-- ☑️ true 🔲 false -->
<!-- :true-value :false-value -->
<input type='checkbox' v-model="checked"/>
<label for="chekcbox">{{ checked }}</label>
<!-- 選中的項目的value會被添加到數組中 -->
<script setup>
const checkNames = ref([])
</script>
<template>
<div>
Checked Names: {{ checkNames }}
</div>
<input type="checkbox" value="Jack" v-model="checkNames">
<label for="jack">Jack</label>
<input type="checkbox" value="Tom" v-model="checkNames">
<label for="jack">Jack</label>
<input type="checkbox" value="Mike" v-model="checkNames">
<label for="jack">Jack</label>
</template>
單選按鈕組#
<div>
Picked: {{picked}}
</div>
<input type='radio' value="One" v-model="picked"/>
<input type='radio' value="Two" v-model="picked"/>
<!-- vue使用Value值來區分不同按鈕 -->
選擇器#
<div>Selected: {{ selected }}</div>
<select v-model="selected">
<!-- 推薦提供一個空值的禁用選項 -->
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
⚠️ 綁定到一個數組,則為多選
💡 使用 v-for 循環渲染選項更方便
- 上述三種組件,可以使用
v-bind
綁定動態數據 - 修飾符
.lazy
鍵入觸發改為 change 觸發.number
自動轉換數字,當type='number'
時會自動啟用.trim
自動去除兩端空格
組件的生命週期鉤子函數#
在組件的生命週期各個階段,用戶可以通過定義鉤子函數做一些特定行為。
監聽器 Watch#
基本用法#
<script setup>
import {ref, watch} from 'vue'
const question = ref('')
const answer = ref('Questions usually contain ...')
const myobj = reactive({ count: 0 })
watch(question, async(newvalue, oldvalue)=>{...})
// 注意,對於一個響應式對象,不可以監聽其成員,因為得到的是一個值
// 應該使用 Getter 函數
watch(()=>myobj.count,(count)=>{ ... })
</script>
深層監聽#
監聽一個響應式對象時(watch 函數第一參數為對象),自動啟用深層監聽,對象的任何成員更新,都会觸發動作。但由於監聽對象依舊是同一個對象,因此回調函數的 newva, oldval
依舊沒變。
監聽一個對象的 getter,則只有對象改變,才會觸發。
<script setup>
import { reactive } from 'vue'
const obj = reactive({ count:0 })
watch(obj,()=>{
// 自動啟用深層監聽
})
watch(()=>obj,(newv,oldv)=>{
// 只有返回不同的obj對象,才會觸發。
},{deep:true/* 強制轉為深層監聽 */})
</script>
即時執行#
監聽器默認只有數據源發生變化時,才會觸發。
若想在創建完成後立刻觸發一次,可以傳入 immediate: true
參數
watchEffect()#
使用此函數可以無需提供響應式數據源,而是由 vue 自動根據回調函數的依賴情況,進行響應式觸發,此外,該函數默認帶有immediate:true
屬性。
<script setup>
const todoId = ref(1)
const data = ref(null)
watch(todoId, async()=>{
const response = await fetch(
'https://xxx.xxx.com/api/todos/${todoId.value}'
)
data.value = await response.json()
},{immediate:true})
// 等價於
watchEffect(async()=>{
// ...
})
</script>
😄 當回調依賴數據眾多時,watchEffect 的作用就非常明顯了。此外,該函數對於對象屬性的監聽,是表層的,非深層的。
⚠️ 在響應式更新中,用戶監聽回調默認在 DOM 更新之前,因此在回調期間訪問的 DOM 並不是最新的。若要改變此行為,附加 flush:'post'
屬性。或使用後置刷新監聽函數watchPostEffect()
⚠️ 通常在 <script setup>
中創建的監聽器,會隨著組件的創建自動綁定到組件,並隨著組件的卸載自動停止。但當使用異步語句創建的監聽器,需要手動停止,以防止內存泄漏。使用watch/watchEffect的返回值停止一個監聽器。
模板引用#
模板引用可以幫助你獲取到底層 DOM 元素
訪問模板引用#
<script setup>
import {ref, onMounted} from 'vue'
// 聲明一個引用 ref 來存放模板引用
const input = ref(null)
onMounted(()=>{
input.value.focus()
})
</script>
<template>
<!-- ref 名稱必須和 腳本中的變量名相同。 -->
<input ref="input"/>
</template>
⚠️ 組件掛載後,模板引用才會有值。初次渲染時,引用時 null。若要監聽一個模板引用,必須考慮其為 null 的情況。
v-for 模板引用#
請聲明一個數組的引用類型,以存放整個列表的元素。
⚠️ ref 數組不能保證於原數組相同順序。
函數模版引用#
<input :ref="(el)=>{ ... }"/>
函數模板會在組件更新以及銷毀時觸發。銷毀時el===null
組件#
定義組件#
-
使用構建步驟時,Vue 組件定義在一個
.vue
文件中,稱為單文件組件 SFC<script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++"> You clicked me {{ count }} tiems. </button> </template>
-
不使用構建步驟,一個 Vue 組件用一個特定的 Javascript 對象表示。
<script> import {ref} from 'vue' export default { setup() { const count = ref(0) return { count } }, template: ' <button @click="count++"> You clicked me {{count}} times. </button>' } </script>
使用組件#
通過<script setup>
,導入的組件可以在模板中直接使用。
全局組冊組件可以使組件在整個 VueApp 中可用
使用組件只需創建組件名同名的標籤即可。
⚠️ 在 vue 文件中,組件的標籤可以區分大小寫,但若直接在 html 文件中便攜模板,則遵從瀏覽器解析規則。
Props#
prop 相當於組件的 “參數”。在調用組件的時候,可以根據傳入的 prop,顯示不同的內容。
使用defineProps
宏來定義組件的 Props
組合式
<script setup>
defineProps(['title'])
</script>
<template>
<h4>
{{ title }}
</h4>
</template>
選項式
export default {
props: ['title'],
// 必須將props作為參數傳入setup
setup(props) {
console.log(props.title)
}
}
監聽事件#
要想子組件觸發事件,被父組件接收,需要:
- 使用宏
defineEmits(['enlarge-text'])
聲明事件列表 - 在組件中書寫同名的事件觸發
@click="$emit('enlarge-text')"
- 在父組件中書寫事件觸發回調
@enlarge-text=" ... "
與 props 類似,使用選項式聲明事件列表,且setup
函數第二個參數 ctx 上下文可以訪問到ctx.emit
事件列表
插槽#
插槽可以使我們的自定義組件,像普通 html 組件一樣,傳遞內容。
<script setup>
import AlertBox from '/alertbox.vue'
</script>
<template>
<AlertBox>
The Slot Message.
</AlertBox>
</template>
<!-- 這裡是 alertbox.vue 子組件 -->
<template>
<slot/>
</template>
動態組件 - 實現組件切換#
#DOM 解析⚠️#
Vue 高級#
組件註冊#
全局註冊#
<!-- App.vue -->
<script>
import { createApp } from 'vue'
import MyComponent from './mycomponent.vue'
const app = createApp({})
// 1. 直接註冊
app.component('componentName', {/* 組件實現 */})
// 2. 單文件註冊
app.component('componentName', MyComponent)
// 3. 可鏈式調用
app
.component('componentName', MyComponent)
.component('componentName', MyComponent)
.component('componentName', MyComponent)
</script>
局部註冊#
- 在
<script setup>
標籤中導入的組件可以直接使用 - 使用選項式語法,需要顯式在
components:{ nameA, nameB }
中註冊
組件名格式#
- PascalCase 是建議的組件名格式,在單文件組件中,此名稱也可以很好的於普通 html 標籤區別。
- 在 DOM 模板中,請使用對應的 kebab-case 名稱。
- vue 會自動處理兩種格式和使用場景的轉化。
Prop#
聲明 Prop#
- 在
<script setup>
中使用defineProps(['prop1,prop2'])
方式 - 在選項式中使用
props: ['prop1','prop2]
方式 - 以 javascript 對象的方式帶上類型名
<script setup>
defineProps({
title: String,
likes: Number
})
export default {
props: {
title: String,
likes: Number
}
}
</script>
書寫格式#
- 使用 camelCase 命名 Prop 可以直接在模板中不帶引號的使用屬性。
- 在模板中,請使用 kebab-case 格式,與 DOM 書寫風格形成統一。
- vue 會自動處理不同的寫法的對應。
Vuex#
** 一種存儲全局狀態的倉庫 Store。而且其中的狀態都是響應式的。 **
** 其中存儲的所有狀態 state 都不能直接改變,只能通過 提交 commit 改變 **
示例#
import { createApp } from 'vue'
import { createStore } from 'vuex'
const store = createStore({
state(){
return {
count: 0
}
},
mutations: {
increment(state){
state.count++
}
}
})
const app = createApp({})
app.use(store)
// 使用 store.commit('name') 觸發狀態變更
// 使用 store.state 獲取對象
// 在所有VUE組件中,使用 this.$store 訪問store實例 (選項式)
// 在setup中導入useStore,並使用其訪問store。 (組合式)
概念#
-
localStorage
- 是 H5 支持的本地存儲的特性,解決了 cookie 空間不足的問題。
- 只能存儲字符串的數據。
.getItem('key')
.removeItem('key')
.clear()
.key()
-
State
- 相當於一個集中的,全局的響應式數據庫,狀態 State 是這些響應式變量的名字。
- 在組件中調用狀態時,為了保持響應性,應該使用
箭頭函數
的方式訪問。 - 使用
mapState()
可以返回多個 State,同時也可以以函數的方式將 State 計算後返回。在函數內部,可以使用this
訪問到局部組件的變量 - 使用 ** 對象展開運算符
...mapState
可以將函數返回的對象展開混入到上級屬性中。
-
Getter
-
Mutation
-
是更改 state 的唯一途徑。通過
commit
mutation 方法,改變 state -
那麼 Mutation 中是一個包含多個函數的對象,每個函數中有 state 的不同的更改操作。
-
Mutation 函數的第一個參數是 state。
-
⚠️mutation 中定義的函數只能通過 commit 方法觸發,commit 方法第一個參數是 mutation 函數名,之後的參數可以作為參數傳遞給 mutation 函數。在函數定義時,第一個參數通常是 State,之後的參數就是其他參數,與 commit 的第二之後的參數對應。
-
提交方式可以是對象。將第二參數寫成 json 對象的形式,那麼整個對象都將作為載荷提交給 mutation 函數。使用對象風格時,可以將函數名作為 type 屬性加入到 json 對象中,整體提交。
-
Mutation 必須是同步函數
-
⚠️ 使用常量代替 Mutation 事件名是一種開發模式
// mutation-types.js export const SOME_MUTATION = 'SOME_MUTATION' // store.js import { createStore } from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = createStore({ state:{...}, mutations: { [SOME_MUTATION](state){...} } })
-
⚠️
mapMutation
是改進代碼閱讀性的方法,與直接調用無區別import { mapMutations } from 'vuex' export default { methods: { ...mapMutations([ 'increment', // => 'increment'=$store.commit('increment') add: 'increment', ]) } }
-
-
Action
- 可以處理異步函數的 Mutation
-
Module
- 將 store 分割成不同的模塊,在使用
createStore({modules:{}})
創建 store
- 將 store 分割成不同的模塊,在使用
Vue Router#
** 簡單案例 **
<p>
<router-link to="/">GO to home</router-link>
<router-link to="/about">Go to About</router-link>
</p>
<router-view>路由出口</router-view>
使用 router 標籤代替 html 的超鏈接標籤,將跳轉交給 router。
使用 router-view 標籤接收路由處理結果。可以顯示組件或其他動作。
// 1. 示例組件
const Home = {template: '<div>Home</div>'}
const About = {template: '<div>About</div>'}
// 2. 定義路由對象
// 每一個路由對象都包含一個路徑,以及組件
const routes = [
{path:'/', component: Home},
{path:'/about', component: About},
]
// 3. 創建路由實例,設置路由配置。
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes, // => routes: routes
})
// 將路由掛載到 vueApp
const app = Vue.createApp({})
app.use(router)
app.mount("#app")
router 已經啟用。在組件內可以this.$router
訪問當前路由。
在setup
函數中,使用useRouter() useRoute()
函數獲取 router 實例。
動態路由匹配#
場景:當顯示用戶主頁時,需要動態字段傳遞用戶 ID
- 使用
:id
冒號 + 路徑參數 - 此時路徑參數將保存在
this.$route.params
中 - 此外,
$route.query
會保存 URL 中的參數(?參數)
路由參數響應#
場景:當從/users/a
-> /users/b
時,相同的組件將會被復用,提高性能,但組件將不會重新構建,相關函數也不會重新調用。
此時頁面需要對路由參數的變化做出響應。
- 使用
watch
監聽this.$route.params
- 使用
async beforeRouteUpdate(to, from)
導航守衛
正則表達式#
常規的路由參數只會匹配 /
之間的字符。
如果要跳出分隔符的限制,可以使用自定義參數正則
const routes = [
{path:'/:pathMatch(.*)*'}
]
路由參數後的(...)
是正則表達式,符合表達式的字符串被賦給參數pathMatch
,後面的*
表示可選可重複
高級匹配#
...
路由匹配語法#
通常,使用靜態路由 /about
以及一個參數來結尾的動態路由 /users/:userId
即可滿足業務需求。
Vue-router 額外的支持
參數自定義正則#
場景: 下面兩個路由會匹配相同的 URL /:orderId
/:productName
為了做區分,有以下方法
-
前面增加靜態部分
const routes = [ {path: '/o/:orderId'}, {path: '/p/:productName'} ]
-
使用正則表達式修改參數匹配規則
const routes = [ // 注意轉義字符 純數字的結尾被匹配到 orderid {path: '/:orderId(\\d+)'}, {path: '/:productName'}, ]
可重複參數#
*
表示可匹配 0 個或多個路由(/ 包圍的段)+
表示可匹配 1 個或多個路由?
表示可匹配 0 個或 1 個路由- 此時參數接收到的不再是字符串,而是一個數組
- 可以與正則表達式一起使用
配置大小寫敏感,以及嚴格模式#
默認情況下,/users, /users/, /Users/
是同一路由。
嚴格模式將排除情況 2,敏感模式將排除 3
const router = createRouter({
// ...
routes: [/* ... */],
strict: true,
sensitive: true,
})
命名路由#
允許為路由對象 增加 name
屬性,來使用 自動構建 URL 的特性
命名路由可以防止手動輸入 URL 出錯。
<template>
<router-link :to="{ name: 'user', params: { username: 'erina' }}">
User
</router-link>
</template>
<script>
router.push({ name: 'user', params: { username: 'erina' } })
</script>
嵌套路由#
- 當子組件中有
<router-view>
標籤時,可以設置嵌套路由。 - 即該路由設置一個
children:
屬性,包含路由數組。 - 可為嵌套路由設置 空路徑 路由對象,使其依然可以為父路由路徑渲染合適的內容。
編程路由#
除了在 模板 中使用 <router-link>
標籤,還可以在 script
中通過訪問 this.$router
實例,來實現導航。
導航#
使用方法 router.push
為 history 棧添加一個新的記錄。
<router-link :to:"...">
等價於 router.push()
-
push 方法的幾種用法
// 字符串路徑 router.push('/users/eduardo') // 帶有路徑的對象 router.push({ path: '/users/eduardo' }) // 命名的路由,並加上參數,讓路由建立 url router.push({ name: 'user', params: { username: 'eduardo' } }) // 帶查詢參數,結果是 /register?plan=private router.push({ path: '/register', query: { plan: 'private' } }) // 帶 hash,結果是 /about#team router.push({ path: '/about', hash: '#team' })
-
⚠️ path 參數 會使 params 被忽略,所以請使用 name 命名路由。
const username = 'eduardo' // 我們可以手動建立 url,但我們必須自己處理編碼 router.push(`/user/${username}`) // -> /user/eduardo // 同樣 router.push({ path: `/user/${username}` }) // -> /user/eduardo // 如果可能的話,使用 `name` 和 `params` 從自動 URL 編碼中獲益 router.push({ name: 'user', params: { username } }) // -> /user/eduardo // `params` 不能與 `path` 一起使用 router.push({ path: '/user', params: { username } }) // -> /user
-
⚠️ params 接收 string 或 number(可重複參數接收數組),undefined false 等將被自動序列化。可選參數可用 “” 跳過
-
router.push
返回一個Promise
替換當前位置#
動作類似於 push,只是不會向 history 中添加記錄。
<router-link :to="..." replace>
等價 router.replace()
等價 router.push({path:'...', replace: true})
橫跨歷史#
// 向前移動一條記錄,與 router.forward() 相同
router.go(1)
// 返回一條記錄,與 router.back() 相同
router.go(-1)
// 前進 3 條記錄
router.go(3)
// 如果沒有那麼多記錄,靜默失敗
router.go(-100)
router.go(100)
命名視圖#
場景:目前為止,單個路由的 Component 只有一個。若我們的路由目標需要將多個組件放置到不同位置,則需要多個路由出口,以及多個組件。
使用命名視圖,並在路由對象中為不同的視圖指定不同的組件。
<template>
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
</template>
<script></script>
SCSS#
- 需要編譯器編譯成 CSS 文件才能被瀏覽器識別。彌補了 CSS 文件重複性的问题,允許程序員定義變量、函數等。
變量#
- 變量以
$
開頭,且下劃線和短橫線不作區分。 - 變量根據定義位置不同,有不同作用域。
- 多次定義同一個變量,相當於賦值操作,會覆蓋。
選擇器#
-
嵌套選擇器
-
父級選擇器
&
-
嵌套組合選擇器
, > + ~
.container ul{ border: 1px solid #aaa; list-style: none; li{ float:left; >a{ display: inline-block; padding: 6px 12px; } } &:after { display: block; content: ""; clear: both; } }
-
嵌套屬性
li{ border: 1px solid #aaa { left: 0; right: 0; } }
導入文件#
@import App2.scss
導入 sass 文件- 使用
!default
變量後綴,聲明默認值,即不會替換同名變量 - 導入行為相當於將文件插入 import 位置。
- 可以局部導入 sass 內容。
@import 'app2.css'
使用 css 原生導入。
注釋#
/* */
會被保留到 css 文件中/ /
不會保留
混合器(函數)#
-
使用
@mixin
聲明一個混合器@mixin get-border-radius($border-radius, $color:red){ -moz-border-radius: $border-radius; -webkit-border-radius: $border-radius; border-radius: $border-radius; color: $color; }
-
使用
@include
使用混合器 -
混合器可以使用一切 scss 代碼
繼承#
-
%
開頭定義一個被繼承樣式 -
使用
@extend
完成繼承 -
繼承於混合器的不同在於,可以繼承已有的 css 規則
.container { @extend %border-style; color: red; } .container1 { @extend .container; }
計算#
- scss 可以使用標準算數運算
+ - * / %
Axios#
一個 Promise 風格的 http 請求包。可以在 服務端 node.js 環境中使用,也可以在瀏覽器中使用。
簡易實用#
最簡單的使用 Axios 的方法是在 javascript 中調用方法:
axios({method:'post',url:'xxx',data{...}}).then(function)
axios.request(config)
axios.get(urlp[,config])
axios.delete(url[,config])
- ...
Axios 實例#
使用 axios.create({config})
創建一個實例。
實例方法包括:
- Axios#request(config)
- Axios#get(url[,config])
- axios#delete(url[,config])
- axios#head(url[,config])
- Axios#options(url[,config])
- Axios#post(url[,config])
- Axios#put(url[,data[,config]])
- Axios#patch(url[,data[,config]])
- Axios#getUri([config])
請求配置#
只有 URL 是必須的,method 默認為 get
{
// `url` 是用於請求的服務器 URL
url: '/user',
// `method` 是創建請求時使用的方法
method: 'get', // 默認值
// `baseURL` 將自動加在 `url` 前面,除非 `url` 是一個絕對 URL。
// 它可以通過設置一個 `baseURL` 便於為 axios 實例的方法傳遞相對 URL
baseURL: 'https://some-domain.com/api/',
// `transformRequest` 允許在向服務器發送前,修改請求數據
// 它只能用於 'PUT', 'POST' 和 'PATCH' 這幾個請求方法
// 陣列中最後一個函數必須返回一個字符串, 一個Buffer實例,ArrayBuffer,FormData,或 Stream
// 你可以修改請求頭。
transformRequest: [function (data, headers) {
// 對發送的 data 進行任意轉換處理
return data;
}],
// `transformResponse` 在傳遞給 then/catch 前,允許修改響應數據
transformResponse: [function (data) {
// 對接收的 data 進行任意轉換處理
return data;
}],
// 自定義請求頭
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` 是與請求一起發送的 URL 參數
// 必須是一個簡單對象或 URLSearchParams 對象
params: {
ID: 12345
},
// `paramsSerializer`是可選方法,主要用於序列化`params`
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作為請求體被發送的數據
// 僅適用 'PUT', 'POST', 'DELETE 和 'PATCH' 請求方法
// 在沒有設置 `transformRequest` 時,則必須是以下類型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 瀏覽器專屬: FormData, File, Blob
// - Node 專屬: Stream, Buffer
data: {
firstName: 'Fred'
},
// 發送請求體數據的可選語法
// 請求方式 post
// 只有 value 會被發送,key 則不會
data: 'Country=Brasil&City=Belo Horizonte',
// `timeout` 指定請求超時的毫秒數。
// 如果請求時間超過 `timeout` 的值,則請求會被中斷
timeout: 1000, // 默認值是 `0` (永不超時)
// `withCredentials` 表示跨域請求時是否需要使用憑證
withCredentials: false, // default
// `adapter` 允許自定義處理請求,這使測試更加容易。
// 返回一個 promise 並提供一個有效的響應 (參見 lib/adapters/README.md)。
adapter: function (config) {
/* ... */
},
// `auth` HTTP Basic Auth
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示瀏覽器將要響應的數據類型
// 選項包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
// 瀏覽器專屬:'blob'
responseType: 'json', // 默認值
// `responseEncoding` 表示用於解碼響應的編碼 (Node.js 專屬)
// 注意:忽略 `responseType` 的值為 'stream',或者是客戶端請求
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // 默認值
// `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名稱
xsrfCookieName: 'XSRF-TOKEN', // 默認值
// `xsrfHeaderName` 是帶有 xsrf token 值的http 請求頭名稱
xsrfHeaderName: 'X-XSRF-TOKEN', // 默認值
// `onUploadProgress` 允許為上傳處理進度事件
// 瀏覽器專屬
onUploadProgress: function (progressEvent) {
// 處理原生進度事件
},
// `onDownloadProgress` 允許為下載處理進度事件
// 瀏覽器專屬
onDownloadProgress: function (progressEvent) {
// 處理原生進度事件
},
// `maxContentLength` 定義了node.js中允許的HTTP響應內容的最大字節數
maxContentLength: 2000,
// `maxBodyLength`(僅Node)定義允許的http請求內容的最大字節數
maxBodyLength: 2000,
// `validateStatus` 定義了對於給定的 HTTP狀態碼是 resolve 還是 reject promise。
// 如果 `validateStatus` 返回 `true` (或者設置為 `null` 或 `undefined`),
// 則promise 將會 resolved,否則是 rejected。
validateStatus: function (status) {
return status >= 200 && status < 300; // 默認值
},
// `maxRedirects` 定義了在node.js中要遵循的最大重定向數。
// 如果設置為0,則不會進行重定向
maxRedirects: 5, // 默認值
// `socketPath` 定義了在node.js中使用的UNIX套接字。
// e.g. '/var/run/docker.sock' 發送請求到 docker 守護進程。
// 只能指定 `socketPath` 或 `proxy` 。
// 若都指定,這使用 `socketPath` 。
socketPath: null, // default
// `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
// and https requests, respectively, in node.js. This allows options to be added like
// `keepAlive` that are not enabled by default.
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// `proxy` 定義了代理服務器的主機名,端口和協議。
// 您可以使用常規的`http_proxy` 和 `https_proxy` 環境變量。
// 使用 `false` 可以禁用代理功能,同時環境變量也會被忽略。
// `auth`表示應使用HTTP Basic auth連接到代理,並且提供憑據。
// 這將設置一個 `Proxy-Authorization` 請求頭,它會覆蓋 `headers` 中已存在的自定義 `Proxy-Authorization` 請求頭。
// 如果代理服務器使用 HTTPS,則必須設置 protocol 為`https`
proxy: {
protocol: 'https',
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// see https://axios-http.com/zh/docs/cancellation
cancelToken: new CancelToken(function (cancel) {
}),
// `decompress` indicates whether or not the response body should be decompressed
// automatically. If set to `true` will also remove the 'content-encoding' header
// from the responses objects of all decompressed responses
// - Node only (XHR cannot turn off decompression)
decompress: true // 默認值
}
響應結構#
{
// `data` 由服務器提供的響應
data: {},
// `status` 來自服務器響應的 HTTP 狀態碼
status: 200,
// `statusText` 來自服務器響應的 HTTP 狀態信息
statusText: 'OK',
// `headers` 是服務器響應頭
// 所有的 header 名稱都是小寫,並且可以使用方括號語法訪問
// 例如: `response.headers['content-type']`
headers: {},
// `config` 是 `axios` 請求的配置信息
config: {},
// `request` 是生成此響應的請求
// 在node.js中它是最後一個ClientRequest實例 (in redirects),
// 在瀏覽器中則是 XMLHttpRequest 實例
request: {}
}
配置#
-
全局配置
axios.defaults.baseURL = ''; axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; axios.defaults.headers.post['Content-TYpe'] = 'application/x-www-form-urlencoded';
-
自定義默認值
// 創建實例後配置該實例默認值 const instance = axios.create({ baseURL: '' }); // 也可以修改默認值 instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
攔截器‼️#
攔截器可以在請求或響應在被 then 和 catch 處理前攔截。
// 請求攔截 在發送請求前進行處理
axios.interceptors.request.use(function(config){
// 發送前處理
return config;
}, function(error){
// 請求錯誤處理
return Promise.reject(error);
});
// 響應攔截
axios.interceptors.response.use(function(response){
// 2xx 狀態碼時
return response;
}, function(error){
// 超出 2xx
return Promise.reject(error);
})
攔截器可以移除
const myInterceptor = axios.interceptors.request.use(...)
axios.interceptors.request.eject(myInterceptor)
實例也可以添加攔截器
const instance = axios.create()
instance.interceptors.request.use()
錯誤處理#
當使用 catch 處理返回值時,返回對象會存儲到 error.response...
中
設置 validateStates
函數可以配置錯誤狀態碼
使用 error.toJSON()
可以獲取更多信息
取消請求#
-
AbortController
const controller = new AbortController(); axios.get(); // 取消請求 controller.abort();