一、常用API
注意:本文專案均使用腳手架為 Vite
1、setup函式
(1)介紹
如果在專案中使用配置項API,那麼寫起來就和vue2的寫法是一樣的;但是如果在專案中寫的是組合式API,那麼元件中所用到的:資料、方法等等,均要配置在setup中。此外,setup()
鉤子也是在元件中使用組合式 API 的入口,通常只在以下情況下使用:
- 需要在非單檔案元件中使用組合式 API 時。
- 需要在基於選項式 API 的元件中整合基於組合式 API 的程式碼時。
(2)基本使用
setup函式的返回值:返回一個物件,物件中的屬性、方法,在模板中均可以直接使用。setup函式中是預設不帶響應式的,需要使用ref或reactive包裹。
<template>
<div class="home">
<h2>姓名:{{ name }}</h2>
<h2>年齡:{{ age }}</h2>
<button @click="handleAdd">點選加年齡</button>
<button @click="changeName">點選變彭于晏</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'HomeView',
setup() {
// 1 插值語法
let name = ref('xiao')
// let age=19 // 預設沒有響應式
let age = ref(19) // 做成響應式
// 2 方法--》點選年齡+1
function handleAdd() {
console.log(age) // age 的型別不是數字了,而是RefImpl
age.value += 1 // 讓數字加1 ,需要使用 物件.value
}
function changeName() {
name.value = '彭于晏'
}
// 必須return--》這樣setup裡面的資料才能在template中使用
return {
name,
age,
handleAdd,
changeName
}
}
}
</script>
注意:
- 儘量不要與Vue2.x配置混用
- Vue2.x配置(data、methos、computed...)中可以訪問到setup暴露的值中的屬性、方法
- 但在setup中不能訪問到Vue2.x配置(data、methos、computed...)
- 如果有重名, setup優先
2、setup需要注意的地方
(1)setup執行的時機
- 在beforeCreate之前執行(一次),此時元件物件還沒有建立;setup函式執行於beforeCreate和created之前,也就是說setup函式里面無法使用data和methods方法中的資料。
- this是undefined,不能透過this來訪問data/computed/methods /props;
- 其實所有的組合式API 相關的回撥函式中也都不可以。
(2)setup的返回值
- 一般都返回一個物件:為模板提供資料,也就是模板中可以直接使用此物件中的所有屬性/方法;
- 返回物件中的屬性會與data函式返回物件的屬性合併成為元件物件的屬性;
- 返回物件中的方法會與methods中的方法合併成功元件物件的方法;
- 如果有重名,setup優先;
- 注意:一般不要混合使用:methods中可以訪問setup提供的屬性和方法,但在setup方法中不能訪問data和methods;
- setup不能是一個async函式:因為返回值不再是return的物件,而是promise,模板看不到return物件中的屬性資料
(3)setup的引數
setup(props, context) / setup(props, (attrs, slots, emiti);
- props: 包含props配置宣告且傳入了的所有屬性的物件;
- attrs: 包含沒有在props配置中宣告的屬性的物件,相當於 this.$attrs;
- slots: 包含所有傳入的插槽內容的物件, 相當於 this.$slots;
- emit: 用來分發自定義事件的函式 相當於 this.$emit。
(4)元件的屬性
- 只能訪問以下四種:props、attrs、slots、emit
3、ref 和 reactive
ref
用來做 基礎變數[數字,字串,布林]的響應式
reactive
用來做 物件[陣列,字典]的響應式
(1)ref
-
語法:
const xxx = ref(initValue)
- 建立一個包含響應式資料的引用物件(reference物件,簡稱ref物件)。
JS
中運算元據:xxx.value
模板
中讀取資料: 不需要.value
,直接:<div>{{xxx}}</div>
; 因為在模板中訪問從 setup 返回的 ref 時,它會自動淺層解包,因此你無須再在模板中為它寫 .value。
-
備註:
- 接收的資料可以是:基本型別、也可以是物件型別。
- 基本型別的資料:響應式依然是靠
Object.defineProperty()
的get
與set
完成的 - 物件型別的資料:內部 求助 了Vue3.0中的一個新函式——
reactive
函式
<template>
<div class="home">
<h1>setup函式的使用</h1>
{{ name }}--{{ age }}
<br>
<button @click="add">點我年齡+1</button>
<br>
<button @click="handleChange('彭于晏')">點我變彭于晏</button>
</div>
</template>
<script>
import {ref, reactive} from 'vue'
export default {
name: 'HomeView',
setup() {
// vue3多的,vue2沒有,以後建議vue3的程式碼全都寫在這裡,不再寫配置項方式了
// 1 定義變數,跟正常寫js一樣
let name = ref('xiao')
// let age = 19 // 沒有響應式
let age = ref(19) // 有響應式,變成物件了
// 2 定義一個函式,點選按鈕,年齡加一的函式
let add = () => {
// alert('111')
// 讓年齡+1,出問題了,變數確實會變,但是頁面不會變化---》vue3定義的變數,預設不是響應式的
// age++ 自增,就不能這麼寫了
age.value++ //有響應式
console.log(age.value)
}
let handleChange = (n) => {
name.value = n //有響應式
}
// 3 必須要有返回值,是個物件,返回的物件,可以在 模板(template)中使用
return {name, age, add, handleChange}
},
}
</script>
(2)reactive
-
語法:
-
const 代理物件= reactive(源物件)
-
接收一個物件(或陣列),返回一個代理物件(Proxy的例項物件,簡稱proxy物件)
-
運算元據和讀取資料均不需要
.value
-
-
reactive定義的響應式資料是“深層次的”,物件無論多少層,都可以。
-
內部基於 ES6 的 Proxy 實現,透過代理物件操作源物件內部資料進行操作
<template>
<div class="home">
<h1>setup函式的使用</h1>
<p>使用者名稱:{{ userInfo.name }}</p>
<p>年齡:{{ userInfo.age }}</p>
<p>愛好:{{ userInfo.hobby }}</p>
<button @click="handleAdd">點我年齡+1</button>
</div>
</template>
<script>
import {ref, reactive} from 'vue'
export default {
name: 'HomeView',
setup() {
let userInfo = reactive({
name: 'xiao',
age: 19,
hobby: '籃球'
})
let handleAdd = () => {
userInfo.age++
console.log(userInfo)
}
return {userInfo, handleAdd}
},
}
</script>
(3)ref與reactive的對比
- 從定義資料角度對比:
- ref用來定義:基本型別資料
- reactive用來定義:物件(或陣列)型別資料
- 從原理角度對比:
- ref透過Object.defineProperty()的get與set來實現響應式(資料劫持)。
- reactive透過使用Proxy來實現響應式(資料劫持), 並透過Reflect操作源物件內部的資料。
- 從使用角度對比:
- ref定義的資料:運算元據需要.value,讀取資料時模板中直接讀取不需要.value。
- reactive定義的資料:運算元據與讀取資料:均不需要.value。
4、計算屬性-監聽屬性
(1)計算屬性computed
Vue3與Vue2中的計算屬性配置功能是一樣的,不同的是寫法:
- 在Vue2中,computed是透過宣告選項的方式書寫的,在Vue中,宣告選項是指在建立Vue例項時傳入的引數,是一個物件。這個物件可以包含多個屬性和方法,其中包括data、methods、computed、watch等。這些屬性和方法可以用於定義元件的行為和狀態。
- 在Vue3中,computed是透過組合式API的方式書寫的,Vue中的組合式API是一組新的API,它允許我們使用函式而不是宣告選項的方式書寫Vue元件。組合式API包括響應式API、生命週期鉤子、工具函式等,這些API可以讓我們更靈活地組織和複用程式碼,提高程式碼的可讀性和可維護性 。
所以我們在Vue3中使用computed的時候需要先引入
import {computed} from 'vue'
<template>
<h1>計算屬性</h1>
<p>姓:<input type="text" v-model="person.firstName"></p>
<p>名:<input type="text" v-model="person.lastName"></p>
<p>全名:{{ person.fullName }}</p>
<p>全名修改:<input type="text" v-model="person.fullName"></p>
</template>
<script>
import {ref, reactive} from 'vue'
import {computed} from 'vue'
export default {
name: 'App',
setup() {
// 3 計算屬性
const person = reactive({
firstName: '',
lastName: ''
})
// 只有 計算屬性,不修改值的情況
person.fullName = computed(() => {
return person.firstName+person.lastName
})
// 支援修改
person.fullName = computed({
get() {
return person.firstName + person.lastName
},
set(value) {
person.firstName = value.slice(0, 1)
person.lastName = value.slice(1)
},
})
return {person}
},
}
</script>
(2)監聽屬性watch
- Vue2和Vue3中的watch屬性在功能上是一致的。
- 但是要注意兩個小“坑”:
- 監視reactive定義的響應式資料時:oldValue無法正確獲取、強制開啟了深度監視(deep配置失效)。
- 監視reactive定義的響應式資料中某個屬性時:deep配置有效。
在vue3中watch()方法可以幫助我們監聽資料的變化,並按執行一些任務。Vue3中watch接受三個引數,第一個引數是要監聽的響應式資料,第二個引數是回撥函式,第三個引數是配置項。如果需要監聽多個資料,可以在setup函式中使用watch函式多次,每次傳入不同的引數即可。不像vue2中的watch是一個配置項,vue3中的watch是一個方法可以多次呼叫。
情景一:監視ref定義的響應式資料
- 當我們點選按鈕的時候,watch可以監聽到資料的變化。
<template>
<h2>年齡是:{{ age }}</h2>
<button @click="age++">點我年齡增加</button>
</template>
<script>
import {ref, watch} from "vue";
export default {
name: 'App',
setup() {
const age = ref(19)
// 監聽普通
watch(age, (newValue, oldValue) => {
console.log('age變化了', '新值',newValue,'舊值', oldValue)
})
return {age}
}
}
</script>
情景二:監視多個ref定義的響應式資料
const sum = ref(100)
const msg = ref('很好')
function changeSum() {
sum.value += 1
}
const changeMsg = () => {
msg.value = 'asdfas'
}
watch([sum, msg], (newValue, oldValue) => {
console.log('sum或msg變化了', '新值',newValue,'舊值', oldValue)
})
情景三:監視reactive定義的響應式資料
如果加了{immediate:true}
配置項之後表示立即監聽,輸入框中的值還沒有改變就會觸發一次watch方法;
從控制檯列印的資訊,我們可以清晰地看到oldval的值為undefined。這就是我們需要注意的第一點:若watch監視的是reactive定義的響應式資料,則無法正確獲得oldValue!
watch(person, (newValue, oldValue) => {
console.log('person變化了', '新值', newValue, '舊值', oldValue)
}, { immediate: true ,deep:false})
在程式碼中我並沒有寫deep:true
,但是依然可以監聽到person下的age屬性。
而且就算我們在程式碼中關閉深度監聽也是沒有用的,所以這裡就是我們需要注意的第二點:若watch監視的是reactive定義的響應式資料,則強制開啟了深度監視。
情景四:監視reactive定義的響應式資料中的某個屬性
- 如果我們想監聽一個物件中的某一個屬性,我們肯定會輕鬆到想到這個程式碼該怎麼寫。
watch(person.name, (newValue, oldValue) => {
console.log('person變化了', '新值',newValue,'舊值', oldValue)
}, { immediate: true ,deep:false})
-
但這時控制檯會彈出一個警告,簡單翻譯一下就是 : 監視源只能是getter/effect函式、ref、響應物件或這些型別的陣列。通俗的說就是,只能監視一個ref的值或者是reactive物件。
-
所以需要我們這麼寫,正常的寫法是寫一個函式,函式有返回值:
const person = reactive({name: 'xiao', age: 14})
// 2 監聽物件中的某個屬性
watch(() => person.name, (newValue, oldValue) => {
console.log('person.name變化了', '新值',newValue,'舊值', oldValue)
}, { immediate: true ,deep:false})
情景五:監視reactive定義的響應式資料中的某些屬性
- 如果是要監視一個響應式資料的多個屬性,也按照上文寫的監視多個ref定義的響應式資料那樣,將多個屬性寫在一個陣列中,不過每一個屬性都要寫成函式的形式。
watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
console.log('person的age變化了', '新值',newValue,'舊值', oldValue)
},{immediate:true,deep:true})
特殊情況
- 當我們監視一個reactive定義的物件中的某個屬性時,此時deep配置就會生效,然而當我們將deep配置設定為false時,是監聽不到person.age的變化的。
watch(() => person.age, (newValue, oldValue) => {
console.log('person的age變化了','新值',newValue,'舊值', oldValue)
}, { deep: false })
- 不管我們怎麼去修改age對應的屬性值都是監聽不到的。
watchEffect函式
- 當我們使用
watch
監視屬性的時候,需要明確的指出需要監視的是哪個屬性,也要指明監視的回撥函式。 - 而
watchEffect
的工作原理是:不用指定監聽誰,只要watchEffect內部用了某個變數,某個變數傳送變化,就會觸發。
watchEffect(() => {
const x1 = sum.value
const x2 = person.name
console.log('watchEffect配置的回撥執行了')
})
- 在
watchEffect
中,我們將person的name和sum的值賦值給兩個新的變數,證明我們使用了這兩個屬性,所以修改這兩個屬性的值是,就會觸發監聽函式。
(3)總結
-
Computed屬性
-
computed 是一個函式,它返回一個值,該值依賴於元件的資料。當依賴的資料發生改變時,computed 返回的值會自動更新。
-
在 Vue.js 中,我們通常使用 computed 來封裝複雜的邏輯或計算屬性,使得我們能夠更加方便地處理這些邏輯,並且保證其響應式的特性。
-
-
Watch屬性
-
watch 是一個物件,它允許我們觀察 Vue 例項的資料。當資料變化時,我們可以執行一些操作。
-
在某些情況下,我們可能需要等待資料改變後執行某些操作,或者在資料改變時執行非同步操作。這種情況下,我們可以使用 watch。
-
5、生命週期
- vue3生命週期流程圖
(1)Vue2.X和Vue3.X對比
vue2 -------> vue3配置項 -------> vue3組合式
beforeCreate --------> beforeCreate -------> setup(()=>{})
created --------> created -------> setup(()=>{})
beforeMount --------> beforeMount -------> onBeforeMount(()=>{})
mounted --------> mounted -------> onMounted(()=>{})
beforeUpdate --------> beforeUpdate -------> onBeforeUpdate(()=>{})
updated --------> updated -------> onUpdated(()=>{})
beforeDestroy --------> beforeUnmount -------> onBeforeUnmount(()=>{})
destroyed --------> unmounted -------> onUnmounted(()=>{})
(2)配置項API生命週期
- beforeCreate:beforeCreate鉤子用於在例項被建立之前執行邏輯。
- created:created鉤子用於在例項建立完成後執行邏輯。
- beforeMount:beforeMount鉤子在掛載之前執行。
- mounted:mounted鉤子在掛載完成後執行。
- beforeUpdate:beforeUpdate鉤子在資料更新之前執行。
- updated:updated鉤子在資料更新完成後執行。
- beforeUnmount:beforeUnmount鉤子在元件解除安裝之前執行。
- unmounted:unmounted鉤子在元件解除安裝完成後執行。
(3)組合式API生命週期
- setup() : 開始建立元件,在 beforeCreate 和 created 之前執行,建立的是 data 和 method;
- onBeforeMount() : 元件掛載到節點上之前執行的函式;
- onMounted() : 元件掛載完成後執行的函式;
- onBeforeUpdate(): 元件更新之前執行的函式;
- onUpdated(): 元件更新完成之後執行的函式;
- onBeforeUnmount(): 元件解除安裝之前執行的函式;
- onUnmounted(): 元件解除安裝完成後執行的函式
(4)示例
<template>
<div class="home">
<h1>生命週期鉤子</h1>
<h3>年齡是:{{ age }}</h3>
<button @click="addAge">點選age+1</button>
</div>
</template>
<script>
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
export default {
name: 'HomeView',
setup() {
// 生命週期鉤子
// 1 寫在這裡是就是beforeCreate
console.log('beforeCreate')
const age=ref(19)
function addAge(){
age.value++
}
//2 寫在這裡是就是created
console.log('created',age.value)
//3 beforeMount-->onBeforeMount
// onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted
onBeforeMount(()=>{
console.log('onBeforeMount','元件掛載前')
})
//4 mounted-->onMounted
onMounted(()=>{
console.log('onMounted','元件掛載後')
})
//5 beforeUpdate-->onBeforeUpdate
onBeforeUpdate(()=>{
console.log('onBeforeUpdate','更新之前')
})
//6 updated-->onUpdated
onUpdated(()=>{
console.log('onUpdated','更新之後')
console.log(age.value)
})
//7 beforeUnmount-->onBeforeUnmount
onBeforeUnmount(()=>{
console.log('onBeforeUnmount','銷燬之前')
})
//8 unmounted-->onUnmounted
onUnmounted(()=>{
console.log('onUnmounted','銷燬後')
})
return {age,addAge}
},
}
</script>
6、toRef 和 toRefs
(1)toRef
作用:
建立一個ref物件,其value值指向另一個物件中的某個屬性值,與原物件是存在關聯關係的。也就是基於響應式物件上的一個屬性,建立一個對應的ref,這樣建立的ref與它的源屬性是保持同步的,與源物件存在引用關係,改變源屬性的值將更新ref的值。
語法:
const 變數名 = toRef(源物件,源物件下的某個屬性)
如:const name = toRef(person,'name')
使用:
要將響應式物件中的某個屬性單獨提供給外部使用時,但是不想丟失響應式,把一個prop的ref傳遞給一個組合式函式也會很有用。
缺點:
toRef()
只能處理一個屬性,但是toRefs(源物件)
卻可以一次性批次處理
示例:
<template>
<div class="home">
<h1>toRef函式</h1>
{{data}}
<br>
{{ name }}---{{ age }}
<button @click="handleChangeAttrs">點我看控制檯</button>
</div>
</template>
<script>
import {
ref,
toRef,
reactive,
} from 'vue'
export default {
name: 'HomeView',
setup() {
let data = reactive({
name: 'xiao',
age: 19,
hobby: '籃球'
})
// 錯誤示範
const { name, age} = person;
const { web,trade} = person.job;
// 這樣直接運算元據是無法修改的,因為它不是一個響應式資料,只是一個純字串,不具備響應式
function handleChangeAttrs() {
name = "itclanCoder";
age = 20;
// 正確寫法
// 想要修改指定哪個物件具備響應式,那麼就使用toRef函式處理,toRef(源物件,源物件下的某個屬性)
const name = toRef(data, 'name')
// 使用ref與toRef對比
const age = ref(data.age)
function handleChangeAttrs() {
name.value = "劉德華";
age.value = 20;
console.log(name)
console.log(age)
}
return {name, age, handleChangeAttrs}
},
}
</script>
toRef與ref的不同:
如果你用ref
處理資料的話,如下所示,使用ref
處理資料,頁面也能實現資料的響應式,更新,但是它與toRef
是不同,有區別的,因為ref修改資料,頁面資料會更新,但是源資料不會同步,修改,並無引用關係,ref
相當於是對源物件重新複製一份資料 ref()
接收到的是一個純數值。
(2)toRefs
作用:
toRef()
只能處理源物件指定的某個屬性,如果源物件屬性很多,一個一個的使用toRef()
處理會顯得比較麻煩,那麼這個toRefs()
就很有用了,它與toRef()
的功能一致,可以批次建立多個ref
物件,並且能與源物件保持同步,有引用關係
語法:
toRefs(源物件)
如:toRefs(person)
使用:
當從組合式函式中返回響應式物件時,toRefs
是很有用的。使用它,消費者元件可以解構/展開返回的物件而不會失去響應性。
示例:
<template>
<div class="home">
<h1>toRefs</h1>
<h2>{{ name }}---{{ age }}</h2>
<button @click="age++">點選年齡+1</button>
<button @click="addAge">點選年齡+2</button>
<br>
<button @click="handleShow">看控制檯</button>
</div>
</template>
<script>
import {ref, reactive, toRefs} from 'vue'
export default {
name: 'HomeView',
setup() {
// toRefs
let person = reactive({name: 'xiao', age: 19})
function addAge() {
person.age += 2
console.log(person)
}
function handleShow() {
console.log(person)
}
// return {name:ref(person.name), age:ref(person.age),addAge, handleShow}
return {...toRefs(person),addAge, handleShow}
},
}
</script>
注意事項:
toRefs
在呼叫時只會為源物件上可以列舉的屬性建立ref
。如果要為可能還不存在的屬性建立 ref
,則改用 toRef
。
二、setup寫法
1、簡單介紹
- 元件,只需要匯入,就會自動註冊
- setup寫法
<script setup>
寫原來setup函式中的程式碼即可</script>
- 生命週期鉤子--created
- 監聽屬性,計算屬性
- 元件間通訊--父傳子
- 元件通訊--子傳父
- 插槽
- mixin 沒了==>直接匯入匯出用
- 外掛也是一樣
- toRefs-->把物件中所有變數都做成響應式
- toRef -->只把物件中某一個做成響應式
- ref屬性
2、具體使用
(1)App.vue
<script setup>
// 以後,只要再 這樣寫[ <script setup> ] ,script就是setup函式中的
// 定義的變數和函式,不需要return,以後,就不再寫配置項了
// 1 元件,只需要匯入,就會自動註冊
import HelloWorld from './components/HelloWorld.vue'
import Child from "./components/Child.vue";
// 2 setup寫法
import {ref, reactive, computed, toRefs, toRef} from "vue";
import Child2 from "./components/Child2.vue";
const name = ref('xiao')
function changeName() {
name.value = '彭于晏'
}
// 3 生命週期鉤子--created
console.log('created')
// 4 監聽屬性,計算屬性
const newName = computed(() => {
return name.value + '_NB'
})
// 5 元件間通訊 父傳子
const message = ref('hello world 元件你好')
// 6 元件通訊,子傳父
const child_name = ref('')
function handleEvent(name) {
child_name.value = name
}
// 7 插槽
// 8 mixin 沒了-->直接匯入匯出用
import utils from "./utils/index.js";
let a = utils.add(4, 5)
console.log(a)
// 9 外掛一樣
// 10 toRefs-->把物件中所有變數都做成響應式
const person = reactive({name1: 'xiao', age1: 19})
let {name1, age1} = toRefs(person) // 等同於:name:ref(person.name) age:ref(person.age)
// let {name1, age1} = person // 等同於: name1=lqz age1=19
console.log(typeof person.name1)
console.log(typeof name1)
name1.value='sss'
// 11 toRef -->只把物件中某一個做成響應式
const person1 = reactive({name2: 'xiao', age2: 19})
//const name=toRefs(person) //{name:ref(name),age:ref(age)}
const name2 = toRef(person, 'name2') //name=ref(person.name)
function change() {
name2.value = 'xxx'
}
// 12 ref屬性-->注意要元件掛載完後才能拿到child3 值
import Child3 from "./components/Child3.vue";
const child3=ref() // 代指 this.$refs.child3 ,這個地方變數名必須跟在元件上定義的名字一致,放在元件上的ref是child3
// created--->還沒掛載---》元件還沒有
function showLog(){
console.log(child3.value) // child3.value拿到元件物件
child3.value.changeAge() // 使用元件物件的屬性和方法---》vue3---》不能直接使用,需要子元件暴露---》子元件中:defineExpose({age,changeAge})---》只能用子元件暴露的
console.log(child3.value.age)
}
</script>
<template>
<h1>setup寫法</h1>
<h2>{{ name }}</h2>
<button @click="changeName">點我變名字</button>
<h2>計算屬性newName:{{ newName }}</h2>
<hr>
<h1>父傳子-自定義屬性</h1>
<HelloWorld :msg="message"></HelloWorld>
<h1>子傳父-自定義事件</h1>
<h2>子元件傳過來的:{{ child_name }}</h2>
<Child @myevent="handleEvent"></Child>
<h1>插槽</h1>
<Child2>
<template v-slot:a>
<div>我是a</div>
</template>
<template v-slot:b>
<div>我是bbb</div>
</template>
</Child2>
<h1>ref屬性-放在元件上</h1>
<Child3 ref="child3"></Child3>
<button @click="showLog">點我看控制檯</button>
</template>
<style></style>
(2)父子通訊父傳子==> HelloWorld.vue
<script setup>
// 父傳子,接受父傳入的變數
// 1 陣列形式
// defineProps(['msg'])
// 2 物件形式
defineProps({
msg: String,
})
</script>
<template>
<h1>{{ msg }}</h1>
</template>
(3)父子通訊子傳父==> Child.vue
<script setup>
import {ref} from "vue";
let $emit = defineEmits(['myevent']) // 等同於之前的 this.$emit
const name = ref('')
function handleSend() {
$emit('myevent', name.value)
}
</script>
<template>
<input type="text" v-model="name">-->{{ name }}-->
<button @click="handleSend">點我,傳到父</button>
</template>
<style scoped>
</style>
(4)插槽使用==> Child2.vue
<script setup>
</script>
<template>
<h2>child2</h2>
<slot name="a"></slot>
<h2>換行</h2>
<slot name="b"></slot>
</template>
<style scoped>
</style>
(5)ref屬性==> Child3.vue
<script setup>
import {ref} from "vue";
const age=ref(0)
function changeAge(){
age.value+=10
}
defineExpose({age,changeAge}) // 在子元件中暴露
</script>
<template>
<h1>ref屬性使用</h1>
</template>
<style scoped>
</style>
三、axios使用
1、簡單介紹
(1)什麼是axios?
axios是一個流行的基於Promise的HTTP客戶端,可以在瀏覽器和Node.js環境中使用。它允許您在應用程式中進行HTTP請求,從而與後端伺服器進行資料交換。
(2)axios的功能
-
axios的返回結果是一個promise例項物件
-
他的回撥不同於promise的value和reason分別叫做response和err
-
axios的成功值是一個axios封裝的response物件.伺服器返回的真正資料在response.data中
-
axios需要攜帶query引數的話要寫在params中,但是params引數只能寫在請求地址中
2、vue3實現載入電影案例
(1)安裝
npm install axios -S
(2)匯入
import axios from "axios";
(3)使用
// 相當於寫在了created中--》頁面載入完,就傳送請求
axios.get('自己地址').then(res => {
console.log(res)
})
(4)axios普通使用
<script setup>
import axios from "axios";
import {reactive} from "vue";
const filmList = reactive({})
// 相當於寫在了created中--》頁面載入完,就傳送請求
// 普通使用
axios.get('http://127.0.0.1:8000/api/v1/film/').then(res => {
console.log(res.data)
if(res.data.code==100){
// 載入成功了-->把返回的資料,放到變數中
filmList.result=res.data.results // 能賦值,但是不是響應式
console.log('---',filmList)
}else{
alert(res.data.msg)
}
})
</script>
<template>
<h1>顯示電影案例</h1>
<div v-for="item in filmList.result">
<h3>{{ item.name }}</h3>
<img :src="item.poster" alt="" height="300px" width="250px">
</div>
</template>
<style></style>
(5)高階使用
<script setup>
import axios from "axios";
import {reactive} from "vue";
const filmList = reactive([])
// 高階使用 Object.assign--》copy-》把一個物件copy到另一個物件身上
axios.get('http://127.0.0.1:8000/api/v1/film/').then(res => {
console.log(res.data)
if (res.data.code == 100) {
// (1) 直接把res.data.results 複製到filmList.result
Object.assign(filmList,res.data.results)
// (2) 解構賦值
let {data}=res // res={data:{code:100,msg:成功}}
Object.assign(filmList,data.results)
// (3) 解構賦值
let {data: {results}} = res
Object.assign(filmList, results)
// (4) 解構賦值
let {data} = res // {code:100,msg:成功,results:[]}
Object.assign(filmList, data.results)
} else {
alert(res.data.msg)
}
})
</script>
<template>
<h1>顯示電影案例</h1>
<div v-for="item in filmList">
<h3>{{ item.name }}</h3>
<img :src="item.poster" alt="" height="300px" width="250px">
</div>
</template>
<style></style>
3、async和await
(1)async/await是什麼?
async
關鍵字用於定義一個非同步函式,表示該函式是一個協程(coroutine)。await
關鍵字用於暫停非同步函式的執行,等待另一個非同步操作完成。
(2)async和await的基礎使用
-
async 表示這是一個async函式, await只能用在async函式里面,不能單獨使用;
-
async 返回的是一個Promise物件,await就是等待這個promise的返回結果後,再繼續執行;
-
await 等待的是一個Promise物件,後面必須跟一個Promise物件,但是不必寫then(),直接就可以得到返回值。
(3)async/await的特點
- Async作為關鍵字放在函式前面,普通函式變成了非同步函式;
- 非同步函式async函式呼叫,跟普通函式呼叫方式一樣。在一個函式前面加上async,變成 async函式,非同步函式,return:1,列印返回值;
- 返回的是promise成功的物件;
- Async函式配合await關鍵字使用。
(4)載入電影案例改寫
<script setup>
import axios from "axios";
import {reactive} from "vue";
const filmList = reactive({})
async function load() {
// response--》就是原來then中的res
// let response= await axios.get('http://127.0.0.1:8000/api/v1/films/')
// data --》就是原來then中的res.data
// 正常返回的then的給了response--》原來catch的會被異常捕獲
let {data} = await axios.get('http://127.0.0.1:8000/api/v1/film/')
console.log(data)
Object.assign(filmList, data.results)
}
load()
</script>
<template>
<h1>顯示電影案例</h1>
<div v-for="item in filmList.result">
<h3>{{ item.name }}</h3>
<img :src="item.poster" alt="" height="300px" width="250px">
</div>
</template>
<style></style>
4、axios其它配置項
(1)常用配置項
- GET請求
//完整版寫法
const res = axios({
url:'http://localhost:5000/persons',//請求地址
methods:'GET'//請求方式
params:{id:...}//query引數傳送方式
})
log(res)//axios返回值是一個promise例項
res.then(
response => {log(response.data)}
err => {log(err)}
)
//精簡版寫法
axios.get('http:.......',{params:{id:...}}).then(
response =>{}
err =>{}
)
//只要成功的寫法
const res = await axios.get('http:/...')
- POST請求
//完整版
axios({
url:'http://...',
methods:'POST',
data:{name:...,age:...}//json格式的引數
data:`name=..&age=..`//urlencoded格式的引數
})
//精簡版
axios.post('http:...',{name:..,age:..}).then(
response => {}
err => {}
)
- 配置預設屬性
axios({
url:'地址',
method:'post',
headers: {'token': 'adsfa.adsfa.adsf',contentType:'application/json'},
params: {name: xiao, age:19},
data: {firstName: 'xxx'},
timeout: 1000,
})
// 或者這麼寫
axios.baseURL = 'http://...' //URL一定是大寫
axios.defaults.timeout = 2000
axios.defsults.headers = {'token': 'adsfa.adsfa.adsf',contentType:'application/json'}
(2)其他配置項
// 更多引數
{
//1 `url` 是用於請求的伺服器 URL
url: '/user',
//2 `method` 是建立請求時使用的方法
method: 'get', // 預設值
//3 `baseURL` 將自動加在 `url` 前面,除非 `url` 是一個絕對 URL。
// 它可以透過設定一個 `baseURL` 便於為 axios 例項的方法傳遞相對 URL
baseURL: 'https://some-domain.com/api/',
//4 `transformRequest` 允許在向伺服器傳送前,修改請求資料
// 它只能用於 'PUT', 'POST' 和 'PATCH' 這幾個請求方法
// 陣列中最後一個函式必須返回一個字串, 一個Buffer例項,ArrayBuffer,FormData,或 Stream
// 你可以修改請求頭。
transformRequest: [function (data, headers) {
// 對傳送的 data 進行任意轉換處理
return data;
}],
// transformResponse 在傳遞給 then/catch 前,允許修改響應資料
transformResponse: [function (data) {
// 對接收的 data 進行任意轉換處理
return data;
}],
//5 自定義請求頭
headers: {'X-Requested-With': 'XMLHttpRequest'},
//6 params` 是與請求一起傳送的 URL 引數
// 必須是一個簡單物件或 URLSearchParams 物件
params: {
ID: 12345
},
// 7 aramsSerializer`是可選方法,主要用於序列化`params`
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
//8 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',
// 0imeout` 指定請求超時的毫秒數。
// 如果請求時間超過 `timeout` 的值,則請求會被中斷
timeout: 1000, // 預設值是 `0` (永不超時)
// 11 thCredentials` 表示跨域請求時是否需要使用憑證
withCredentials: false, // default
// 12 dapter` 允許自定義處理請求,這使測試更加容易。
// 返回一個 promise 並提供一個有效的響應 (參見 lib/adapters/README.md)。
adapter: function (config) {
/* ... */
},
// 13 auth` HTTP Basic Auth
auth: {
username: 'xiao'
password: '123‘
},
// 14 `responseType` 表示瀏覽器將要響應的資料型別
// 選項包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
// 瀏覽器專屬:'blob'
responseType: 'json', // 預設值
// 15 `responseEncoding` 表示用於解碼響應的編碼 (Node.js 專屬)
// 注意:忽略 `responseType` 的值為 'stream',或者是客戶端請求
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // 預設值
// 16 `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名稱
xsrfCookieName: 'XSRF-TOKEN', // 預設值
// 17 `xsrfHeaderName` 是帶有 xsrf token 值的http 請求頭名稱
xsrfHeaderName: 'X-XSRF-TOKEN', // 預設值
// 18 `onUploadProgress` 允許為上傳處理進度事件
// 瀏覽器專屬
onUploadProgress: function (progressEvent) {
// 處理原生進度事件
},
// 19 `onDownloadProgress` 允許為下載處理進度事件
// 瀏覽器專屬
onDownloadProgress: function (progressEvent) {
// 處理原生進度事件
},
// 20 `maxContentLength` 定義了node.js中允許的HTTP響應內容的最大位元組數
maxContentLength: 2000,
// 21 `maxBodyLength`(僅Node)定義允許的http請求內容的最大位元組數
maxBodyLength: 2000,
// 22 `validateStatus` 定義了對於給定的 HTTP狀態碼是 resolve 還是 reject promise。
// 如果 `validateStatus` 返回 `true` (或者設定為 `null` 或 `undefined`),
// 則promise 將會 resolved,否則是 rejected。
validateStatus: function (status) {
return status >= 200 && status < 300; // 預設值
},
// 23 `maxRedirects` 定義了在node.js中要遵循的最大重定向數。
// 如果設定為0,則不會進行重定向
maxRedirects: 5, // 預設值
// 24 `socketPath` 定義了在node.js中使用的UNIX套接字。
// e.g. '/var/run/docker.sock' 傳送請求到 docker 守護程序。
// 只能指定 `socketPath` 或 `proxy` 。
// 若都指定,這使用 `socketPath` 。
socketPath: null, // default
// 25 `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 }),
// 26 `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: 'xiao',
password: '123'
}
},
// 27 see https://axios-http.com/zh/docs/cancellation
cancelToken: new CancelToken(function (cancel) {
}),
// 28 `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 // 預設值
}
5、axios請求響應攔截器
axios請求響應攔截器是axios提供的一個重要功能,它可以在我們傳送請求或接收響應時進行處理。透過攔截器,我們可以在請求或響應被處理前對其進行修改、日誌記錄或新增額外的處理邏輯。
在axios中,您可以透過axios.interceptors.request
和axios.interceptors.response
來新增請求和響應攔截器。這兩個方法都接受兩個回撥函式作為引數,一個用於處理成功的情況,另一個用於處理錯誤的情況。
下面是一個簡單的示例,演示瞭如何使用axios的攔截器:
// 新增請求攔截器
axios.interceptors.request.use(function (config) {
// 在傳送請求之前做些什麼
console.log('請求攔截器被觸發');
return config;
}, function (error) {
// 對請求錯誤做些什麼
return Promise.reject(error);
});
// 新增響應攔截器
axios.interceptors.response.use(function (response) {
// 對響應資料做點什麼
console.log('響應攔截器被觸發');
return response;
}, function (error) {
// 對響應錯誤做點什麼
return Promise.reject(error);
});
在上面的示例中,我們使用axios.interceptors.request.use
新增了一個請求攔截器,它在每次傳送請求之前被觸發。類似地,使用axios.interceptors.response.use
新增了一個響應攔截器,它在每次接收到響應後被觸發。
此外,我們還可以在攔截器中進行各種操作,例如新增請求頭、記錄日誌、對響應資料進行處理等。這使得axios擁有了更高的靈活性和可定製性,能夠滿足各種複雜的需求。
四、promise語法
1、普通函式和回撥函式
(1)普通函式
普通函式是最常見的函式型別,就是可以被正常呼叫的函式,一般函式執行完畢後才會繼續執行下一行程式碼。普通函式可以接受引數並返回一個值。例如:
<script>
let fun1 = () =>{
console.log("fun1 執行了")
}
// 呼叫函式
fun1()
// 函式執行完畢,繼續執行後續程式碼
console.log("其他程式碼繼續執行")
</script>
(2)回撥函式
回撥函式是作為引數傳遞給其他函式的函式,表示未來才會執行的一些功能,後續程式碼不會等待該函式執行完畢就開始執行了,用於在某個操作或事件完成後執行。回撥函式通常用於處理非同步操作,例如在非同步請求完成後執行某些操作。例如:
function fetchData(callback) {
// 設定一個2000毫秒後會執行一次的定時任務,基於事件自動呼叫,console.log先執行
setTimeout(() => {
const data = 'Some data';
callback(data);
}, 2000);
}
function processData(data) {
console.log('Data received:', data);
}
fetchData(processData);
在這個例子中,fetchData
函式是一個模擬的非同步操作,它接受一個回撥函式作為引數,在非同步操作完成後呼叫該回撥函式並傳遞資料。processData
函式作為回撥函式傳遞給fetchData
,當資料準備就緒時會被呼叫。
回撥函式常用於處理事件處理、非同步請求、定時器等場景,可以使程式碼更加靈活和可擴充套件,但也容易導致回撥地獄(callback hell)問題,使程式碼難以閱讀和維護。
總的來說,普通函式和回撥函式都是JavaScript中常見的函式型別,普通函式用於一般的函式呼叫和返回值,而回撥函式用於在某個操作完成後執行特定的邏輯。
2、promise基本使用(用來處理回撥函式)
在JavaScript中,Promise是一種用於處理非同步操作的物件,它代表了一個非同步操作的最終完成或失敗,並返回結果值。Promise有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。
就比如現實生活中你跟你女朋友說,5年後等我賺夠500w就結婚 ==> 定義函式
- 進行中(努力賺錢,其他程式碼繼續執行)
- 成功(賺夠500w ==> 結婚)
- 失敗(沒賺夠 ==> 分手)
下面是Promise的基本語法:
// 建立一個Promise物件
const myPromise = new Promise((resolve, reject) => {
// 非同步操作
if (/* 非同步操作成功 */) {
resolve('成功時的結果');
} else {
reject('失敗時的原因');
// 主動拋異常,也是執行失敗
throw new Error("error message")
}
});
// 使用Promise物件
myPromise.then((result) => {
// 當Promise狀態變為fulfilled時呼叫,result為成功時的結果
console.log(result);
}).catch((error) => {
// 當Promise狀態變為rejected時呼叫,error為失敗時的原因
console.log(error);
});
在上面的示例中,我們首先建立了一個Promise物件myPromise
,在Promise的建構函式中傳入一個執行器函式,該函式接受兩個引數resolve
和reject
,分別用於將Promise的狀態從pending改變為fulfilled(成功)或rejected(失敗)。
在Promise物件建立後,我們可以使用.then()
方法來處理成功狀態下的結果,使用.catch()
方法來處理失敗狀態下的原因。
Promise的語法使得非同步操作的處理變得更加直觀和易於管理,避免了回撥地獄(callback hell)的問題,使得程式碼更加清晰和可讀。
此外,值得注意的是我在上面提到的axios返回的也是一個promise物件
3、async和await的使用
- 在上面的async和await簡單介紹中,瞭解到async 返回的是一個Promise物件,await就是等待這個promise的返回結果後,再繼續執行;所以promise物件肯定支援 async和await 寫法
// async 和await 寫法
// async標識函式後,async函式的返回值會變成一個promise物件
async function demo01() {
let promise = new Promise(function (resolve, reject) {
// resolve,reject 是兩個函式
console.log("promise 開始執行")
// resolve("promise 執行成功")
// reject("promise 執行失敗")
// 主動拋異常,也是執行失敗
throw new Error("error message")
})
return promise
}
console.log('11111')
// await 關鍵字,必須寫在async修飾的函式中
async function demo02() {
try {
let res = await demo01() // 正常呼叫,返回promise 物件,加await 呼叫--》返回正常then的資料
console.log(res)
} catch (err) {
console.log('出錯了')
}
}
demo02() // 它會等正常執行完成才會呼叫
console.log('222222')
五、vue3中的vue-router
- 官網文件:介紹 | Vue Router (vuejs.org)
1、基本使用
(1)安裝
- 在vue3中需要安裝vue-router4版本的,所以安裝的時候需要我們指定版本
npm install -S vue-router@4
cnpm install vue-router@4 --save
(2)註冊
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router";
import AboutView from "../view/AboutView.vue";
import HomeView from "../view/HomeView.vue";
import LoginView from "../view/LoginView.vue";
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about/:id',
name: 'about',
component: AboutView
},
{
path: '/login',
name: 'Login',
component: LoginView
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
(3)main.js中使用
import {createApp} from 'vue'
// 使用vue-router
import router from './router'
import App from './App.vue'
createApp(App).use(router).mount('#app')
(4)補充 鏈式呼叫
鏈式呼叫是一種程式設計風格,通常用於方法呼叫或操作的連續執行。在很多程式語言中,鏈式呼叫透過在一個物件上連續呼叫多個方法來簡化程式碼,並使程式碼更易讀和緊湊。這種方法的返回值通常是一個物件本身,以便可以繼續在其上呼叫其他方法。
以下是一個簡單的示例,演示如何在一個物件上進行鏈式呼叫:
class Calculator:
def __init__(self, value):
self.value = value
def add(self, x):
self.value += x
return self # 返回自身以支援鏈式呼叫
def multiply(self, x):
self.value *= x
return self # 返回自身以支援鏈式呼叫
# 建立一個 Calculator 例項並進行鏈式呼叫
result = Calculator(5).add(3).multiply(4).value
print(result) # 輸出:32
在上面的示例中,Calculator
類具有 add
和 multiply
兩個方法,這兩個方法都返回 self
,以支援鏈式呼叫。透過在例項化後直接在其上連續呼叫這些方法,可以在單行程式碼中實現多個操作。
鏈式呼叫在很多庫和框架中被廣泛應用,例如jQuery中的方法呼叫、Python中的pandas庫等。
2、路由跳轉
(1)普通路由跳轉(宣告式路由)
這種路由實現跳轉的話,to中的內容目前是固定的,點選後只能切換/about物件元件(宣告式路由)
- 寫路徑
<router-link to="/about"></router-link>
(2)程式設計式路由
- 透過useRouter,動態決定向那個元件切換的路由
- 在 Vue 3 和 Vue Router 4 中,你可以使用
useRouter
來實現動態路由(程式設計式路由) - 這裡的
useRouter
方法返回的是一個 router 物件,你可以用它來做如導航到新頁面、返回上一頁面等操作。
(3)案例
- 透過普通按鈕配合事件繫結實現路由頁面跳轉,不直接使用router-link標籤
- HomeView.vue
<script setup>
import {useRouter} from 'vue-router'
let router = useRouter()
function handleTo() {
// 程式設計式路由
// 直接push一個路徑
router.push('/about')
// push一個帶有path屬性的物件
router.push({path:'/about'})
}
localStorage.setItem('token','asdfa.afda.asdf')
</script>
<template>
<h1>首頁</h1>
<h1>頁面跳轉</h1>
<router-link to="/about">
<button>跳轉到about-html跳</button>
</router-link>
<button @click="handleTo">跳轉到about-js跳</button>
<hr>
</template>
<style scoped>
</style>
3、路由傳參(useRoute)
(1)請求地址中以 ? 形式攜帶(鍵值對引數)
- 類似與get請求透過url傳參,資料是鍵值對形式的
- 例如: 檢視資料詳情
/showDetail?hid=1
,hid=1
就是要傳遞的鍵值對引數 - 在 Vue 3 和 Vue Router 4 中,你可以使用
useRoute
這個函式從 Vue 的組合式 API 中獲取路由物件。 useRoute
方法返回的是當前的 route 物件,你可以用它來獲取關於當前路由的資訊,如當前的路徑、查詢引數等。
- 例如: 檢視資料詳情
(2)使用帶引數的路徑
- 請求地址中攜帶,例如:/about/資料/
- 在路由配置中,可以定義帶引數的路徑,透過在路由配置的path中使用
:
來定義引數名稱。
(3)案例
需求:切換到ShowDetail.vue元件時,向該元件透過路由傳遞引數。
- App.vue
<script setup type="module">
import {useRouter} from 'vue-router'
//建立動態路由物件
let router = useRouter()
//動態路由路徑傳參方法
let showDetail= (id,language)=>{
// 嘗試使用拼接字串方式傳遞路徑引數
//router.push(`showDetail/${id}/${languange}`)
/*路徑引數,需要使用params */
router.push({name:"showDetail",params:{id:id,language:language}})
}
let showDetail2= (id,language)=>{
/*uri鍵值對引數,需要使用query */
router.push({path:"/showDetail2",query:{id:id,language:language}})
}
</script>
<template>
<div>
<h1>App頁面</h1>
<hr/>
<!-- 路徑引數 -->
<router-link to="/showDetail/1/JAVA">showDetail路徑傳參顯示JAVA</router-link>
<button @click="showDetail(1,'JAVA')">showDetail動態路由路徑傳參顯示JAVA</button>
<hr/>
<!-- 鍵值對引數 -->
<router-link v-bind:to="{path:'/showDetail2',query:{id:1,language:'Java'}}">showDetail2鍵值對傳參顯示JAVA</router-link>
<button @click="showDetail2(1,'JAVA')">showDetail2動態路由鍵值對傳參顯示JAVA</button>
<hr>
showDetail檢視展示:<router-view name="showDetailView"></router-view>
<hr>
showDetail2檢視展示:<router-view name="showDetailView2"></router-view>
</div>
</template>
<style scoped>
</style>
- 修改router/index.js增加路徑引數佔位符
// 匯入路由建立的相關方法
import {createRouter,createWebHashHistory} from 'vue-router'
// 匯入vue元件
import ShowDetail from '../components/ShowDetail.vue'
import ShowDetail2 from '../components/ShowDetail2.vue'
// 建立路由物件,宣告路由規則
const router = createRouter({
history: createWebHashHistory(),
routes:[
{
/* 此處:id :language作為路徑的佔位符 */
path:'/showDetail/:id/:language',
/* 動態路由傳參時,根據該名字找到該路由 */
name:'showDetail',
components:{
showDetailView:ShowDetail
}
},
{
path:'/showDetail2',
components:{
showDetailView2:ShowDetail2
}
},
]
})
// 對外暴露路由物件
export default router;
- ShowDetail.vue 透過useRoute獲取路徑引數
<script setup type="module">
import{useRoute} from 'vue-router'
import { onUpdated,ref } from 'vue';
// 獲取當前的route物件
let route =useRoute()
let languageId = ref(0)
let languageName = ref('')
// 藉助更新時生命週期,將資料更新進入響應式物件
onUpdated (()=>{
// 獲取物件中的引數
languageId.value=route.params.id
languageName.value=route.params.language
console.log(languageId.value)
console.log(languageName.value)
})
</script>
<template>
<div>
<h1>ShowDetail頁面</h1>
<h3>編號{{route.params.id}}:{{route.params.language}}是世界上最好的語言</h3>
<h3>編號{{languageId}}:{{languageName}}是世界上最好的語言</h3>
</div>
</template>
<style scoped>
</style>
- ShowDetail2.vue透過useRoute獲取鍵值對引數
<script setup type="module">
import{useRoute} from 'vue-router'
import { onUpdated,ref } from 'vue';
// 獲取當前的route物件
let route =useRoute()
let languageId = ref(0)
let languageName = ref('')
// 藉助更新時生命週期,將資料更新進入響應式物件
onUpdated (()=>{
// 獲取物件中的引數(透過query獲取引數,此時引數是key-value形式的)
console.log(route.query)
console.log(languageId.value)
console.log(languageName.value)
languageId.value=route.query.id
languageName.value=route.query.language
})
</script>
<template>
<div>
<h1>ShowDetail2頁面</h1>
<h3>編號{{route.query.id}}:{{route.query.language}}是世界上最好的語言</h3>
<h3>編號{{languageId}}:{{languageName}}是世界上最好的語言</h3>
</div>
</template>
<style scoped>
</style>
4、 路由重定向
路由重定向指的是:使用者在訪問地址 A 的時候,強制使用者跳轉到地址 C ,從而展示特定的元件頁面。 透過路由規則的 redirect 屬性,指定一個新的路由地址,可以很方便地設定路由的重定向。
- path 表示需要被重定向的 “原地址” ;
- redirect 表示將要被重定向到的 “新地址”
// 匯入路由建立的相關方法
import {createRouter,createWebHashHistory} from 'vue-router'
// 匯入vue元件
import Home from '../components/Home.vue'
import List from '../components/List.vue'
// 建立路由物件,宣告路由規則
const router = createRouter({
history: createWebHashHistory(),
routes:[
{
path:'/',
components:{
default:Home,
homeView:Home
}
},
{
path:'/list',
components:{
listView : List
}
},
{
path:'/showAll',
// 重定向
redirect :'/list'
},
]
})
// 對外暴露路由物件
export default router;
5、路由巢狀--多級路由
(1)配置children屬性
語法:
{
path : "/父路徑",
component : 父元件,
children : [{
path : "子路徑",
component : 子元件
}]
}
- 需要我們注意的是:子路徑不能帶 ' / '
- router/index.js
const routes = [
{
path: '/backend',
name: 'home',
component: HomeView,
children: [ //透過children配置子級路由
{
path: 'index', //此處一定不要寫:/news
component: IndexView
},
{
path: 'order',
component: OrderView
},
{
path: 'goods',
component: GoodsView
}
]
},
{
path: '/about/:id',
name: 'about',
component: AboutView
}
]
(2)配置跳轉路徑
語法:
<router-link to="完整路徑">內容</router-link>
-
需要注意的是這裡的完整路徑是從配置路由的第一層路徑開始
-
HomeView.vue
<template>
<div class="home">
<div class="left">
<router-link to="/backend/index"><p>首頁</p></router-link>
<router-link to="/backend/order"><p>訂單管理</p></router-link>
<router-link to="/backend/goods"><p>商品管理</p></router-link>
</div>
<div class="right">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'HomeView',
methods: {}
}
</script>
<style scoped>
.home {
display: flex;
}
.left {
height: 500px;
width: 20%;
background-color: aquamarine;
}
.right {
height: 500px;
width: 80%;
background-color: gray;
}
</style>
(3)命名路由(可以簡化路由的跳轉)
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello' //給路由命名
path:'welcome',
component:Hello,
}
]
}
]
}
<!--簡化前,需要寫完整的路徑 -->
<router-link to="/demo/test/welcome">跳轉</router-link>
<!--簡化後,直接透過名字跳轉 -->
<router-link :to="{name:'hello'}">跳轉</router-link>
<!--簡化寫法配合傳遞引數 -->
<router-link
:to="{
name:'hello',
query:{
id:666,
title:'你好'
}
}"
>跳轉</router-link>
(4)router-link的replace屬性
- 作用:控制路由跳轉時操作瀏覽器歷史記錄的模式
- 瀏覽器的歷史記錄有兩種寫入方式:分別為push和replace,push是追加歷史記錄,replace是替換當前記錄。路由跳轉時候預設為push
- 如何開啟replace模式:News
6、路由守衛
(1)介紹
在 Vue 3 中,路由守衛是用於在路由切換期間進行一些特定任務的回撥函式。路由守衛可以用於許多工,例如驗證使用者是否已登入、在路由切換前提供確認提示、請求資料等。Vue 3 為路由守衛提供了全面的支援,並提供了以下幾種型別的路由守衛:
- 全域性前置守衛:在路由切換前被呼叫,可以用於驗證使用者是否已登入、中斷導航、請求資料等。
- 全域性後置守衛:在路由切換之後被呼叫,可以用於處理資料、操作 DOM 、記錄日誌等。
- 守衛程式碼的位置: 在router.js中
//全域性前置路由守衛
router.beforeEach( (to,from,next) => {
//to 是目標地包裝物件 .path屬性可以獲取地址
//from 是來源地包裝物件 .path屬性可以獲取地址
//next是方法,不呼叫預設攔截! next() 放行,直接到達目標元件
//next('/地址')可以轉發到其他地址,到達目標元件前會再次經過前置路由守衛
console.log(to.path,from.path,next)
//需要判斷,注意避免無限重定向
if(to.path == '/index'){
next()
}else{
next('/index')
}
} )
//全域性後置路由守衛
router.afterEach((to, from) => {
console.log(`Navigate from ${from.path} to ${to.path}`);
});
(2)案例
登入案例,登入以後才可以進入home,否則必須進入login
- 定義Login.vue
<script setup>
import {ref} from 'vue'
import {useRouter} from 'vue-router'
let username =ref('')
let password =ref('')
let router = useRouter();
let login = () =>{
console.log(username.value,password.value)
if(username.value == 'root' & password.value == '123456'){
router.push({path:'/home',query:{'username':username.value}})
//登入成功利用前端儲存機制,儲存賬號!
localStorage.setItem('username',username.value)
//sessionStorage.setItem('username',username)
}else{
alert('登入失敗,賬號或者密碼錯誤!');
}
}
</script>
<template>
<div>
賬號: <input type="text" v-model="username" placeholder="請輸入賬號!"><br>
密碼: <input type="password" v-model="password" placeholder="請輸入密碼!"><br>
<button @click="login()">登入</button>
</div>
</template>
<style scoped>
</style>
- 定義Home.vue
<script setup>
import {ref} from 'vue'
import {useRoute,useRouter} from 'vue-router'
let route =useRoute()
let router = useRouter()
// 並不是每次進入home頁時,都有使用者名稱引數傳入
//let username = route.query.username
let username =window.localStorage.getItem('username');
let logout= ()=>{
// 清除localStorge中的username
//window.sessionStorage.removeItem('username')
window.localStorage.removeItem('username')
// 動態路由到登入頁
router.push("/login")
}
</script>
<template>
<div>
<h1>Home頁面</h1>
<h3>歡迎{{username}}登入</h3>
<button @click="logout">退出登入</button>
</div>
</template>
<style scoped>
</style>
- App.vue
<script setup type="module">
</script>
<template>
<router-view></router-view>
</template>
<style scoped>
</style>
- 定義routers.js
// 匯入路由建立的相關方法
import {createRouter,createWebHashHistory} from 'vue-router'
// 匯入vue元件
import Home from '../components/Home.vue'
import Login from '../components/login.vue'
// 建立路由物件,宣告路由規則
const router = createRouter({
history: createWebHashHistory(),
routes:[
{
path:'/home',
component:Home
},
{
path:'/',
redirect:"/home"
},
{
path:'/login',
component:Login
},
]
})
// 設定路由的全域性前置守衛
router.beforeEach((to,from,next)=>{
/*
to 要去那
from 從哪裡來
next 放行路由時需要呼叫的方法,不呼叫則不放行
*/
console.log(`從哪裡來:${from.path},到哪裡去:${to.path}`)
if(to.path == '/login'){
//放行路由 注意放行不要形成迴圈
next()
}else{
//let username =window.sessionStorage.getItem('username');
let username =window.localStorage.getItem('username');
if(null != username){
next()
}else{
next('/login')
}
}
})
// 設定路由的全域性後置守衛
router.afterEach((to,from)=>{
console.log(`從哪裡來:${from.path},到哪裡去:${to.path}`)
})
// 對外暴露路由物件
export default router;
- 啟動測試
npm run dev
7、路由兩種工作模式
在許多現代 JavaScript 框架(如 Vue.js 和 React)中,前端路由器用於管理應用程式的 URL,並在 URL 發生變化時載入不同的元件或頁面內容。路由歷史物件負責記錄使用者在應用程式中瀏覽的歷史記錄,以便使用者可以使用瀏覽器的前進和後退按鈕導航。
路由的工作模式一共有兩種:hash模式和history模式。我們可以在建立路由物件的時候對路由的工作模式進行配置,預設是hash模式,下面是vue3中路由工作模式的書寫方式:
createWebHashHistory
:hash模式。createWebHashHistory()
是Vue.js
基於 hash 模式建立路由的工廠函式。在使用這種模式下,路由資訊儲存在 URL 的 hash 中,使用createWebHashHistory()
方法,可以建立一個路由歷史記錄物件,用於管理應用程式的路由。在Vue.js
應用中,通常使用該方法來建立路由的歷史記錄物件。createWebHistory
:history模式。createWebHistory
是一個用於建立路由歷史物件的函式,通常在 Web 應用程式的前端路由中使用。在 Vue.js 中,createWebHistory
函式通常與createRouter
一起使用,用於建立基於 HTML5 History API 的路由歷史物件。
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router";
const router = createRouter({
history: createWebHistory(),
routes,
})
(1)hash模式
- 對於一個url來說,什麼是hash值? ==>
#
及其後面的內容就是hash值。 - hash值不會包含在 HTTP 請求中,即:hash值不會帶給伺服器
https://192.168.1.1/api/v1/user#login
- 地址中永遠帶著#號,不美觀 。
- 若以後將地址透過第三方手機app分享,若app校驗嚴格,則地址會被標記為不合法。
- 但是相容性較好。
因為 #
後面的內容不會當做路徑傳給伺服器,有更強的相容性,不會出現專案部署到伺服器上後重新整理找不到路徑的問題。
(2)history模式
- history模式下的路徑什麼就是正常訪問網站路徑
https://192.168.1.1/api/v1/user/login
- 地址乾淨,美觀
- 相容性和hash模式相比略差。
- 應用部署上線時需要後端人員支援,解決重新整理頁面服務端404的問題
8、路由懶載入
(1)介紹
路由懶載入是一種將路由元件按需非同步載入的方式,只有當路由對應的元件需要使用時,才會動態地載入該元件對應的程式碼。使用路由懶載入可以最佳化應用程式的效能。
-
當我們把專案寫完過後打包出來的JavaScript包會變得非常大,會影響效能。
-
如果把不同的元件分割成不同的程式碼塊,當路由被訪問的時候才載入相應元件,這樣就會更加高效。
-
component: ()=> import("元件路徑");
注意:我們引入元件的步驟被放到了component配置中,所以不需要再引入元件了。
(2)示例
在Vue Router中使用路由懶載入,我們可以透過使用import()
和動態import()
兩種方式來實現
使用import()方式實現懶載入:
const Home = () => import('./views/Home.vue')
const About = () => import('./views/About.vue')
const routes = [
{
path: '/',
component: Home
},
{
path: '/about',
component: About
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
使用動態import()方式實現懶載入:
const routes = [
{
path: '/',
component: () => import('./views/Home.vue')
},
{
path: '/about',
component: () => import('./views/About.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
六、Vue3狀態管理器Pinia
1、什麼是Pinia?
Pinia(發音為 /piːnjʌ/
,類似於英語中的“peenya”)是最接近有效包名 piña(西班牙語中的_pineapple_)的詞。 Pinia 是 Vue 的儲存庫,Pinia和Vuex一樣都是是vue的全域性狀態管理器,它允許跨元件/頁面共享狀態。實際上,其實Pinia就是Vuex5,官網也說過,為了尊重原作者,所以取名 pinia,而沒有取名 Vuex,所以大家可以直接將 pinia 比作為 Vue3 的 Vuex。
- 官網文件:Pinia 中文文件
2、對比vuex
- Pinia 同時支援 Vue2 以及 Vue3 ,這讓同時使用兩個版本的小夥伴更容易上手;
- Pinia 中只存在 State,getter,action,剔除掉了 Vuex 中的 Mutation 及 Module;
- Pinia 中的 action 可同時支援同步任務、非同步任務;
- 更友好的支援了 TypeScript ,無需建立自定義複雜包裝器來支援 TypeScript,所有內容都是型別化的,並且 API 的設計方式儘可能利用 TS 型別推斷;
- Pinia 在修改狀態的時候不需要透過其他 api,如:vuex 需透過 commit,dispatch 來修改,所以在語法上比 vuex 更容易理解和使用靈活;
- 由於去除掉了 Module ,無需再建立各個模組巢狀了。Vuex 中,如果資料過多,通常會透過劃分模組來進行管理,而 Pinia 中,每個 Store 都是獨立的,互不影響;
- 支援服務端渲染;
3、使用步驟
(1)安裝
npm install pinia
(2)建立js檔案
- 在store/counter.js,寫入程式碼,可以定義多個
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
//1 定義變數
state: () => {
return {
count: 0,
hobby:'籃球'
}
},
//2 這裡面寫方法,與後端互動或邏輯判斷,再運算元據
actions: {
increment(good_id) {
// 跟後端互動--》把good_id--》真正加購物車
this.count++
},
changeHobby(hobby){
this.hobby=hobby
}
},
//3 getter-->獲取資料
getters: {
getCount(){
return this.count
},
},
})
(3)main.js中使用外掛
import {createPinia} from 'pinia'
const pinia = createPinia()
createApp(App).use(router).use(pinia).mount('#app')
(4)元件中使用
- 在元件中使用pinia的資料
import { useCounterStore} from '../store/counter';
let counter= useCounterStore()
// 以後透過counter物件--》操作其中state,getter,action的東西
//Pinia 中的state、getter 和 action,我們可以假設這些概念相當於元件中的 data、 computed 和 methods。
(5)注意
-
State (狀態) 在大多數情況下,state 都是你的 store 的核心。人們通常會先定義能代表他們 APP 的 state。在 Pinia 中,state 被定義為一個返回初始狀態的函式。
-
getter函式推薦使用箭頭函式,並且它將接收
state
作為第一個引數:
// getter-->獲取資料
getters: {
getCount:(state)=>{
return state.count
},
},
- Action 相當於元件中的 method。它們可以透過
defineStore()
中的actions
屬性來定義,並且它們也是定義業務邏輯的完美選擇。類似 getter,action 也可透過this
訪問整個 store 例項,並支援完整的型別標註(以及自動補全)。不同的是,action
可以是非同步的,你可以在它們裡面await
呼叫任何 API,以及其他 action!
七、elementui-plus
1、介紹
本節要敘述的是elementui-plus,是一個基於 Vue 3,面向設計師和開發者的元件庫。旨在幫助開發者構建出現代化、美觀且高效的 Web 應用程式介面。它是對 Element UI 的進一步發展,專注於提供更好的效能、更豐富的元件以及更好的開發體驗。
Element Plus 是 Element UI 的一個分支和進化版本。Element UI 是一個非常受歡迎的 Vue UI 元件庫,旨在為開發者提供現代、美觀的介面元件。Element Plus 則是在 Element UI 的基礎上進一步發展而來,專注於提供更好的效能、更豐富的元件以及更好的開發體驗,同時也相容了 Vue 3 的新特性。因此,可以說 Element Plus 是 Element UI 的下一個版本,是 Element UI 的升級和擴充套件。
- 官方文件:一個 Vue 3 UI 框架 | Element Plus
但是另一款元件庫也值得我們去學習:Ant Design Vue
2、使用
(1)安裝
cnpm install element-plus --save
(2)註冊
- main.js中註冊
//匯入element-plus相關內容
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).use(router).use(pinia).use(ElementPlus).mount('#app')
(3)在元件中使用
<script setup>
import { ElMessage } from 'element-plus'
const open2 = () => {
ElMessage({
message: '恭喜您成功了',
type: 'success',
})
}
</script>
<template>
<div class="mb-4">
<el-button>Default</el-button>
<el-button type="primary">Primary</el-button>
<el-button type="success">Success</el-button>
<el-button type="info">Info</el-button>
<el-button type="warning">Warning</el-button>
<el-button type="danger">Danger</el-button>
</div>
<div>
<el-card style="max-width: 480px">
<template #header>Yummy hamburger</template>
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
style="width: 100%"
/>
</el-card>
</div>
<div>
<el-button :plain="true" @click="open2">Message</el-button>
</div>
</template>
<style scoped>
</style>
八、補充 代理模式
在 Python 中,代理模式是一種結構型設計模式,其目的是透過引入一個代理物件來控制對另一個物件的訪問。代理通常充當客戶端和實際物件之間的中間人,從而可以在訪問實際物件時新增額外的功能,如許可權控制、快取、延遲載入等。
以下是一個簡單的示例,演示瞭如何在 Python 中實現代理模式:
# 實際物件
class RealSubject:
def request(self):
print("RealSubject: Handling request")
# 代理物件
class Proxy:
def __init__(self, real_subject):
self.real_subject = real_subject
def request(self):
if self.check_access():
self.real_subject.request()
self.log_access()
def check_access(self):
# 檢查訪問許可權
print("Proxy: Checking access")
return True
def log_access(self):
# 記錄訪問日誌
print("Proxy: Logging the time of request")
# 客戶端程式碼
real_subject = RealSubject()
proxy = Proxy(real_subject)
# 透過代理物件訪問實際物件
proxy.request()
在這個示例中,RealSubject
是實際的物件,而 Proxy
是代理物件。代理物件在呼叫 request
方法時會先檢查訪問許可權,然後再呼叫實際物件的 request
方法,並記錄訪問日誌。
代理模式的優點包括:
- 安全控制:代理可以控制客戶端對物件的訪問許可權。
- 延遲載入:代理可以延遲載入實際物件,直到客戶端真正需要訪問它。
- 快取:代理可以快取實際物件的結果,避免重複計算。
- 簡化客戶端:客戶端可以與代理物件互動,而無需直接與實際物件互動。