Vue入门到关门之Vue3学习

一、常用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生命周期流程图

在这里插入图片描述

(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.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

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(“组件路径”);

在这里插入图片描述

注意:我们引入组件的步骤被放到了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。

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 的升级和扩展。

但是另一款组件库也值得我们去学习: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. 简化客户端:客户端可以与代理对象交互,而无需直接与实际对象交互。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值