一、常用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 方法--》点击年龄+1function handleAdd() {console.log(age) // age 的类型不是数字了,而是RefImplage.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.valueconst x2 = person.nameconsole.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 写在这里是就是beforeCreateconsole.log('beforeCreate')const age=ref(19)function addAge(){age.value++}//2 写在这里是就是createdconsole.log('created',age.value)//3 beforeMount-->onBeforeMount// onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmountedonBeforeMount(()=>{console.log('onBeforeMount','组件挂载前')})//4 mounted-->onMountedonMounted(()=>{console.log('onMounted','组件挂载后')})//5 beforeUpdate-->onBeforeUpdateonBeforeUpdate(()=>{console.log('onBeforeUpdate','更新之前')})//6 updated-->onUpdatedonUpdated(()=>{console.log('onUpdated','更新之后')console.log(age.value)})//7 beforeUnmount-->onBeforeUnmountonBeforeUnmount(()=>{console.log('onBeforeUnmount','销毁之前')})//8 unmounted-->onUnmountedonUnmounted(()=>{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() {// toRefslet person = reactive({name: 'xiao', age: 19})function addAge() {person.age += 2console.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.resultObject.assign(filmList,res.data.results)// (2) 解构赋值let {data}=res // res={data:{code:100,msg:成功}}Object.assign(filmList,data.results)// (3) 解构赋值let {data: {results}} = resObject.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` 是用于请求的服务器 URLurl: '/user',//2 `method` 是创建请求时使用的方法method: 'get', // 默认值//3 `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URLbaseURL: '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, Bufferdata: {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 Authauth: {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 requestsresponseEncoding: '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/cancellationcancelToken: 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 = valuedef add(self, x):self.value += xreturn self # 返回自身以支持链式调用def multiply(self, x):self.value *= xreturn 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.idlanguageName.value=route.params.languageconsole.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.idlanguageName.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', //此处一定不要写:/newscomponent: 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.usernamelet 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><imgsrc="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_subjectdef request(self):if self.check_access():self.real_subject.request()self.log_access()def check_access(self):# 检查访问权限print("Proxy: Checking access")return Truedef log_access(self):# 记录访问日志print("Proxy: Logging the time of request")# 客户端代码
real_subject = RealSubject()
proxy = Proxy(real_subject)# 通过代理对象访问实际对象
proxy.request()
在这个示例中,RealSubject
是实际的对象,而 Proxy
是代理对象。代理对象在调用 request
方法时会先检查访问权限,然后再调用实际对象的 request
方法,并记录访问日志。
代理模式的优点包括:
- 安全控制:代理可以控制客户端对对象的访问权限。
- 延迟加载:代理可以延迟加载实际对象,直到客户端真正需要访问它。
- 缓存:代理可以缓存实际对象的结果,避免重复计算。
- 简化客户端:客户端可以与代理对象交互,而无需直接与实际对象交互。