Vue入門到關門之Vue3學習

Xiao0101發表於2024-05-09

一、常用API

注意:本文專案均使用腳手架為 Vite

1、setup函式

(1)介紹

如果在專案中使用配置項API,那麼寫起來就和vue2的寫法是一樣的;但是如果在專案中寫的是組合式API,那麼元件中所用到的:資料、方法等等,均要配置在setup中。此外,setup() 鉤子也是在元件中使用組合式 API 的入口,通常只在以下情況下使用:

  1. 需要在非單檔案元件中使用組合式 API 時。
  2. 需要在基於選項式 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()getset完成的
    • 物件型別的資料:內部 求助 了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生命週期流程圖

image

(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()接收到的是一個純數值。
image

(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.requestaxios.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的建構函式中傳入一個執行器函式,該函式接受兩個引數resolvereject,分別用於將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 類具有 addmultiply 兩個方法,這兩個方法都返回 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 為路由守衛提供了全面的支援,並提供了以下幾種型別的路由守衛:

  1. 全域性前置守衛:在路由切換前被呼叫,可以用於驗證使用者是否已登入、中斷導航、請求資料等。
  2. 全域性後置守衛:在路由切換之後被呼叫,可以用於處理資料、操作 DOM 、記錄日誌等。
  3. 守衛程式碼的位置: 在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("元件路徑");

image

注意:我們引入元件的步驟被放到了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 方法,並記錄訪問日誌。

代理模式的優點包括:

  1. 安全控制:代理可以控制客戶端對物件的訪問許可權。
  2. 延遲載入:代理可以延遲載入實際物件,直到客戶端真正需要訪問它。
  3. 快取:代理可以快取實際物件的結果,避免重複計算。
  4. 簡化客戶端:客戶端可以與代理物件互動,而無需直接與實際物件互動。

相關文章