目錄
一、作用域插槽
二、pinia的使用
一、Pinia 基本概念與用法
1. 安裝與初始化
2. 創建 Store
3. 在組件中使用 Store
4. 高級用法
5、storeToRefs?
二、Pinia 與 Vuex 的主要區別
三、為什么選擇 Pinia?
三、定義全局指令
1.封裝通用 DOM 操作,復用邏輯
2.增強模板的聲明式編程能力
3.解耦組件邏輯,保持組件簡潔
4.便于統一維護和擴展
四、指令鉤子
1. 鉤子函數列表及含義
2. 核心參數說明(以?mounted(el, binding, vnode, prevVnode)?為例)
3. 實際場景示例(用?v-focus?指令理解鉤子)
4. 為什么需要指令鉤子?
五、圖片懶加載
六、路由傳參
1、Vue 2 獲取路由參數
1.?動態路由參數(路徑中的參數,如?/user/:id)
2.?查詢參數(URL 中的??key=value)
2、Vue 3 獲取路由參數
1.?動態路由參數
2.?查詢參數
對比
補充說明
總結
七、路由
1、默認請求參數
2、路由緩存問題
解決方法
一、作用域插槽
<template><div class="app"><el-table :data=list><el-table-column label="ID" prop="id"></el-table-column><el-table-column label="姓名" prop="name" width="150"></el-table-column><el-table-column label="籍貫" prop="place"></el-table-column><el-table-column label="操作" width="150"><template #default="{row}">//作用域插槽<el-button type="primary" @click="updateData(row.id)" link>編輯</el-button><el-button type="danger" @click="deleteData(row.id)">刪除</el-button></template></el-table-column></el-table></div>
</template>
#default="{row}"
:這是?作用域插槽(scoped slot),row
?代表當前行的數據對象。- 每個操作按鈕都能通過?
row
?訪問當前行的所有屬性(如?row.id
、row.name
)。
二、pinia的使用
一、Pinia 基本概念與用法
Pinia 是 Vue.js 的新一代狀態管理庫,由 Vue 核心團隊開發,旨在替代 Vuex。它提供了更簡潔的 API、更好的 TypeScript 支持和更小的體積。
1. 安裝與初始化
npm install pinia
// main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';const pinia = createPinia();
const app = createApp(App);app.use(pinia);
app.mount('#app');
或者
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'const pinia = createPinia()
createApp(App).use(pinia).mount('#app')
2. 創建 Store
// stores/counter.js
import { defineStore } from 'pinia';// 使用組合式 API 風格
export const useCounterStore = defineStore('counter', () => {// 狀態const count = ref(0);// 計算屬性const doubleCount = computed(() => count.value * 2);// 方法const increment = () => {count.value++;};const reset = () => {count.value = 0;};return { count, doubleCount, increment, reset };
});// 或使用選項式 API 風格(類似 Vuex)
export const useUserStore = defineStore({id: 'user',state: () => ({name: '張三',age: 20}),getters: {fullName: (state) => `用戶: ${state.name}`},actions: {updateName(newName) {this.name = newName;}}
});
3. 在組件中使用 Store
<template><div><p>Count: {{ counter.count }}</p><p>Double: {{ counter.doubleCount }}</p><button @click="counter.increment">+1</button><button @click="counter.reset">重置</button></div>
</template><script setup>
import { useCounterStore } from '../stores/counter';// 獲取 store 實例
const counter = useCounterStore();// 直接訪問 state
console.log(counter.count);// 調用 action
counter.increment();
</script>
4. 高級用法
// 訂閱 state 變化
const unsubscribe = counter.$subscribe((mutation, state) => {console.log('State changed:', mutation.type, state);
});// 重置 state
counter.$reset();// 批量修改 state
counter.$patch({count: 100,// 其他屬性
});// 插件示例
const loggerPlugin = (context) => {console.log('Store initialized:', context.store.$id);return {// 擴展 storehello: 'world'};
};pinia.use(loggerPlugin);
5、storeToRefs
?
storeToRefs
?是 Pinia 提供的一個輔助函數,用于從 store 中提取狀態并保持其響應式特性。它解決了在解構 store 時丟失響應式的問題,讓你可以更優雅地在組件中使用 store 狀態。
- 問題:直接解構 store 對象(如?
const { count } = store
)會丟失響應式。 - 解決方案:
storeToRefs
?將 store 中的狀態轉換為響應式引用(refs),保持響應式更新。
// ? 錯誤:解構后丟失響應式
const { count } = store;
// ? 轉換為響應式引用
const { count } = storeToRefs(store);
?storeToRefs用于響應式數據、coumputed。對于方法,直接解構即可。
二、Pinia 與 Vuex 的主要區別
特性 | Pinia | Vuex (4.x) |
---|---|---|
API 風格 | 組合式 API 為主,支持選項式 | 僅支持選項式 API |
模塊化方式 | 自動模塊,無嵌套結構 | 需要手動定義模塊 |
TypeScript 支持 | 一流支持,無需額外配置 | 需要額外配置,類型定義復雜 |
體積 | 更小(~1kb) | 較大(~2kb) |
Mutation | 無 Mutation,直接修改 state | 必須通過 Mutation 修改 state |
代碼結構 | 更簡潔,更少樣板代碼 | 更多樣板代碼(state/getters/mutations/actions) |
插件系統 | 更靈活 | 相對固定 |
DevTools 支持 | 更現代,支持時間旅行調試 | 支持時間旅行調試 |
Vue 版本支持 | Vue 2 和 Vue 3 | Vue 2 和 Vue 3 |
三、為什么選擇 Pinia?
-
更簡潔的 API:
- 無需?
mapState
、mapActions
?等輔助函數。 - 直接通過?
store.counter
?訪問狀態,store.increment()
?調用方法。
- 無需?
-
更好的 TypeScript 支持:
- 自動類型推導,無需手動定義接口。
- 組合式 API 天然支持類型安全。
-
無 Mutation:
- 移除了 Vuex 中冗余的 Mutation 概念,直接在 Action 中修改 state。
三、定義全局指令
Vue 本身有像?v-if
、v-show
、v-model
?這類內置指令,方便開發者操作 DOM、處理數據渲染等。而全局指令,是開發者借助 Vue 提供的機制,自己定義的、可在整個 Vue 應用里通用的指令。
它基于 Vue 的指令系統拓展而來,注冊后,在所有組件的模板中,都能用?v-指令名
?的形式(比如定義了全局指令?v-focus
,就可以在任意組件?<input v-focus />
?這樣用 )去調用,來實現特定功能。
1.封裝通用 DOM 操作,復用邏輯
Vue 組件主要聚焦數據和界面的映射,但有些頻繁的 DOM 底層操作(如元素自動聚焦、自定義滾動行為、表單輸入格式化等 ),若在每個組件里重復寫,既冗余又難維護。全局指令可以把這類操作封裝起來,一處定義,處處使用。
比如定義一個?v-focus
?全局指令,實現元素掛載后自動獲取焦點:
// Vue2 中全局指令注冊(main.js 里)
Vue.directive('focus', {inserted: function (el) { el.focus() }
})// Vue3 中全局指令注冊(main.js 里)
const app = createApp(App)
app.directive('focus', {mounted(el) {el.focus()}
})
之后在任意組件的輸入框上用?<input v-focus />
,就能自動聚焦,不用在每個組件里寫獲取焦點的邏輯。
2.增強模板的聲明式編程能力
全局指令能讓模板更簡潔、語義化。
比如定義?v-permission
?指令控制按鈕權限:
// 假設根據用戶權限決定元素是否顯示
app.directive('permission', {mounted(el, binding) {const hasPerm = checkPermission(binding.value) if (!hasPerm) {el.parentNode && el.parentNode.removeChild(el) }}
})
模板中用?<button v-permission="'delete'">刪除</button>
,語義清晰,一看就知道和權限控制有關,無需在組件里寫一堆權限判斷的 JS 邏輯。
3.解耦組件邏輯,保持組件簡潔
把和 DOM 操作強相關的代碼(比如復雜的動畫初始化、第三方庫初始化 )放到指令里,組件就不用關心這些細節,專注業務數據和基本渲染。
比如用指令初始化一個圖表庫:
app.directive('chart', {mounted(el, binding) {const chart = new Chart(el, binding.value.config) // 后續更新等邏輯也可在指令鉤子處理}
})
組件里只需?<div v-chart="{ config: { /* 圖表配置 */ } }"></div>
,組件代碼更簡潔,職責更單一。
4.便于統一維護和擴展
當需要修改指令功能(如調整權限判斷邏輯、優化聚焦的時機 ),只需改全局指令的定義,所有用到該指令的組件都會生效,不用逐個組件修改,大大提升了代碼的可維護性。
四、指令鉤子
在 Vue 中,指令鉤子(Directive Hooks)?是指令定義對象里的一組生命周期函數,用于在指令綁定的 DOM 元素的不同生命周期階段執行自定義邏輯。它們像 “鉤子”,能讓你在元素從創建到銷毀的各個關鍵節點介入,做一些 DOM 操作、數據處理或邏輯控制。
1. 鉤子函數列表及含義
鉤子函數 | 執行時機(Vue 3 場景) | 典型用途 |
---|---|---|
created | 指令綁定到元素后,DOM 渲染前調用(元素還沒插入 DOM 樹) | 可初始化一些與元素關聯的 “純數據邏輯”,比如根據指令參數準備數據,或添加事件監聽(但注意此時 DOM 可能不可操作) |
beforeMount | 元素即將插入 DOM 樹前調用(元素已存在于虛擬 DOM,但未真實渲染到頁面) | 做一些 DOM 插入前的準備,比如根據指令動態修改元素屬性(如?el.setAttribute ?),但元素還沒在頁面可見 |
mounted | 元素插入 DOM 樹后調用(真實 DOM 已渲染,可操作) | 最常用!比如操作 DOM(聚焦輸入框?el.focus() ?)、初始化第三方庫(基于 DOM 渲染圖表、地圖 ) |
beforeUpdate | 元素所在組件更新前調用(組件數據變化,虛擬 DOM 準備對比更新,真實 DOM 還未改變) | 可在更新前記錄元素狀態(比如舊的滾動位置、樣式 ),用于更新后恢復或對比 |
updated | 元素所在組件更新后調用(虛擬 DOM 對比完成,真實 DOM 已更新) | 基于新的 DOM 狀態做調整(比如重新計算元素尺寸、更新第三方庫的配置 ) |
beforeUnmount | 元素所在組件卸載前調用(組件即將銷毀,DOM 還未移除) | 清除定時器、移除自定義事件監聽(避免內存泄漏),比如?el.removeEventListener |
unmounted | 元素所在組件卸載后調用(DOM 已從頁面移除) | 收尾工作,比如釋放第三方庫占用的資源、徹底清理與該元素相關的全局狀態 |
2. 核心參數說明(以?mounted(el, binding, vnode, prevVnode)
?為例)
el
:指令綁定的真實 DOM 元素,可直接操作(如?el.style.color = 'red'
?)。binding
:指令的綁定信息,包含:binding.value
:指令的參數值(如?v-my-directive="100"
?里的?100
?)。binding.arg
:指令的參數(如?v-my-directive:foo
?里的?foo
?)。binding.modifiers
:指令的修飾符(如?v-my-directive.bar
?里的?{ bar: true }
?)。
vnode
:Vue 生成的虛擬 DOM 節點(描述當前元素的虛擬結構,一般用于高級場景,如對比新舊虛擬節點差異 )。prevVnode
:上一次的虛擬 DOM 節點(僅在更新 / 卸載階段存在,用于對比變化 )。
3. 實際場景示例(用?v-focus
?指令理解鉤子)
需求:定義一個?v-focus
?指令,讓輸入框插入 DOM 后自動聚焦。
<script setup>
import { createApp } from 'vue'const app = createApp(App)// 注冊全局指令
app.directive('focus', {// 元素插入 DOM 后執行(此時可操作真實 DOM)mounted(el) {el.focus() // 讓輸入框自動聚焦}
})
</script><template><!-- 使用指令 --><input v-focus placeholder="自動聚焦的輸入框" />
</template>
如果需要在組件更新后,根據條件重新聚焦,就可以用?updated
?鉤子:
app.directive('focus', {mounted(el) {el.focus()},updated(el, binding) {// 如果指令參數為 true,更新后重新聚焦if (binding.value) {el.focus()}}
})
在模板中動態控制:
<input v-focus="shouldFocus" placeholder="動態聚焦" />
4. 為什么需要指令鉤子?
- 精準控制 DOM 生命周期:Vue 是數據驅動的框架,直接操作 DOM 容易和響應式邏輯沖突。指令鉤子讓你在 “安全” 的時機操作 DOM(比如?
mounted
?確保元素已渲染 )。 - 復用 DOM 操作邏輯:把聚焦、權限控制、第三方庫初始化等邏輯封裝到指令,避免在組件里重復寫(比如 10 個輸入框都要自動聚焦,用?
v-focus
?一行解決 )。 - 解耦組件與 DOM 操作:組件只關心業務數據,DOM 操作細節交給指令,代碼更簡潔、職責更清晰。
總結:指令鉤子是 Vue 指令在 DOM 不同生命周期階段的 “切入點”,讓我們能優雅地操作 DOM、復用邏輯,同時避開直接操作 DOM 帶來的風險,是 Vue 拓展 DOM 能力的核心機制之一。
五、圖片懶加載
核心原理:圖片進入視口區才發送資源請求
思路:定義一個全局自定義指令;利用vueUse的useIntersectionObserver判斷元素是否進入視口;進入視口后再給img標簽的src添加url地址
//main.jsimport { useIntersectionObserver } from '@vueuse/core'const app = createApp(App)app.use(createPinia())
app.use(router)app.mount('#app')// 定義全局指令
app.directive('pic-lazy',{mounted(el,binding){//el:指令綁定的元素//binding:指令對象console.log(el,binding)// 監聽元素是否進入視口useIntersectionObserver(el,// 回調函數,isIntersecting 表示是否進入視口([{ isIntersecting }]) => {if (isIntersecting) {// console.log('元素進入視口啦!')// 這里可以寫進入視口后的邏輯,比如加載圖片、統計曝光等// stop() // 如果只想監聽一次,進入視口后可以停止監聽el.src = binding.valueconsole.log(isIntersecting)}},// 可選配置:設置進入視口的“占比”閾值(0 ~ 1),默認 0.1(即 10% 進入視口就算){ threshold: 0.5 })}
})
對于圖片標簽,把原來的<img :src="……" alt="" /> 改為?<img v-pic-lazy="……" alt="" />
六、路由傳參
1、Vue 2 獲取路由參數
在 Vue 2 中,路由參數主要通過?$route
?對象獲取,支持動態路由參數和查詢參數。
1.?動態路由參數(路徑中的參數,如?/user/:id
)
// 路由配置
{path: '/user/:id',component: User
}// 在組件中獲取
export default {computed: {userId() {return this.$route.params.id; // 獲取路徑中的 :id 參數}},created() {console.log(this.$route.params.id); // 直接訪問}
}
2.?查詢參數(URL 中的??key=value
)
// URL: /user?name=john&age=20
export default {computed: {userName() {return this.$route.query.name; // 獲取查詢參數 name},userAge() {return this.$route.query.age; // 獲取查詢參數 age}}
}
2、Vue 3 獲取路由參數
Vue 3 的組合式 API(Composition API)改變了獲取路由參數的方式,需要通過?useRoute
?函數引入路由實例。
1.?動態路由參數
import { useRoute } from 'vue-router';export default {setup() {const route = useRoute();// 方式一:直接在 setup 中使用console.log(route.params.id);// 方式二:定義為響應式數據(推薦)const userId = computed(() => route.params.id);return {userId};}
}
2.?查詢參數
import { useRoute } from 'vue-router';export default {setup() {const route = useRoute();// 獲取查詢參數const userName = computed(() => route.query.name);const userAge = computed(() => route.query.age);return {userName,userAge};}
}
對比
場景 | Vue 2.x | Vue 3.x |
---|---|---|
動態路由參數 | this.$route.params.id | const route = useRoute(); route.params.id |
查詢參數 | this.$route.query.name | const route = useRoute(); route.query.name |
響應式處理 | 通過?watch ?監聽?$route | 使用?computed ?或?watch ?監聽?route |
補充說明
-
Vue 3 中仍可使用?
$route
在 Vue 3 的選項式 API(Options API)中,仍可通過?this.$route
?訪問路由參數,但組合式 API 推薦使用?useRoute
。 -
響應式處理
- 在 Vue 3 中,路由參數變化時,需要使用?
computed
?或?watch
?確保數據響應式更新:import { useRoute, watch } from 'vue-router';setup() {const route = useRoute();// 監聽路由變化watch(() => route.params.id, (newId, oldId) => {console.log('ID 變化:', newId);}); }
- 在 Vue 3 中,路由參數變化時,需要使用?
-
路由配置示例
無論是 Vue 2 還是 Vue 3,路由配置中定義參數的方式相同:// 路由配置(Vue 2/Vue 3 通用) {path: '/user/:id', // 動態路由參數name: 'User',component: User }
總結
- Vue 2:依賴?
this.$route
?對象,在?created
、computed
?等選項中使用。 - Vue 3:通過?
useRoute()
?函數獲取路由實例,在組合式 API 的?setup
?函數中使用,更靈活且類型安全。
七、路由
1、默認請求參數
下面這個接口默認傳的值為1
function getNewthingAPI({params={}}){const {distributionSite='1'}=paramsreturn httpInstance({url:'/home/new',params:{distributionSite}})
}
調用時可以對這個值進行指定
getBannerAPI( {params: { distributionSite: '2' }})
如果不指定,就使用默認值1
getBannerAPI()
2、路由緩存問題
路由緩存問題通常指在單頁應用( Vue、React)中,切換路由后組件狀態未重置或數據未更新,主要原因包括:
- 組件復用機制:路由切換時,若新舊路由使用同一組件(如動態路由?
/:id
),框架可能復用該組件實例,導致組件內的數據、生命周期鉤子(如?created
、mounted
)不重新執行,狀態保留舊值。 - 緩存策略設置:主動使用緩存配置(如 Vue 的?
<keep-alive>
)時,未正確配置緩存范圍,導致不需要緩存的組件被緩存,數據未刷新。 - 數據依賴未更新:組件數據依賴路由參數,但未監聽路由參數變化,參數改變后未觸發數據重新獲取(如 Vue 中未使用?
watch
?或?onBeforeRouteUpdate
?監聽?$route.params
)。 - 異步數據加載時機:數據請求放在?
mounted
?等只執行一次的鉤子中,路由切換后組件復用,鉤子不再觸發,導致新數據未加載。
解決方法
-
禁用不必要的緩存
若無需緩存組件,移除?<keep-alive>
?或通過?exclude
?排除特定組件:<!-- Vue中排除不需要緩存的組件 --> <keep-alive exclude="DetailPage"><router-view /> </keep-alive>
-
監聽路由參數變化
在復用組件中,監聽路由參數變化并重新加載數據:// 方法1:使用watch監聽$route watch: {$route(to, from) {if (to.params.id !== from.params.id) {this.loadData(to.params.id); // 重新加載數據}} }// 方法2:使用導航守衛 onBeforeRouteUpdate((to) => {this.loadData(to.params.id); });
-
強制組件重新渲染
通過?key
?使組件實例重新創建:<!-- Vue中給router-view添加key,確保路由變化時重新渲染 --> <router-view :key="$route.fullPath" />
$route.fullPath
?包含完整路由信息,參數變化時?key
?改變,觸發組件重新掛載。 -
調整數據加載時機
將數據請求放在每次路由進入時都執行的鉤子中,onMounted
?結合watch監聽。 -
清除緩存數據
在組件離開時手動重置狀態(如 Vue 的?onBeforeUnmount
?或 React 的?useEffect
?清理函數):// Vue中離開組件時重置數據 onBeforeUnmount() {this.data = null; }