一、常用API
注意:本文项目均使用脚手架为 Vite
1、setup函数
(1)介绍
如果在项目中使用配置项API,那么写起来就和vue2的写法是一样的;但是如果在项目中写的是组合式API,那么组件中所用到的:数据、方法等等,均要配置在setup中。此外,setup()
钩子也是在组件中使用组合式 API 的入口,通常只在以下情况下使用:
- 需要在非单文件组件中使用组合式 API 时。
- 需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。
(2)基本使用
setup函数的返回值:返回一个对象,对象中的属性、方法,在模板中均可以直接使用。setup函数中是默认不带响应式的,需要使用ref或reactive包裹。
<template>
<div class="home">
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<button @click="handleAdd">点击加年龄</button>
<button @click="changeName">点击变彭于晏</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'HomeView',
setup() {
// 1 插值语法
let name = ref('xiao')
// let age=19 // 默认没有响应式
let age = ref(19) // 做成响应式
// 2 方法--》点击年龄+1
function handleAdd() {
console.log(age) // age 的类型不是数字了,而是RefImpl
age.value += 1 // 让数字加1 ,需要使用 对象.value
}
function changeName() {
name.value = '彭于晏'
}
// 必须return--》这样setup里面的数据才能在template中使用
return {
name,
age,
handleAdd,
changeName
}
}
}
</script>
注意:
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed…)中可以访问到setup暴露的值中的属性、方法
- 但在setup中不能访问到Vue2.x配置(data、methos、computed…)
- 如果有重名, setup优先
2、setup需要注意的地方
(1)setup执行的时机
- 在beforeCreate之前执行(一次),此时组件对象还没有创建;setup函数执行于beforeCreate和created之前,也就是说setup函数里面无法使用data和methods方法中的数据。
- this是undefined,不能通过this来访问data/computed/methods /props;
- 其实所有的组合式API 相关的回调函数中也都不可以。
(2)setup的返回值
- 一般都返回一个对象:为模板提供数据,也就是模板中可以直接使用此对象中的所有属性/方法;
- 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性;
- 返回对象中的方法会与methods中的方法合并成功组件对象的方法;
- 如果有重名,setup优先;
- 注意:一般不要混合使用:methods中可以访问setup提供的属性和方法,但在setup方法中不能访问data和methods;
- setup不能是一个async函数:因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性数据
(3)setup的参数
setup(props, context) / setup(props, (attrs, slots, emiti);
- props: 包含props配置声明且传入了的所有属性的对象;
- attrs: 包含没有在props配置中声明的属性的对象,相当于 this.$attrs;
- slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots;
- emit: 用来分发自定义事件的函数 相当于 this.$emit。
(4)组件的属性
- 只能访问以下四种:props、attrs、slots、emit
3、ref 和 reactive
ref
用来做 基础变量[数字,字符串,布尔]的响应式
reactive
用来做 对象[数组,字典]的响应式
(1)ref
-
语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
JS
中操作数据:xxx.value
模板
中读取数据: 不需要.value
,直接:<div>{{xxx}}</div>
; 因为在模板中访问从 setup 返回的 ref 时,它会自动浅层解包,因此你无须再在模板中为它写 .value。
-
备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的 - 对象类型的数据:内部 求助 了Vue3.0中的一个新函数——
reactive
函数
<template>
<div class="home">
<h1>setup函数的使用</h1>
{{ name }}--{{ age }}
<br>
<button @click="add">点我年龄+1</button>
<br>
<button @click="handleChange('彭于晏')">点我变彭于晏</button>
</div>
</template>
<script>
import {ref, reactive} from 'vue'
export default {
name: 'HomeView',
setup() {
// vue3多的,vue2没有,以后建议vue3的代码全都写在这里,不再写配置项方式了
// 1 定义变量,跟正常写js一样
let name = ref('xiao')
// let age = 19 // 没有响应式
let age = ref(19) // 有响应式,变成对象了
// 2 定义一个函数,点击按钮,年龄加一的函数
let add = () => {
// alert('111')
// 让年龄+1,出问题了,变量确实会变,但是页面不会变化---》vue3定义的变量,默认不是响应式的
// age++ 自增,就不能这么写了
age.value++ //有响应式
console.log(age.value)
}
let handleChange = (n) => {
name.value = n //有响应式
}
// 3 必须要有返回值,是个对象,返回的对象,可以在 模板(template)中使用
return {name, age, add, handleChange}
},
}
</script>
(2)reactive
-
语法:
-
const 代理对象= reactive(源对象)
-
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
-
操作数据和读取数据均不需要
.value
-
-
reactive定义的响应式数据是“深层次的”,对象无论多少层,都可以。
-
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作
<template>
<div class="home">
<h1>setup函数的使用</h1>
<p>用户名:{{ userInfo.name }}</p>
<p>年龄:{{ userInfo.age }}</p>
<p>爱好:{{ userInfo.hobby }}</p>
<button @click="handleAdd">点我年龄+1</button>
</div>
</template>
<script>
import {ref, reactive} from 'vue'
export default {
name: 'HomeView',
setup() {
let userInfo = reactive({
name: 'xiao',
age: 19,
hobby: '篮球'
})
let handleAdd = () => {
userInfo.age++
console.log(userInfo)
}
return {userInfo, handleAdd}
},
}
</script>
(3)ref与reactive的对比
- 从定义数据角度对比:
- ref用来定义:基本类型数据
- reactive用来定义:对象(或数组)类型数据
- 从原理角度对比:
- ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。
- reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- 从使用角度对比:
- ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。
- reactive定义的数据:操作数据与读取数据:均不需要.value。
4、计算属性-监听属性
(1)计算属性computed
Vue3与Vue2中的计算属性配置功能是一样的,不同的是写法:
- 在Vue2中,computed是通过声明选项的方式书写的,在Vue中,声明选项是指在创建Vue实例时传入的参数,是一个对象。这个对象可以包含多个属性和方法,其中包括data、methods、computed、watch等。这些属性和方法可以用于定义组件的行为和状态。
- 在Vue3中,computed是通过组合式API的方式书写的,Vue中的组合式API是一组新的API,它允许我们使用函数而不是声明选项的方式书写Vue组件。组合式API包括响应式API、生命周期钩子、工具函数等,这些API可以让我们更灵活地组织和复用代码,提高代码的可读性和可维护性 。
所以我们在Vue3中使用computed的时候需要先引入
import {computed} from 'vue'
<template>
<h1>计算属性</h1>
<p>姓:<input type="text" v-model="person.firstName"></p>
<p>名:<input type="text" v-model="person.lastName"></p>
<p>全名:{{ person.fullName }}</p>
<p>全名修改:<input type="text" v-model="person.fullName"></p>
</template>
<script>
import {ref, reactive} from 'vue'
import {computed} from 'vue'
export default {
name: 'App',
setup() {
// 3 计算属性
const person = reactive({
firstName: '',
lastName: ''
})
// 只有 计算属性,不修改值的情况
person.fullName = computed(() => {
return person.firstName+person.lastName
})
// 支持修改
person.fullName = computed({
get() {
return person.firstName + person.lastName
},
set(value) {
person.firstName = value.slice(0, 1)
person.lastName = value.slice(1)
},
})
return {person}
},
}
</script>
(2)监听属性watch
- Vue2和Vue3中的watch属性在功能上是一致的。
- 但是要注意两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
在vue3中watch()方法可以帮助我们监听数据的变化,并按执行一些任务。Vue3中watch接受三个参数,第一个参数是要监听的响应式数据,第二个参数是回调函数,第三个参数是配置项。如果需要监听多个数据,可以在setup函数中使用watch函数多次,每次传入不同的参数即可。不像vue2中的watch是一个配置项,vue3中的watch是一个方法可以多次调用。
情景一:监视ref定义的响应式数据
- 当我们点击按钮的时候,watch可以监听到数据的变化。
<template>
<h2>年龄是:{{ age }}</h2>
<button @click="age++">点我年龄增加</button>
</template>
<script>
import {ref, watch} from "vue";
export default {
name: 'App',
setup() {
const age = ref(19)
// 监听普通
watch(age, (newValue, oldValue) => {
console.log('age变化了', '新值',newValue,'旧值', oldValue)
})
return {age}
}
}
</script>
情景二:监视多个ref定义的响应式数据
const sum = ref(100)
const msg = ref('很好')
function changeSum() {
sum.value += 1
}
const changeMsg = () => {
msg.value = 'asdfas'
}
watch([sum, msg], (newValue, oldValue) => {
console.log('sum或msg变化了', '新值',newValue,'旧值', oldValue)
})
情景三:监视reactive定义的响应式数据
如果加了{immediate:true}
配置项之后表示立即监听,输入框中的值还没有改变就会触发一次watch方法;
从控制台打印的信息,我们可以清晰地看到oldval的值为undefined。这就是我们需要注意的第一点:若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!
watch(person, (newValue, oldValue) => {
console.log('person变化了', '新值', newValue, '旧值', oldValue)
}, { immediate: true ,deep:false})
在代码中我并没有写deep:true
,但是依然可以监听到person下的age属性。
而且就算我们在代码中关闭深度监听也是没有用的,所以这里就是我们需要注意的第二点:若watch监视的是reactive定义的响应式数据,则强制开启了深度监视。
情景四:监视reactive定义的响应式数据中的某个属性
- 如果我们想监听一个对象中的某一个属性,我们肯定会轻松到想到这个代码该怎么写。
watch(person.name, (newValue, oldValue) => {
console.log('person变化了', '新值',newValue,'旧值', oldValue)
}, { immediate: true ,deep:false})
-
但这时控制台会弹出一个警告,简单翻译一下就是 : 监视源只能是getter/effect函数、ref、响应对象或这些类型的数组。通俗的说就是,只能监视一个ref的值或者是reactive对象。
-
所以需要我们这么写,正常的写法是写一个函数,函数有返回值:
const person = reactive({name: 'xiao', age: 14})
// 2 监听对象中的某个属性
watch(() => person.name, (newValue, oldValue) => {
console.log('person.name变化了', '新值',newValue,'旧值', oldValue)
}, { immediate: true ,deep:false})
情景五:监视reactive定义的响应式数据中的某些属性
- 如果是要监视一个响应式数据的多个属性,也按照上文写的监视多个ref定义的响应式数据那样,将多个属性写在一个数组中,不过每一个属性都要写成函数的形式。
watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
console.log('person的age变化了', '新值',newValue,'旧值', oldValue)
},{immediate:true,deep:true})
特殊情况
- 当我们监视一个reactive定义的对象中的某个属性时,此时deep配置就会生效,然而当我们将deep配置设置为false时,是监听不到person.age的变化的。
watch(() => person.age, (newValue, oldValue) => {
console.log('person的age变化了','新值',newValue,'旧值', oldValue)
}, { deep: false })
- 不管我们怎么去修改age对应的属性值都是监听不到的。
watchEffect函数
- 当我们使用
watch
监视属性的时候,需要明确的指出需要监视的是哪个属性,也要指明监视的回调函数。 - 而
watchEffect
的工作原理是:不用指定监听谁,只要watchEffect内部用了某个变量,某个变量发送变化,就会触发。
watchEffect(() => {
const x1 = sum.value
const x2 = person.name
console.log('watchEffect配置的回调执行了')
})
- 在
watchEffect
中,我们将person的name和sum的值赋值给两个新的变量,证明我们使用了这两个属性,所以修改这两个属性的值是,就会触发监听函数。
(3)总结
-
Computed属性
-
computed 是一个函数,它返回一个值,该值依赖于组件的数据。当依赖的数据发生改变时,computed 返回的值会自动更新。
-
在 Vue.js 中,我们通常使用 computed 来封装复杂的逻辑或计算属性,使得我们能够更加方便地处理这些逻辑,并且保证其响应式的特性。
-
-
Watch属性
-
watch 是一个对象,它允许我们观察 Vue 实例的数据。当数据变化时,我们可以执行一些操作。
-
在某些情况下,我们可能需要等待数据改变后执行某些操作,或者在数据改变时执行异步操作。这种情况下,我们可以使用 watch。
-
5、生命周期
- vue3生命周期流程图
(1)Vue2.X和Vue3.X对比
vue2 -------> vue3配置项 -------> vue3组合式
beforeCreate --------> beforeCreate -------> setup(()=>{})
created --------> created -------> setup(()=>{})
beforeMount --------> beforeMount -------> onBeforeMount(()=>{})
mounted --------> mounted -------> onMounted(()=>{})
beforeUpdate --------> beforeUpdate -------> onBeforeUpdate(()=>{})
updated --------> updated -------> onUpdated(()=>{})
beforeDestroy --------> beforeUnmount -------> onBeforeUnmount(()=>{})
destroyed --------> unmounted -------> onUnmounted(()=>{})
(2)配置项API生命周期
- **beforeCreate:**beforeCreate钩子用于在实例被创建之前执行逻辑。
- **created:**created钩子用于在实例创建完成后执行逻辑。
- **beforeMount:**beforeMount钩子在挂载之前执行。
- **mounted:**mounted钩子在挂载完成后执行。
- **beforeUpdate:**beforeUpdate钩子在数据更新之前执行。
- **updated:**updated钩子在数据更新完成后执行。
- **beforeUnmount:**beforeUnmount钩子在组件卸载之前执行。
- **unmounted:**unmounted钩子在组件卸载完成后执行。
(3)组合式API生命周期
- setup() : 开始创建组件,在 beforeCreate 和 created 之前执行,创建的是 data 和 method;
- onBeforeMount() : 组件挂载到节点上之前执行的函数;
- onMounted() : 组件挂载完成后执行的函数;
- onBeforeUpdate(): 组件更新之前执行的函数;
- onUpdated(): 组件更新完成之后执行的函数;
- onBeforeUnmount(): 组件卸载之前执行的函数;
- onUnmounted(): 组件卸载完成后执行的函数
(4)示例
<template>
<div class="home">
<h1>生命周期钩子</h1>
<h3>年龄是:{{ age }}</h3>
<button @click="addAge">点击age+1</button>
</div>
</template>
<script>
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
export default {
name: 'HomeView',
setup() {
// 生命周期钩子
// 1 写在这里是就是beforeCreate
console.log('beforeCreate')
const age=ref(19)
function addAge(){
age.value++
}
//2 写在这里是就是created
console.log('created',age.value)
//3 beforeMount-->onBeforeMount
// onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted
onBeforeMount(()=>{
console.log('onBeforeMount','组件挂载前')
})
//4 mounted-->onMounted
onMounted(()=>{
console.log('onMounted','组件挂载后')
})
//5 beforeUpdate-->onBeforeUpdate
onBeforeUpdate(()=>{
console.log('onBeforeUpdate','更新之前')
})
//6 updated-->onUpdated
onUpdated(()=>{
console.log('onUpdated','更新之后')
console.log(age.value)
})
//7 beforeUnmount-->onBeforeUnmount
onBeforeUnmount(()=>{
console.log('onBeforeUnmount','销毁之前')
})
//8 unmounted-->onUnmounted
onUnmounted(()=>{
console.log('onUnmounted','销毁后')
})
return {age,addAge}
},
}
</script>
6、toRef 和 toRefs
(1)toRef
作用:
创建一个ref对象,其value值指向另一个对象中的某个属性值,与原对象是存在关联关系的。也就是基于响应式对象上的一个属性,创建一个对应的ref,这样创建的ref与它的源属性是保持同步的,与源对象存在引用关系,改变源属性的值将更新ref的值。
语法:
const 变量名 = toRef(源对象,源对象下的某个属性)
如:const name = toRef(person,'name')
使用:
要将响应式对象中的某个属性单独提供给外部使用时,但是不想丢失响应式,把一个prop的ref传递给一个组合式函数也会很有用。
缺点:
toRef()
只能处理一个属性,但是toRefs(源对象)
却可以一次性批量处理
示例:
<template>
<div class="home">
<h1>toRef函数</h1>
{{data}}
<br>
{{ name }}---{{ age }}
<button @click="handleChangeAttrs">点我看控制台</button>
</div>
</template>
<script>
import {
ref,
toRef,
reactive,
} from 'vue'
export default {
name: 'HomeView',
setup() {
let data = reactive({
name: 'xiao',
age: 19,
hobby: '篮球'
})
// 错误示范
const { name, age} = person;
const { web,trade} = person.job;
// 这样直接操作数据是无法修改的,因为它不是一个响应式数据,只是一个纯字符串,不具备响应式
function handleChangeAttrs() {
name = "itclanCoder";
age = 20;
// 正确写法
// 想要修改指定哪个对象具备响应式,那么就使用toRef函数处理,toRef(源对象,源对象下的某个属性)
const name = toRef(data, 'name')
// 使用ref与toRef对比
const age = ref(data.age)
function handleChangeAttrs() {
name.value = "刘德华";
age.value = 20;
console.log(name)
console.log(age)
}
return {name, age, handleChangeAttrs}
},
}
</script>
toRef与ref的不同:
如果你用ref
处理数据的话,如下所示,使用ref
处理数据,页面也能实现数据的响应式,更新,但是它与toRef
是不同,有区别的,因为ref修改数据,页面数据会更新,但是源数据不会同步,修改,并无引用关系,ref
相当于是对源对象重新拷贝一份数据 ref()
接收到的是一个纯数值。
(2)toRefs
作用:
toRef()
只能处理源对象指定的某个属性,如果源对象属性很多,一个一个的使用toRef()
处理会显得比较麻烦,那么这个toRefs()
就很有用了,它与toRef()
的功能一致,可以批量创建多个ref
对象,并且能与源对象保持同步,有引用关系
语法:
toRefs(源对象)
如:toRefs(person)
使用:
当从组合式函数中返回响应式对象时,toRefs
是很有用的。使用它,消费者组件可以解构/展开返回的对象而不会失去响应性。
示例:
<template>
<div class="home">
<h1>toRefs</h1>
<h2>{{ name }}---{{ age }}</h2>
<button @click="age++">点击年龄+1</button>
<button @click="addAge">点击年龄+2</button>
<br>
<button @click="handleShow">看控制台</button>
</div>
</template>
<script>
import {ref, reactive, toRefs} from 'vue'
export default {
name: 'HomeView',
setup() {
// toRefs
let person = reactive({name: 'xiao', age: 19})
function addAge() {
person.age += 2
console.log(person)
}
function handleShow() {
console.log(person)
}
// return {name:ref(person.name), age:ref(person.age),addAge, handleShow}
return {...toRefs(person),addAge, handleShow}
},
}
</script>
注意事项:
toRefs
在调用时只会为源对象上可以枚举的属性创建ref
。如果要为可能还不存在的属性创建 ref
,则改用 toRef
。
二、setup写法
1、简单介绍
- 组件,只需要导入,就会自动注册
- setup写法
<script setup>
写原来setup函数中的代码即可</script>
- 生命周期钩子–created
- 监听属性,计算属性
- 组件间通信–父传子
- 组件通信–子传父
- 插槽
- mixin 没了==>直接导入导出用
- 插件也是一样
- toRefs–>把对象中所有变量都做成响应式
- toRef -->只把对象中某一个做成响应式
- ref属性
2、具体使用
(1)App.vue
<script setup>
// 以后,只要再 这样写[ <script setup> ] ,script就是setup函数中的
// 定义的变量和函数,不需要return,以后,就不再写配置项了
// 1 组件,只需要导入,就会自动注册
import HelloWorld from './components/HelloWorld.vue'
import Child from "./components/Child.vue";
// 2 setup写法
import {ref, reactive, computed, toRefs, toRef} from "vue";
import Child2 from "./components/Child2.vue";
const name = ref('xiao')
function changeName() {
name.value = '彭于晏'
}
// 3 生命周期钩子--created
console.log('created')
// 4 监听属性,计算属性
const newName = computed(() => {
return name.value + '_NB'
})
// 5 组件间通信 父传子
const message = ref('hello world 组件你好')
// 6 组件通信,子传父
const child_name = ref('')
function handleEvent(name) {
child_name.value = name
}
// 7 插槽
// 8 mixin 没了-->直接导入导出用
import utils from "./utils/index.js";
let a = utils.add(4, 5)
console.log(a)
// 9 插件一样
// 10 toRefs-->把对象中所有变量都做成响应式
const person = reactive({name1: 'xiao', age1: 19})
let {name1, age1} = toRefs(person) // 等同于:name:ref(person.name) age:ref(person.age)
// let {name1, age1} = person // 等同于: name1=lqz age1=19
console.log(typeof person.name1)
console.log(typeof name1)
name1.value='sss'
// 11 toRef -->只把对象中某一个做成响应式
const person1 = reactive({name2: 'xiao', age2: 19})
//const name=toRefs(person) //{name:ref(name),age:ref(age)}
const name2 = toRef(person, 'name2') //name=ref(person.name)
function change() {
name2.value = 'xxx'
}
// 12 ref属性-->注意要组件挂载完后才能拿到child3 值
import Child3 from "./components/Child3.vue";
const child3=ref() // 代指 this.$refs.child3 ,这个地方变量名必须跟在组件上定义的名字一致,放在组件上的ref是child3
// created--->还没挂载---》组件还没有
function showLog(){
console.log(child3.value) // child3.value拿到组件对象
child3.value.changeAge() // 使用组件对象的属性和方法---》vue3---》不能直接使用,需要子组件暴露---》子组件中:defineExpose({age,changeAge})---》只能用子组件暴露的
console.log(child3.value.age)
}
</script>
<template>
<h1>setup写法</h1>
<h2>{{ name }}</h2>
<button @click="changeName">点我变名字</button>
<h2>计算属性newName:{{ newName }}</h2>
<hr>
<h1>父传子-自定义属性</h1>
<HelloWorld :msg="message"></HelloWorld>
<h1>子传父-自定义事件</h1>
<h2>子组件传过来的:{{ child_name }}</h2>
<Child @myevent="handleEvent"></Child>
<h1>插槽</h1>
<Child2>
<template v-slot:a>
<div>我是a</div>
</template>
<template v-slot:b>
<div>我是bbb</div>
</template>
</Child2>
<h1>ref属性-放在组件上</h1>
<Child3 ref="child3"></Child3>
<button @click="showLog">点我看控制台</button>
</template>
<style></style>
(2)父子通信父传子==> HelloWorld.vue
<script setup>
// 父传子,接受父传入的变量
// 1 数组形式
// defineProps(['msg'])
// 2 对象形式
defineProps({
msg: String,
})
</script>
<template>
<h1>{{ msg }}</h1>
</template>
(3)父子通信子传父==> Child.vue
<script setup>
import {ref} from "vue";
let $emit = defineEmits(['myevent']) // 等同于之前的 this.$emit
const name = ref('')
function handleSend() {
$emit('myevent', name.value)
}
</script>
<template>
<input type="text" v-model="name">-->{{ name }}-->
<button @click="handleSend">点我,传到父</button>
</template>
<style scoped>
</style>
(4)插槽使用==> Child2.vue
<script setup>
</script>
<template>
<h2>child2</h2>
<slot name="a"></slot>
<h2>换行</h2>
<slot name="b"></slot>
</template>
<style scoped>
</style>
(5)ref属性==> Child3.vue
<script setup>
import {ref} from "vue";
const age=ref(0)
function changeAge(){
age.value+=10
}
defineExpose({age,changeAge}) // 在子组件中暴露
</script>
<template>
<h1>ref属性使用</h1>
</template>
<style scoped>
</style>
三、axios使用
1、简单介绍
(1)什么是axios?
axios是一个流行的基于Promise的HTTP客户端,可以在浏览器和Node.js环境中使用。它允许您在应用程序中进行HTTP请求,从而与后端服务器进行数据交换。
(2)axios的功能
-
axios的返回结果是一个promise实例对象
-
他的回调不同于promise的value和reason分别叫做response和err
-
axios的成功值是一个axios封装的response对象.服务器返回的真正数据在response.data中
-
axios需要携带query参数的话要写在params中,但是params参数只能写在请求地址中
2、vue3实现加载电影案例
(1)安装
npm install axios -S
(2)导入
import axios from "axios";
(3)使用
// 相当于写在了created中--》页面加载完,就发送请求
axios.get('自己地址').then(res => {
console.log(res)
})
(4)axios普通使用
<script setup>
import axios from "axios";
import {reactive} from "vue";
const filmList = reactive({})
// 相当于写在了created中--》页面加载完,就发送请求
// 普通使用
axios.get('http://127.0.0.1:8000/api/v1/film/').then(res => {
console.log(res.data)
if(res.data.code==100){
// 加载成功了-->把返回的数据,放到变量中
filmList.result=res.data.results // 能赋值,但是不是响应式
console.log('---',filmList)
}else{
alert(res.data.msg)
}
})
</script>
<template>
<h1>显示电影案例</h1>
<div v-for="item in filmList.result">
<h3>{{ item.name }}</h3>
<img :src="item.poster" alt="" height="300px" width="250px">
</div>
</template>
<style></style>
(5)高级使用
<script setup>
import axios from "axios";
import {reactive} from "vue";
const filmList = reactive([])
// 高级使用 Object.assign--》copy-》把一个对象copy到另一个对象身上
axios.get('http://127.0.0.1:8000/api/v1/film/').then(res => {
console.log(res.data)
if (res.data.code == 100) {
// (1) 直接把res.data.results 复制到filmList.result
Object.assign(filmList,res.data.results)
// (2) 解构赋值
let {data}=res // res={data:{code:100,msg:成功}}
Object.assign(filmList,data.results)
// (3) 解构赋值
let {data: {results}} = res
Object.assign(filmList, results)
// (4) 解构赋值
let {data} = res // {code:100,msg:成功,results:[]}
Object.assign(filmList, data.results)
} else {
alert(res.data.msg)
}
})
</script>
<template>
<h1>显示电影案例</h1>
<div v-for="item in filmList">
<h3>{{ item.name }}</h3>
<img :src="item.poster" alt="" height="300px" width="250px">
</div>
</template>
<style></style>
3、async和await
(1)async/await是什么?
async
关键字用于定义一个异步函数,表示该函数是一个协程(coroutine)。await
关键字用于暂停异步函数的执行,等待另一个异步操作完成。
(2)async和await的基础使用
-
async 表示这是一个async函数, await只能用在async函数里面,不能单独使用;
-
async 返回的是一个Promise对象,await就是等待这个promise的返回结果后,再继续执行;
-
await 等待的是一个Promise对象,后面必须跟一个Promise对象,但是不必写then(),直接就可以得到返回值。
(3)async/await的特点
- Async作为关键字放在函数前面,普通函数变成了异步函数;
- 异步函数async函数调用,跟普通函数调用方式一样。在一个函数前面加上async,变成 async函数,异步函数,return:1,打印返回值;
- 返回的是promise成功的对象;
- Async函数配合await关键字使用。
(4)加载电影案例改写
<script setup>
import axios from "axios";
import {reactive} from "vue";
const filmList = reactive({})
async function load() {
// response--》就是原来then中的res
// let response= await axios.get('http://127.0.0.1:8000/api/v1/films/')
// data --》就是原来then中的res.data
// 正常返回的then的给了response--》原来catch的会被异常捕获
let {data} = await axios.get('http://127.0.0.1:8000/api/v1/film/')
console.log(data)
Object.assign(filmList, data.results)
}
load()
</script>
<template>
<h1>显示电影案例</h1>
<div v-for="item in filmList.result">
<h3>{{ item.name }}</h3>
<img :src="item.poster" alt="" height="300px" width="250px">
</div>
</template>
<style></style>
4、axios其它配置项
(1)常用配置项
- GET请求
//完整版写法
const res = axios({
url:'http://localhost:5000/persons',//请求地址
methods:'GET'//请求方式
params:{id:...}//query参数发送方式
})
log(res)//axios返回值是一个promise实例
res.then(
response => {log(response.data)}
err => {log(err)}
)
//精简版写法
axios.get('http:.......',{params:{id:...}}).then(
response =>{}
err =>{}
)
//只要成功的写法
const res = await axios.get('http:/...')
- POST请求
//完整版
axios({
url:'http://...',
methods:'POST',
data:{name:...,age:...}//json格式的参数
data:`name=..&age=..`//urlencoded格式的参数
})
//精简版
axios.post('http:...',{name:..,age:..}).then(
response => {}
err => {}
)
- 配置默认属性
axios({
url:'地址',
method:'post',
headers: {'token': 'adsfa.adsfa.adsf',contentType:'application/json'},
params: {name: xiao, age:19},
data: {firstName: 'xxx'},
timeout: 1000,
})
// 或者这么写
axios.baseURL = 'http://...' //URL一定是大写
axios.defaults.timeout = 2000
axios.defsults.headers = {'token': 'adsfa.adsfa.adsf',contentType:'application/json'}
(2)其他配置项
// 更多参数
{
//1 `url` 是用于请求的服务器 URL
url: '/user',
//2 `method` 是创建请求时使用的方法
method: 'get', // 默认值
//3 `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
//4 `transformRequest` 允许在向服务器发送前,修改请求数据
// 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
// 你可以修改请求头。
transformRequest: [function (data, headers) {
// 对发送的 data 进行任意转换处理
return data;
}],
// transformResponse 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对接收的 data 进行任意转换处理
return data;
}],
//5 自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
//6 params` 是与请求一起发送的 URL 参数
// 必须是一个简单对象或 URLSearchParams 对象
params: {
ID: 12345
},
// 7 aramsSerializer`是可选方法,主要用于序列化`params`
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
//8 data` 是作为请求体被发送的数据
// 仅适用 'PUT', 'POST', 'DELETE 和 'PATCH' 请求方法
// 在没有设置 `transformRequest` 时,则必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 浏览器专属: FormData, File, Blob
// - Node 专属: Stream, Buffer
data: {
firstName: 'Fred'
},
// 发送请求体数据的可选语法
// 请求方式 post
// 只有 value 会被发送,key 则不会
data: 'Country=Brasil&City=Belo Horizonte',
// 0imeout` 指定请求超时的毫秒数。
// 如果请求时间超过 `timeout` 的值,则请求会被中断
timeout: 1000, // 默认值是 `0` (永不超时)
// 11 thCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
// 12 dapter` 允许自定义处理请求,这使测试更加容易。
// 返回一个 promise 并提供一个有效的响应 (参见 lib/adapters/README.md)。
adapter: function (config) {
/* ... */
},
// 13 auth` HTTP Basic Auth
auth: {
username: 'xiao'
password: '123‘
},
// 14 `responseType` 表示浏览器将要响应的数据类型
// 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
// 浏览器专属:'blob'
responseType: 'json', // 默认值
// 15 `responseEncoding` 表示用于解码响应的编码 (Node.js 专属)
// 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // 默认值
// 16 `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
xsrfCookieName: 'XSRF-TOKEN', // 默认值
// 17 `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称
xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值
// 18 `onUploadProgress` 允许为上传处理进度事件
// 浏览器专属
onUploadProgress: function (progressEvent) {
// 处理原生进度事件
},
// 19 `onDownloadProgress` 允许为下载处理进度事件
// 浏览器专属
onDownloadProgress: function (progressEvent) {
// 处理原生进度事件
},
// 20 `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数
maxContentLength: 2000,
// 21 `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数
maxBodyLength: 2000,
// 22 `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise。
// 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
// 则promise 将会 resolved,否则是 rejected。
validateStatus: function (status) {
return status >= 200 && status < 300; // 默认值
},
// 23 `maxRedirects` 定义了在node.js中要遵循的最大重定向数。
// 如果设置为0,则不会进行重定向
maxRedirects: 5, // 默认值
// 24 `socketPath` 定义了在node.js中使用的UNIX套接字。
// e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。
// 只能指定 `socketPath` 或 `proxy` 。
// 若都指定,这使用 `socketPath` 。
socketPath: null, // default
// 25 `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
// and https requests, respectively, in node.js. This allows options to be added like
// `keepAlive` that are not enabled by default.
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// 26 `proxy` 定义了代理服务器的主机名,端口和协议。
// 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
// 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。
// `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
// 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers` 中已存在的自定义 `Proxy-Authorization` 请求头。
// 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
proxy: {
protocol: 'https',
host: '127.0.0.1',
port: 9000,
auth: {
username: 'xiao',
password: '123'
}
},
// 27 see https://axios-http.com/zh/docs/cancellation
cancelToken: new CancelToken(function (cancel) {
}),
// 28 `decompress` indicates whether or not the response body should be decompressed
// automatically. If set to `true` will also remove the 'content-encoding' header
// from the responses objects of all decompressed responses
// - Node only (XHR cannot turn off decompression)
decompress: true // 默认值
}
5、axios请求响应拦截器
axios请求响应拦截器是axios提供的一个重要功能,它可以在我们发送请求或接收响应时进行处理。通过拦截器,我们可以在请求或响应被处理前对其进行修改、日志记录或添加额外的处理逻辑。
在axios中,您可以通过axios.interceptors.request
和axios.interceptors.response
来添加请求和响应拦截器。这两个方法都接受两个回调函数作为参数,一个用于处理成功的情况,另一个用于处理错误的情况。
下面是一个简单的示例,演示了如何使用axios的拦截器:
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
console.log('请求拦截器被触发');
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
console.log('响应拦截器被触发');
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
在上面的示例中,我们使用axios.interceptors.request.use
添加了一个请求拦截器,它在每次发送请求之前被触发。类似地,使用axios.interceptors.response.use
添加了一个响应拦截器,它在每次接收到响应后被触发。
此外,我们还可以在拦截器中进行各种操作,例如添加请求头、记录日志、对响应数据进行处理等。这使得axios拥有了更高的灵活性和可定制性,能够满足各种复杂的需求。
四、promise语法
1、普通函数和回调函数
(1)普通函数
普通函数是最常见的函数类型,就是可以被正常调用的函数,一般函数执行完毕后才会继续执行下一行代码。普通函数可以接受参数并返回一个值。例如:
<script>
let fun1 = () =>{
console.log("fun1 执行了")
}
// 调用函数
fun1()
// 函数执行完毕,继续执行后续代码
console.log("其他代码继续执行")
</script>
(2)回调函数
回调函数是作为参数传递给其他函数的函数,表示未来才会执行的一些功能,后续代码不会等待该函数执行完毕就开始执行了,用于在某个操作或事件完成后执行。回调函数通常用于处理异步操作,例如在异步请求完成后执行某些操作。例如:
function fetchData(callback) {
// 设置一个2000毫秒后会执行一次的定时任务,基于事件自动调用,console.log先执行
setTimeout(() => {
const data = 'Some data';
callback(data);
}, 2000);
}
function processData(data) {
console.log('Data received:', data);
}
fetchData(processData);
在这个例子中,fetchData
函数是一个模拟的异步操作,它接受一个回调函数作为参数,在异步操作完成后调用该回调函数并传递数据。processData
函数作为回调函数传递给fetchData
,当数据准备就绪时会被调用。
回调函数常用于处理事件处理、异步请求、定时器等场景,可以使代码更加灵活和可扩展,但也容易导致回调地狱(callback hell)问题,使代码难以阅读和维护。
总的来说,普通函数和回调函数都是JavaScript中常见的函数类型,普通函数用于一般的函数调用和返回值,而回调函数用于在某个操作完成后执行特定的逻辑。
2、promise基本使用(用来处理回调函数)
在JavaScript中,Promise是一种用于处理异步操作的对象,它代表了一个异步操作的最终完成或失败,并返回结果值。Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
就比如现实生活中你跟你女朋友说,5年后等我赚够500w就结婚 ==> 定义函数
- 进行中(努力赚钱,其他代码继续执行)
- 成功(赚够500w ==> 结婚)
- 失败(没赚够 ==> 分手)
下面是Promise的基本语法:
// 创建一个Promise对象
const myPromise = new Promise((resolve, reject) => {
// 异步操作
if (/* 异步操作成功 */) {
resolve('成功时的结果');
} else {
reject('失败时的原因');
// 主动抛异常,也是执行失败
throw new Error("error message")
}
});
// 使用Promise对象
myPromise.then((result) => {
// 当Promise状态变为fulfilled时调用,result为成功时的结果
console.log(result);
}).catch((error) => {
// 当Promise状态变为rejected时调用,error为失败时的原因
console.log(error);
});
在上面的示例中,我们首先创建了一个Promise对象myPromise
,在Promise的构造函数中传入一个执行器函数,该函数接受两个参数resolve
和reject
,分别用于将Promise的状态从pending改变为fulfilled(成功)或rejected(失败)。
在Promise对象创建后,我们可以使用.then()
方法来处理成功状态下的结果,使用.catch()
方法来处理失败状态下的原因。
Promise的语法使得异步操作的处理变得更加直观和易于管理,避免了回调地狱(callback hell)的问题,使得代码更加清晰和可读。
此外,值得注意的是我在上面提到的axios返回的也是一个promise对象
3、async和await的使用
- 在上面的async和await简单介绍中,了解到async 返回的是一个Promise对象,await就是等待这个promise的返回结果后,再继续执行;所以promise对象肯定支持 async和await 写法
// async 和await 写法
// async标识函数后,async函数的返回值会变成一个promise对象
async function demo01() {
let promise = new Promise(function (resolve, reject) {
// resolve,reject 是两个函数
console.log("promise 开始执行")
// resolve("promise 执行成功")
// reject("promise 执行失败")
// 主动抛异常,也是执行失败
throw new Error("error message")
})
return promise
}
console.log('11111')
// await 关键字,必须写在async修饰的函数中
async function demo02() {
try {
let res = await demo01() // 正常调用,返回promise 对象,加await 调用--》返回正常then的数据
console.log(res)
} catch (err) {
console.log('出错了')
}
}
demo02() // 它会等正常执行完成才会调用
console.log('222222')
五、vue3中的vue-router
- 官网文档:介绍 | Vue Router (vuejs.org)
1、基本使用
(1)安装
- 在vue3中需要安装vue-router4版本的,所以安装的时候需要我们指定版本
npm install -S vue-router@4
cnpm install vue-router@4 --save
(2)注册
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router";
import AboutView from "../view/AboutView.vue";
import HomeView from "../view/HomeView.vue";
import LoginView from "../view/LoginView.vue";
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about/:id',
name: 'about',
component: AboutView
},
{
path: '/login',
name: 'Login',
component: LoginView
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
(3)main.js中使用
import {createApp} from 'vue'
// 使用vue-router
import router from './router'
import App from './App.vue'
createApp(App).use(router).mount('#app')
(4)补充 链式调用
链式调用是一种编程风格,通常用于方法调用或操作的连续执行。在很多编程语言中,链式调用通过在一个对象上连续调用多个方法来简化代码,并使代码更易读和紧凑。这种方法的返回值通常是一个对象本身,以便可以继续在其上调用其他方法。
以下是一个简单的示例,演示如何在一个对象上进行链式调用:
class Calculator:
def __init__(self, value):
self.value = value
def add(self, x):
self.value += x
return self # 返回自身以支持链式调用
def multiply(self, x):
self.value *= x
return self # 返回自身以支持链式调用
# 创建一个 Calculator 实例并进行链式调用
result = Calculator(5).add(3).multiply(4).value
print(result) # 输出:32
在上面的示例中,Calculator
类具有 add
和 multiply
两个方法,这两个方法都返回 self
,以支持链式调用。通过在实例化后直接在其上连续调用这些方法,可以在单行代码中实现多个操作。
链式调用在很多库和框架中被广泛应用,例如jQuery中的方法调用、Python中的pandas库等。
2、路由跳转
(1)普通路由跳转(声明式路由)
这种路由实现跳转的话,to中的内容目前是固定的,点击后只能切换/about对象组件(声明式路由)
- 写路径
<router-link to="/about"></router-link>
(2)编程式路由
- 通过useRouter,动态决定向那个组件切换的路由
- 在 Vue 3 和 Vue Router 4 中,你可以使用
useRouter
来实现动态路由(编程式路由) - 这里的
useRouter
方法返回的是一个 router 对象,你可以用它来做如导航到新页面、返回上一页面等操作。
(3)案例
- 通过普通按钮配合事件绑定实现路由页面跳转,不直接使用router-link标签
- HomeView.vue
<script setup>
import {useRouter} from 'vue-router'
let router = useRouter()
function handleTo() {
// 编程式路由
// 直接push一个路径
router.push('/about')
// push一个带有path属性的对象
router.push({path:'/about'})
}
localStorage.setItem('token','asdfa.afda.asdf')
</script>
<template>
<h1>首页</h1>
<h1>页面跳转</h1>
<router-link to="/about">
<button>跳转到about-html跳</button>
</router-link>
<button @click="handleTo">跳转到about-js跳</button>
<hr>
</template>
<style scoped>
</style>
3、路由传参(useRoute)
(1)请求地址中以 ? 形式携带(键值对参数)
- 类似与get请求通过url传参,数据是键值对形式的
- 例如: 查看数据详情
/showDetail?hid=1
,hid=1
就是要传递的键值对参数 - 在 Vue 3 和 Vue Router 4 中,你可以使用
useRoute
这个函数从 Vue 的组合式 API 中获取路由对象。 useRoute
方法返回的是当前的 route 对象,你可以用它来获取关于当前路由的信息,如当前的路径、查询参数等。
- 例如: 查看数据详情
(2)使用带参数的路径
- 请求地址中携带,例如:/about/数据/
- 在路由配置中,可以定义带参数的路径,通过在路由配置的path中使用
:
来定义参数名称。
(3)案例
需求:切换到ShowDetail.vue组件时,向该组件通过路由传递参数。
- App.vue
<script setup type="module">
import {useRouter} from 'vue-router'
//创建动态路由对象
let router = useRouter()
//动态路由路径传参方法
let showDetail= (id,language)=>{
// 尝试使用拼接字符串方式传递路径参数
//router.push(`showDetail/${id}/${languange}`)
/*路径参数,需要使用params */
router.push({name:"showDetail",params:{id:id,language:language}})
}
let showDetail2= (id,language)=>{
/*uri键值对参数,需要使用query */
router.push({path:"/showDetail2",query:{id:id,language:language}})
}
</script>
<template>
<div>
<h1>App页面</h1>
<hr/>
<!-- 路径参数 -->
<router-link to="/showDetail/1/JAVA">showDetail路径传参显示JAVA</router-link>
<button @click="showDetail(1,'JAVA')">showDetail动态路由路径传参显示JAVA</button>
<hr/>
<!-- 键值对参数 -->
<router-link v-bind:to="{path:'/showDetail2',query:{id:1,language:'Java'}}">showDetail2键值对传参显示JAVA</router-link>
<button @click="showDetail2(1,'JAVA')">showDetail2动态路由键值对传参显示JAVA</button>
<hr>
showDetail视图展示:<router-view name="showDetailView"></router-view>
<hr>
showDetail2视图展示:<router-view name="showDetailView2"></router-view>
</div>
</template>
<style scoped>
</style>
- 修改router/index.js增加路径参数占位符
// 导入路由创建的相关方法
import {createRouter,createWebHashHistory} from 'vue-router'
// 导入vue组件
import ShowDetail from '../components/ShowDetail.vue'
import ShowDetail2 from '../components/ShowDetail2.vue'
// 创建路由对象,声明路由规则
const router = createRouter({
history: createWebHashHistory(),
routes:[
{
/* 此处:id :language作为路径的占位符 */
path:'/showDetail/:id/:language',
/* 动态路由传参时,根据该名字找到该路由 */
name:'showDetail',
components:{
showDetailView:ShowDetail
}
},
{
path:'/showDetail2',
components:{
showDetailView2:ShowDetail2
}
},
]
})
// 对外暴露路由对象
export default router;
- ShowDetail.vue 通过useRoute获取路径参数
<script setup type="module">
import{useRoute} from 'vue-router'
import { onUpdated,ref } from 'vue';
// 获取当前的route对象
let route =useRoute()
let languageId = ref(0)
let languageName = ref('')
// 借助更新时生命周期,将数据更新进入响应式对象
onUpdated (()=>{
// 获取对象中的参数
languageId.value=route.params.id
languageName.value=route.params.language
console.log(languageId.value)
console.log(languageName.value)
})
</script>
<template>
<div>
<h1>ShowDetail页面</h1>
<h3>编号{{route.params.id}}:{{route.params.language}}是世界上最好的语言</h3>
<h3>编号{{languageId}}:{{languageName}}是世界上最好的语言</h3>
</div>
</template>
<style scoped>
</style>
- ShowDetail2.vue通过useRoute获取键值对参数
<script setup type="module">
import{useRoute} from 'vue-router'
import { onUpdated,ref } from 'vue';
// 获取当前的route对象
let route =useRoute()
let languageId = ref(0)
let languageName = ref('')
// 借助更新时生命周期,将数据更新进入响应式对象
onUpdated (()=>{
// 获取对象中的参数(通过query获取参数,此时参数是key-value形式的)
console.log(route.query)
console.log(languageId.value)
console.log(languageName.value)
languageId.value=route.query.id
languageName.value=route.query.language
})
</script>
<template>
<div>
<h1>ShowDetail2页面</h1>
<h3>编号{{route.query.id}}:{{route.query.language}}是世界上最好的语言</h3>
<h3>编号{{languageId}}:{{languageName}}是世界上最好的语言</h3>
</div>
</template>
<style scoped>
</style>
4、 路由重定向
路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面。 通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向。
- path 表示需要被重定向的 “原地址” ;
- redirect 表示将要被重定向到的 “新地址”
// 导入路由创建的相关方法
import {createRouter,createWebHashHistory} from 'vue-router'
// 导入vue组件
import Home from '../components/Home.vue'
import List from '../components/List.vue'
// 创建路由对象,声明路由规则
const router = createRouter({
history: createWebHashHistory(),
routes:[
{
path:'/',
components:{
default:Home,
homeView:Home
}
},
{
path:'/list',
components:{
listView : List
}
},
{
path:'/showAll',
// 重定向
redirect :'/list'
},
]
})
// 对外暴露路由对象
export default router;
5、路由嵌套–多级路由
(1)配置children属性
语法:
{
path : "/父路径",
component : 父组件,
children : [{
path : "子路径",
component : 子组件
}]
}
- 需要我们注意的是:子路径不能带 ’ / ’
- router/index.js
const routes = [
{
path: '/backend',
name: 'home',
component: HomeView,
children: [ //通过children配置子级路由
{
path: 'index', //此处一定不要写:/news
component: IndexView
},
{
path: 'order',
component: OrderView
},
{
path: 'goods',
component: GoodsView
}
]
},
{
path: '/about/:id',
name: 'about',
component: AboutView
}
]
(2)配置跳转路径
语法:
<router-link to="完整路径">内容</router-link>
-
需要注意的是这里的完整路径是从配置路由的第一层路径开始
-
HomeView.vue
<template>
<div class="home">
<div class="left">
<router-link to="/backend/index"><p>首页</p></router-link>
<router-link to="/backend/order"><p>订单管理</p></router-link>
<router-link to="/backend/goods"><p>商品管理</p></router-link>
</div>
<div class="right">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: 'HomeView',
methods: {}
}
</script>
<style scoped>
.home {
display: flex;
}
.left {
height: 500px;
width: 20%;
background-color: aquamarine;
}
.right {
height: 500px;
width: 80%;
background-color: gray;
}
</style>
(3)命名路由(可以简化路由的跳转)
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello' //给路由命名
path:'welcome',
component:Hello,
}
]
}
]
}
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>
<!--简化写法配合传递参数 -->
<router-link
:to="{
name:'hello',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
(4)router-link的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
- 如何开启replace模式:News
6、路由守卫
(1)介绍
在 Vue 3 中,路由守卫是用于在路由切换期间进行一些特定任务的回调函数。路由守卫可以用于许多任务,例如验证用户是否已登录、在路由切换前提供确认提示、请求数据等。Vue 3 为路由守卫提供了全面的支持,并提供了以下几种类型的路由守卫:
- 全局前置守卫:在路由切换前被调用,可以用于验证用户是否已登录、中断导航、请求数据等。
- 全局后置守卫:在路由切换之后被调用,可以用于处理数据、操作 DOM 、记录日志等。
- 守卫代码的位置: 在router.js中
//全局前置路由守卫
router.beforeEach( (to,from,next) => {
//to 是目标地包装对象 .path属性可以获取地址
//from 是来源地包装对象 .path属性可以获取地址
//next是方法,不调用默认拦截! next() 放行,直接到达目标组件
//next('/地址')可以转发到其他地址,到达目标组件前会再次经过前置路由守卫
console.log(to.path,from.path,next)
//需要判断,注意避免无限重定向
if(to.path == '/index'){
next()
}else{
next('/index')
}
} )
//全局后置路由守卫
router.afterEach((to, from) => {
console.log(`Navigate from ${from.path} to ${to.path}`);
});
(2)案例
登录案例,登录以后才可以进入home,否则必须进入login
- 定义Login.vue
<script setup>
import {ref} from 'vue'
import {useRouter} from 'vue-router'
let username =ref('')
let password =ref('')
let router = useRouter();
let login = () =>{
console.log(username.value,password.value)
if(username.value == 'root' & password.value == '123456'){
router.push({path:'/home',query:{'username':username.value}})
//登录成功利用前端存储机制,存储账号!
localStorage.setItem('username',username.value)
//sessionStorage.setItem('username',username)
}else{
alert('登录失败,账号或者密码错误!');
}
}
</script>
<template>
<div>
账号: <input type="text" v-model="username" placeholder="请输入账号!"><br>
密码: <input type="password" v-model="password" placeholder="请输入密码!"><br>
<button @click="login()">登录</button>
</div>
</template>
<style scoped>
</style>
- 定义Home.vue
<script setup>
import {ref} from 'vue'
import {useRoute,useRouter} from 'vue-router'
let route =useRoute()
let router = useRouter()
// 并不是每次进入home页时,都有用户名参数传入
//let username = route.query.username
let username =window.localStorage.getItem('username');
let logout= ()=>{
// 清除localStorge中的username
//window.sessionStorage.removeItem('username')
window.localStorage.removeItem('username')
// 动态路由到登录页
router.push("/login")
}
</script>
<template>
<div>
<h1>Home页面</h1>
<h3>欢迎{{username}}登录</h3>
<button @click="logout">退出登录</button>
</div>
</template>
<style scoped>
</style>
- App.vue
<script setup type="module">
</script>
<template>
<router-view></router-view>
</template>
<style scoped>
</style>
- 定义routers.js
// 导入路由创建的相关方法
import {createRouter,createWebHashHistory} from 'vue-router'
// 导入vue组件
import Home from '../components/Home.vue'
import Login from '../components/login.vue'
// 创建路由对象,声明路由规则
const router = createRouter({
history: createWebHashHistory(),
routes:[
{
path:'/home',
component:Home
},
{
path:'/',
redirect:"/home"
},
{
path:'/login',
component:Login
},
]
})
// 设置路由的全局前置守卫
router.beforeEach((to,from,next)=>{
/*
to 要去那
from 从哪里来
next 放行路由时需要调用的方法,不调用则不放行
*/
console.log(`从哪里来:${from.path},到哪里去:${to.path}`)
if(to.path == '/login'){
//放行路由 注意放行不要形成循环
next()
}else{
//let username =window.sessionStorage.getItem('username');
let username =window.localStorage.getItem('username');
if(null != username){
next()
}else{
next('/login')
}
}
})
// 设置路由的全局后置守卫
router.afterEach((to,from)=>{
console.log(`从哪里来:${from.path},到哪里去:${to.path}`)
})
// 对外暴露路由对象
export default router;
- 启动测试
npm run dev
7、路由两种工作模式
在许多现代 JavaScript 框架(如 Vue.js 和 React)中,前端路由器用于管理应用程序的 URL,并在 URL 发生变化时加载不同的组件或页面内容。路由历史对象负责记录用户在应用程序中浏览的历史记录,以便用户可以使用浏览器的前进和后退按钮导航。
路由的工作模式一共有两种:hash模式和history模式。我们可以在创建路由对象的时候对路由的工作模式进行配置,默认是hash模式,下面是vue3中路由工作模式的书写方式:
createWebHashHistory
:hash模式。createWebHashHistory()
是Vue.js
基于 hash 模式创建路由的工厂函数。在使用这种模式下,路由信息保存在 URL 的 hash 中,使用createWebHashHistory()
方法,可以创建一个路由历史记录对象,用于管理应用程序的路由。在Vue.js
应用中,通常使用该方法来创建路由的历史记录对象。createWebHistory
:history模式。createWebHistory
是一个用于创建路由历史对象的函数,通常在 Web 应用程序的前端路由中使用。在 Vue.js 中,createWebHistory
函数通常与createRouter
一起使用,用于创建基于 HTML5 History API 的路由历史对象。
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router";
const router = createRouter({
history: createWebHistory(),
routes,
})
(1)hash模式
- 对于一个url来说,什么是hash值? ==>
#
及其后面的内容就是hash值。 - hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器
https://192.168.1.1/api/v1/user#login
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 但是兼容性较好。
因为 #
后面的内容不会当做路径传给服务器,有更强的兼容性,不会出现项目部署到服务器上后刷新找不到路径的问题。
(2)history模式
- history模式下的路径什么就是正常访问网站路径
https://192.168.1.1/api/v1/user/login
- 地址干净,美观
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
8、路由懒加载
(1)介绍
路由懒加载是一种将路由组件按需异步加载的方式,只有当路由对应的组件需要使用时,才会动态地加载该组件对应的代码。使用路由懒加载可以优化应用程序的性能。
-
当我们把项目写完过后打包出来的JavaScript包会变得非常大,会影响性能。
-
如果把不同的组件分割成不同的代码块,当路由被访问的时候才加载相应组件,这样就会更加高效。
-
component: ()=> import(“组件路径”);
注意:我们引入组件的步骤被放到了component配置中,所以不需要再引入组件了。
(2)示例
在Vue Router中使用路由懒加载,我们可以通过使用import()
和动态import()
两种方式来实现
使用import()方式实现懒加载:
const Home = () => import('./views/Home.vue')
const About = () => import('./views/About.vue')
const routes = [
{
path: '/',
component: Home
},
{
path: '/about',
component: About
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
使用动态import()方式实现懒加载:
const routes = [
{
path: '/',
component: () => import('./views/Home.vue')
},
{
path: '/about',
component: () => import('./views/About.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
六、Vue3状态管理器Pinia
1、什么是Pinia?
Pinia(发音为 /piːnjʌ/
,类似于英语中的“peenya”)是最接近有效包名 piña(西班牙语中的_pineapple_)的词。 Pinia 是 Vue 的存储库,Pinia和Vuex一样都是是vue的全局状态管理器,它允许跨组件/页面共享状态。实际上,其实Pinia就是Vuex5,官网也说过,为了尊重原作者,所以取名 pinia,而没有取名 Vuex,所以大家可以直接将 pinia 比作为 Vue3 的 Vuex。
- 官网文档:Pinia 中文文档
2、对比vuex
- Pinia 同时支持 Vue2 以及 Vue3 ,这让同时使用两个版本的小伙伴更容易上手;
- Pinia 中只存在 State,getter,action,剔除掉了 Vuex 中的 Mutation 及 Module;
- Pinia 中的 action 可同时支持同步任务、异步任务;
- 更友好的支持了 TypeScript ,无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断;
- Pinia 在修改状态的时候不需要通过其他 api,如:vuex 需通过 commit,dispatch 来修改,所以在语法上比 vuex 更容易理解和使用灵活;
- 由于去除掉了 Module ,无需再创建各个模块嵌套了。Vuex 中,如果数据过多,通常会通过划分模块来进行管理,而 Pinia 中,每个 Store 都是独立的,互不影响;
- 支持服务端渲染;
3、使用步骤
(1)安装
npm install pinia
(2)创建js文件
- 在store/counter.js,写入代码,可以定义多个
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
//1 定义变量
state: () => {
return {
count: 0,
hobby:'篮球'
}
},
//2 这里面写方法,与后端交互或逻辑判断,再操作数据
actions: {
increment(good_id) {
// 跟后端交互--》把good_id--》真正加购物车
this.count++
},
changeHobby(hobby){
this.hobby=hobby
}
},
//3 getter-->获取数据
getters: {
getCount(){
return this.count
},
},
})
(3)main.js中使用插件
import {createPinia} from 'pinia'
const pinia = createPinia()
createApp(App).use(router).use(pinia).mount('#app')
(4)组件中使用
- 在组件中使用pinia的数据
import { useCounterStore} from '../store/counter';
let counter= useCounterStore()
// 以后通过counter对象--》操作其中state,getter,action的东西
//Pinia 中的state、getter 和 action,我们可以假设这些概念相当于组件中的 data、 computed 和 methods。
(5)注意
-
State (状态) 在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。
-
getter函数推荐使用箭头函数,并且它将接收
state
作为第一个参数:
// getter-->获取数据
getters: {
getCount:(state)=>{
return state.count
},
},
- Action 相当于组件中的 method。它们可以通过
defineStore()
中的actions
属性来定义,并且它们也是定义业务逻辑的完美选择。类似 getter,action 也可通过this
访问整个 store 实例,并支持完整的类型标注(以及自动补全)。不同的是,action
可以是异步的,你可以在它们里面await
调用任何 API,以及其他 action!
七、elementui-plus
1、介绍
本节要叙述的是elementui-plus,是一个基于 Vue 3,面向设计师和开发者的组件库。旨在帮助开发者构建出现代化、美观且高效的 Web 应用程序界面。它是对 Element UI 的进一步发展,专注于提供更好的性能、更丰富的组件以及更好的开发体验。
Element Plus 是 Element UI 的一个分支和进化版本。Element UI 是一个非常受欢迎的 Vue UI 组件库,旨在为开发者提供现代、美观的界面组件。Element Plus 则是在 Element UI 的基础上进一步发展而来,专注于提供更好的性能、更丰富的组件以及更好的开发体验,同时也兼容了 Vue 3 的新特性。因此,可以说 Element Plus 是 Element UI 的下一个版本,是 Element UI 的升级和扩展。
- 官方文档:一个 Vue 3 UI 框架 | Element Plus
但是另一款组件库也值得我们去学习:Ant Design Vue
2、使用
(1)安装
cnpm install element-plus --save
(2)注册
- main.js中注册
//导入element-plus相关内容
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).use(router).use(pinia).use(ElementPlus).mount('#app')
(3)在组件中使用
<script setup>
import { ElMessage } from 'element-plus'
const open2 = () => {
ElMessage({
message: '恭喜您成功了',
type: 'success',
})
}
</script>
<template>
<div class="mb-4">
<el-button>Default</el-button>
<el-button type="primary">Primary</el-button>
<el-button type="success">Success</el-button>
<el-button type="info">Info</el-button>
<el-button type="warning">Warning</el-button>
<el-button type="danger">Danger</el-button>
</div>
<div>
<el-card style="max-width: 480px">
<template #header>Yummy hamburger</template>
<img
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
style="width: 100%"
/>
</el-card>
</div>
<div>
<el-button :plain="true" @click="open2">Message</el-button>
</div>
</template>
<style scoped>
</style>
八、补充 代理模式
在 Python 中,代理模式是一种结构型设计模式,其目的是通过引入一个代理对象来控制对另一个对象的访问。代理通常充当客户端和实际对象之间的中间人,从而可以在访问实际对象时添加额外的功能,如权限控制、缓存、延迟加载等。
以下是一个简单的示例,演示了如何在 Python 中实现代理模式:
# 实际对象
class RealSubject:
def request(self):
print("RealSubject: Handling request")
# 代理对象
class Proxy:
def __init__(self, real_subject):
self.real_subject = real_subject
def request(self):
if self.check_access():
self.real_subject.request()
self.log_access()
def check_access(self):
# 检查访问权限
print("Proxy: Checking access")
return True
def log_access(self):
# 记录访问日志
print("Proxy: Logging the time of request")
# 客户端代码
real_subject = RealSubject()
proxy = Proxy(real_subject)
# 通过代理对象访问实际对象
proxy.request()
在这个示例中,RealSubject
是实际的对象,而 Proxy
是代理对象。代理对象在调用 request
方法时会先检查访问权限,然后再调用实际对象的 request
方法,并记录访问日志。
代理模式的优点包括:
- 安全控制:代理可以控制客户端对对象的访问权限。
- 延迟加载:代理可以延迟加载实际对象,直到客户端真正需要访问它。
- 缓存:代理可以缓存实际对象的结果,避免重复计算。
- 简化客户端:客户端可以与代理对象交互,而无需直接与实际对象交互。