创建第一个vue项目
1.安装node.js
cmd输入node查看是否安装成功
2.vscode开启一个终端,配置淘宝镜像
# 修改为淘宝镜像源
npm config set registry https://registry.npmmirror.com
输入如下命令创建第一个Vue项目
3.下载依赖,启动项目
访问5173端口
第一个Vue项目的目录结构
我们先打开main.ts,这是我们vue工程的入口文件
在这里创建vue程序,并把它挂载到id为app的标签下(ps:这里的App.vue是我们的根组件)
再打开index.html
这里将main.js引入,并挂载了app程序到这里。
最后打开App.vue,也就是我们的根组件
先不看其他编码的细节,我们可以大胆猜测HelloWorld.vue被挂到App.vue里面,证实了它是根组件。
简单验证
Hello.vue
<template>
<h1>{{ msg }}</h1>
</template>
<script lang="ts" setup>
let msg="Hello"
</script>
<style>
</style>
App.vue
<template>
<Hello></Hello>
<h1>{{msg}}</h1>
<Hello></Hello>
</template>
<script lang="ts" setup>
import Hello from './components/Hello.vue';//引入Hello.vue
let msg="App"
</script>
<style>
</style>
setup概述
setup语法糖
setup函数有一个语法糖,可以让我们把setup函数从script里面独立出去
<script setup lang="ts">
console.log(this) //undefined
// 数据(注意:此时的name、age、tel都不是响应式数据)
let name = '张三'
let age = 18
let tel = '13888888888'
// 方法
function changName(){
name = '李四'//注意:此时这么修改name页面是不变化的
}
function changAge(){
console.log(age)
age += 1 //注意:此时这么修改age页面是不变化的
}
function showTel(){
alert(tel)
}
</script>
Vue的生命周期
setup函数会在beforeCreate函数前调用
响应式数据
基本类型的响应式数据
只能用ref
<template>
<!-- alt+shift+a 注释 -->
<h1>{{ msg }}</h1>
<!-- 在template中,不用age.value 会自动帮我们 .value -->
<h1>年龄{{ age }}</h1>
<button @click="f1">点我年龄+1</button>
</template>
<script lang="ts" setup name="Hello">
import { ref } from 'vue';
let msg="Hello"
let age=ref(18)//用ref包裹,让他成为响应式数据
function f1(){
age.value+=1 //在script中,需要age.value才能得到它的值
}
</script>
在js中操作数据需要 .value 在模板中不需要
对象类型的响应式数据
可用reactive,也可用ref
reactive
//对象类型的直接用reactive包起来即可
let s1=reactive({
id:"1",
name:"张三",
age:"28",
sex:"男",
classroom:"3"
})
//取值不需要 .value
function changeS1Name(){
s1.name="张十三"
}
ref
仍需要 .value
由下图可知,ref实现响应式对象的底层还是使用了reactive
ref和reactive的区别
<template>
<ul>
<li>品牌:{{ car1.brand }}</li>
<li>价格:{{ car1.price }}</li>
</ul>
<button @click="f1">ref改变</button>
<br>
<ul>
<li>品牌:{{ car2.brand }}</li>
<li>价格:{{ car2.price }}</li>
</ul>
<button @click="f2">reactive改变1</button>
<button @click="f3">reactive改变2</button>
</template>
<script lang="ts" setup>
import { ref,reactive } from 'vue';
//选中shift+ (
let car1=ref({
brand:"雪佛兰",
price:666
})
let car2=reactive({
brand:"奔驰",
price:123
})
function f1(){
car1.value={
brand:"宝马",
price:987
}
}//可以
function f2(){
car2={
brand:"宝马",
price:987
}
/* car2.brand="wda" */
}//失效 无法获得Car的响应式对象,要改整体只能成员属性一个个地改
function f3(){
Object.assign(car2,{brand:"宝马",price:987})
}//这样可以,直接分配一个新对象给car2
</script>
解构响应式对象
如果我们要处理一个有很多成员的响应式对象,且我们只希望对其中几个成员进行修改,那么就可以对其进行解构
<template>
姓名<h1>{{name }}</h1>
<br>
年龄<h1>{{ age }}</h1>
<br>
性别<h1>{{ sex }}</h1>
其他我们不想实时修改的属性<h1>{{ person.others }}</h1>
<button @click="changeMsg">修改</button>
</template>
<script lang="ts" setup>
import { reactive,toRefs,toRef} from 'vue';
let person=reactive({
name:"张胜男",
age:22,
sex:"女",
others:"其他属性..."
})
//将解构出来的成员编程ref响应式对象
let {name,age}=toRefs(person)//解构多个
let sex=toRef(person,"sex")//解构单个
function changeMsg(){
//因为解构出来的是ref对象,所以要 .value
name.value="李四";
age.value=99;
sex.value="男"
//此时页面可以变化
console.log(name,age,sex);
}
/* let {name,age}=person
let sex=person.sex
function changeMsg(){
name="李四";
age=99;
sex="男"
// 李四,99,男
// 但是页面上的姓名和年龄并没有改变
// 这是因为解构出来的成员并不是响应式的
console.log(name,age,sex);
} */
</script>
计算属性
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。
它的格式如下:
const 我们要计算的属性=computed(function(){
...
return 计算结果
})
案例
<template>
姓<input v-model="xing">
<br>
名<input v-model="ming">
<br>
姓名<span>{{ name }}</span>
</template>
<script lang="ts" setup>
import { computed,ref } from 'vue';
let xing=ref("yang")
let ming=ref("wei")
let name=computed(function (){
return xing.value.slice(0,1).toUpperCase()+xing.value.slice(1)+"-"+ming.value
})
</script>
<style>
span{
border: 1px solid red;
}
</style>
计算属性优于函数调用的地方在于它有缓存机制,刷新页面时函数会重新计算,计算属性检查调用的变量的值,没有变动的话直接返回上次计算的结果。
此外,计算属性的值是只读的
要对他实现可读可写要在computed函数里面提供get和set方法
<template>
姓<input v-model="xing">
<br>
名<input v-model="ming">
<br>
姓名<span>{{ name }}</span>
<br>
<button @click="changeValue">对计算属性的直接写操作会调用set方法,并且值会给到set方法的形参,但是还是得通过更改调用的变量值才能复写</button>
</template>
<script lang="ts" setup>
import { computed,ref } from 'vue';
let xing=ref("yang")
let ming=ref("wei")
let name=computed({
get(){
return xing.value.slice(0,1).toUpperCase()+xing.value.slice(1)+"-"+ming.value
},
set(val){
let[str1,str2]=val.split("-")
xing.value=str1
ming.value=str2
}
})
function changeValue(){
name.value="Li-si"
}
</script>
对计算属性的直接写操作会调用set方法,并且值会给到set方法的形参,但是还是得通过更改调用的变量值才能复写
Vue常用指令
条件渲染
案例
<!-- 条件渲染
-->
<template>
<span v-if="flag">flag为true时显示这个span</span>
<br>
<span v-show="flag">条件为真时渲染</span>
<br>
<span v-if="sex==1">🚹</span>
<span v-else-if="sex==0">🚺</span>
<span v-else>不男不女</span>
<button @click="f1">改变sex值</button>
<br>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
let flag=true
let sex=ref(0)
function f1(){
if(sex.value==0){
sex.value=1
return
}
else{
sex.value=0
}
}
</script>
v-if和v-show的区别,当判定条件为假时,v-show只是不显示它所包裹的内容,会保留它的结构,而v-if时直接删除,不保留
列表渲染
案例
<!-- 列表渲染 -->
<template>
<table>
<tr v-for="stu in students" :key="stu.id">
<td>{{stu.id}}</td>
<td>{{stu.name}}</td>
<td>{{stu.age}}</td>
<td v-if="stu.sex==0">🚹</td>
<td v-else>🚺</td>
<td>{{ stu.classroom }}</td>
</tr>
</table>
<!-- v-for -->
<span>**************************</span>
<div v-for="value in myObject">
<span>{{ value }}</span>
</div>
<span>**************************</span>
<div v-for="(stu, key) in students">
{{ key }}: {{ stu }}
</div>
<span>**************************</span>
<div v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
let students=reactive([
{
id:"2",
name:"李四",
age:"36",
sex:1,
classroom:"2"
},
{
id:"3",
name:"周天",
age:"77",
sex:0,
classroom:"1"
},
{
id:"4",
name:"王五",
age:"99",
sex:0,
classroom:"8"
},
{
id:"5",
name:"刘六",
age:"10",
sex:1,
classroom:"5"
}
])
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
watch
作用:监视数据的变化
牢记watch只能监视如下4种数据:
ref
定义的数据。reactive
定义的数据。- 函数返回一个值(
getter
函数)。 - 一个包含上述内容的数组。
因为ref可以定义基本类型和对象类型,所以watch对数据的监视可以分为5种情况
1.watch监视ref定义的基本类型数据
<template>
<div class="person">
<h1>情况一:监视ref定义的基本类型数据</h1>
<h2>当前求和为:{{sum}}</h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>
<script lang="ts" setup >
import {ref,watch} from 'vue'
// 数据
let sum = ref(0)
// 方法
function changeSum(){
sum.value += 1
}
/*
watch参数 要监视的数据 一个回调函数(在被监视的数据的地址值发生改变时被调用)
watch返回一个停止监视函数 调用它停止对watch的监视
*/
const stop=watch(sum,(newValue,oldValue)=>{
console.log("新值:"+newValue+"--"+"旧值"+oldValue)
if(newValue>=10){
stop() //停止监视
}
})
</script>
重点:回调函数在数据的地址值发生变化时被调用
2.watch监视ref定义的对象类型数据
<template>
<div class="person">
<h1>情况二:监视【ref】定义的【对象类型】数据</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个人</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
// 数据
let person = ref({
name:'张三',
age:18
})
// 方法
function changeName(){
person.value.name += '~'
}
function changeAge(){
person.value.age += 1
}
function changePerson(){
person.value = {name:'李四',age:90}
}
/*
监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视
watch的第一个参数是:被监视的数据
watch的第二个参数是:监视的回调
watch的第三个参数是:配置对象(deep、immediate等等.....)
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{deep:true})
</script>
可以看到,开启深度监视后,name和age字段的改变也会被我们监视到
****************************************
如果watch是这样写的(没有开启深度监视,那么只有修改整个人,回调函数才会被调用)
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
})
3.watch监视reactive定义的对象类型数据
<template>
<h2>姓名:{{ student.name }}</h2>
<h2>年龄:{{ student.age }}</h2>
<button @click="f1">改变姓名</button>
<button @click="f2">改变年龄</button>
<button @click="f3">改变整个人</button>
</template>
<script setup>
import { reactive, watch } from 'vue';
let student=reactive({
"name":"张三",
"age":13
})
function f1(){
student.name=student.name+"~"
}
function f2(){
student.age=student.age+1
}
function f3(){
Object.assign(student,{
"name":"李四",
"age":99
})
}
watch(student,(newValue,oldValue)=>{
console.log("发生变化",newValue,oldValue)
})
/*
这样是不行的 因为reactive定义的整个对象会失去响应式
function f3(){
student={
"name":"李四",
"age":99
}
}*/
</script>
注意看,这里的newValue和oldValue都是一样的
我们首先需要明确的是:
1.回调函数只有被监视数据的地址发生改变时才被调用
2.reactive监视的数据默认开启深度监视,也就是student里面的name和age会被监视到
以改变姓名举例,watch监视到name地址值改变,回调函数被调用,但是我们监视的是student这个整体,它的地址值未曾改变(Object.assign只是覆盖了两个字段的值),因此这时候拿到的newValue和oldValue都是修改后的值。
在实际开发中,我们一般只用到修改后的值,因此不需要太纠结。
watch(person,(value)=>{
console.log('我只要修改后的值',value)
})
4.watch监视ref或reactive定义的对象类型里面的某个属性
reactive
<template>
<div class="person">
<h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeC1">修改第一台车</button>
<button @click="changeC2">修改第二台车</button>
<button @click="changeCar">修改整个车</button>
</div>
</template>
<script lang="ts" setup>
import {reactive,watch} from 'vue'
// 数据
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
// 方法
function changeName(){
person.name += '~'
}
function changeAge(){
person.age += 1
}
function changeC1(){
person.car.c1 = '奥迪'
}
function changeC2(){
person.car.c2 = '大众'
}
function changeCar(){
person.car = {c1:'雅迪',c2:'爱玛'}
}
</script>
如果要监视的是对象里面的一个基本数据类型,要用getter方法
// 监视,情况四:监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
watch(()=> person.name,(newValue,oldValue)=>{
console.log('person.name变化了',newValue,oldValue)
})
如果监视的是对象里面的一个对象类型,可以用getter方法,也可以不用,如果需要监视这个被监视的对象的里面,还是要开启深度监视
// 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
watch(()=>person.car,(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
},{deep:true})
ref(同理reactive)
<template>
<div class="person">
<h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeC1">修改第一台车</button>
<button @click="changeC2">修改第二台车</button>
<button @click="changeCar">修改整个车</button>
</div>
</template>
<script lang="ts" setup>
import {ref,watch} from 'vue'
// 数据
let person = ref({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
// 方法
function changeName(){
person.value.name+="~"
}
function changeAge(){
person.value.age += 1
}
function changeC1(){
person.value.car.c1 = '奥迪'
}
function changeC2(){
person.value.car.c2 = '大众'
}
function changeCar(){
person.value.car = {c1:'雅迪',c2:'爱玛'}
}
</script>
如果要监视的是对象里面的一个基本数据类型,要用getter方法
watch(()=>person.value.name,(newValue,oldValue)=>{
console.log("name改变了",newValue,oldValue)
})
监视对象里面的一个对象类型,开启深度监视
watch(()=>person.value.car,(newValue,oldValue)=>{
console.log("car改变了",newValue,oldValue)
},{deep:true})
5.监视多个数据
统一用getter方法 然后开深度监视
<template>
<div class="person">
<h1>情况五:监视上述的多个数据</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeC1">修改第一台车</button>
<button @click="changeC2">修改第二台车</button>
<button @click="changeCar">修改整个车</button>
</div>
</template>
<script lang="ts" setup >
import {reactive,watch} from 'vue'
// 数据
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
// 方法
function changeName(){
person.name += '~'
}
function changeAge(){
person.age += 1
}
function changeC1(){
person.car.c1 = '奥迪'
}
function changeC2(){
person.car.c2 = '大众'
}
function changeCar(){
person.car = {c1:'雅迪',c2:'爱玛'} //因为这里是直接赋值 所以地址值改变了
}
// 监视,情况五:监视上述的多个数据
watch(()=>[person.car,person.age],(newValue,oldValue)=>{
console.log("car或age被修改",newValue,oldValue)
},{deep:true})
</script>
watchEffect
官网:立即运行一个函数(相当于{immediate}),同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
说的通俗点就是watch的升级版,直接写你的代码逻辑,他会根据你用到的去监听,不需要你自己去告诉它你要监视哪些数据。
<template>
<div class="person">
<h1>需求:水温达到50℃,或水位达到20cm,则联系服务器</h1>
<h2 id="demo">水温:{{temp}}</h2>
<h2>水位:{{height}}</h2>
<button @click="changePrice">水温+10</button>
<button @click="changeSum">水位+10</button>
</div>
</template>
<script lang="ts" setup>
import {ref,watch,watchEffect} from 'vue'
// 数据
let temp = ref(0)
let height = ref(0)
// 方法
function changePrice(){
temp.value += 10
}
function changeSum(){
height.value += 10
}
/*
// 用watch实现,需要明确的指出要监视:temp、height
watch([temp,height],(value)=>{
// 从value中获取最新的temp值、height值
const [newTemp,newHeight] = value
// 室温达到50℃,或水位达到20cm,立刻联系服务器
if(newTemp >= 50 || newHeight >= 20){
console.log('联系服务器')
}
})
*/
// 用watchEffect实现,直接写你的代码逻辑,他会根据你用到的去监听
//格式:watchEffect( ()=>{} )
const stopWtach = watchEffect(()=>{
// 室温达到50℃,或水位达到20cm,立刻联系服务器
if(temp.value >= 50 || height.value >= 20){
console.log(document.getElementById('demo')?.innerText)
console.log('联系服务器')
}
// 水温达到100,或水位达到50,取消监视
if(temp.value === 100 || height.value === 50){
console.log('清理了')
stopWtach()
}
})
</script>
标签的ref属性
用ref标记dom元素
取代了用id来标识标签,因为用id来标识标签,如果父组件和子组件中有id一样的,会引起混淆。
用ref标记组件
子组件
向父亲暴露name,age,classroom(非响应式) sex不暴露
父组件
得到子组件的实例,测试按钮用于测试能否获取子组件的数据
测试
TS中的接口,泛型
在src中新建一个和组件文件夹同级的types文件夹,里面写ts文件
定义一个学生接口,用于规范学生内的成员(记得导出)
使用接口规范数据格式
集合可用泛型
age 加上一个 ? 表示这个字段是可选的
没有age也不报错
reactive直接传泛型也可以
props的使用(父组件给子组件传数据)
第一种:接收并保存到props里面
父组件
子组件
测试
第二种:接收+限制类型
父组件
同上
如果父组件给出的数据和子组件要求的不一样,会报错
子组件
测试
同上
第三种:
父组件
故意不传myList
子组件
怎么写:第二种情况的语句 作为withDefaults的 第一个参数,第二个参数用大括号包裹(因为有多个不同类型的数据,是对象)起来,里面 key: value 其中value要用返回值的形式给出(这里用箭头函数)
测试
Vue的生命周期
总述
测试Vue3的生命周期
父组件
<!-- 父组件App.vue -->
<template>
<TestVueLifeCycle></TestVueLifeCycle>
</template>
<script lang="ts" setup name="App">
import TestVueLifeCycle from './components/TestVueLifeCycle.vue';
import { onBeforeMount,onMounted } from 'vue';
onBeforeMount(()=>{
console.log("父-挂载前")
})
onMounted(()=>{
console.log("父-挂载完成")
})
</script>
子组件
<template>
<h2>{{ count }}</h2>
<button @click="f1">点我count+1</button>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { onBeforeMount,onMounted } from 'vue';//挂载前,挂载完成
import { onBeforeUpdate,onUpdated } from 'vue';//更新前,更新完成
import { onBeforeUnmount,onUnmounted } from 'vue';//销毁前,销毁完成
let count=ref(0)
function f1(){
count.value++
}
console.log("setup完成vue2中创建前,创建完成工作")
onBeforeMount(()=>{
console.log("子-挂载前")
})
onMounted(()=>{
console.log("子-挂载完成")
})
onBeforeUpdate(()=>{
console.log("更新前")
})
onUpdated((()=>{
console.log("更新完成")
}))
onBeforeUnmount(()=>{
console.log("销毁前")
})
onUnmounted(()=>{
console.log("销毁完成")
})
</script>
测试
更新的钩子函数的执行不难理解(执行了两次按钮点击的效果),那前面的钩子为什么会这样执行呢?
答:
1.运行我们的vue工程,首先来到index.html,这是我们的入口html,它调用了main.ts
2.来到main.ts,这里调用了App.vue,并尝试创建它并将它挂载到index.html的名为app的div标签下(输出 父-setup 父-挂载前)
3.来到App.vue 它自上而下执行,所以会先执行所有孩子的创建和挂载工作,等所有的孩子都创建和挂载完成后,App.vue才执行onMounted函数
自定义hooks
准备工作:首先导入axios的依赖
案例
首先写一个子组件,如下
<template>
<h2>{{ sum }}</h2>
<button @click="addSum">点我sum+1</button>
<hr>
<img v-for="(dog,index) in dogs" :key="index" :src="dog.src">
<br>
<button @click="addDog">点我狗狗+1</button>
</template>
<script lang="ts" setup>
import axios from 'axios';
import { ref,reactive } from 'vue';
let sum=ref(0)
let dogs=reactive([{src:"https://images.dog.ceo/breeds/pembroke/n02113023_6826.jpg"}])
function addSum(){
sum.value++;
}
function addDog(){
axios.get('https://dog.ceo/api/breed/pembroke/images/random').then(
response=>{
let msg={src:response.data.message}
console.log(msg)
dogs.push(msg)
}
).catch(error=>{
console.log(error,"找不到路径")
}
)
}
</script>
<style>
img{
height: 100px;
margin-left: 5px;
}
</style>
这个组件实现的效果如下(有两个功能,点击按钮sum+1 点击按钮出现一张新的狗狗的图片)
注意观察代码的结构,如果一个组件要实现的功能多起来时,方法和数据都放在一个vue里面,会导致代码特别臃肿,要同时找到一个方法所需要的数据和方法也十分困难,这时候hook就派上用场了。
什么是hook?hook有什么优势?
- 什么是
hook
?—— 本质是一个函数,把setup
函数中使用的Composition API
进行了封装,类似于vue2.x
中的mixin
。 - 自定义
hook
的优势:复用代码, 让setup
中的逻辑更清楚易懂。
用自定义hook对上述代码进行改进
1.首先在src目录下新建一个hooks目录
2.编写自定义hook(按功能分类,将一个功能的方法和数据抽离出来,用函数包裹,最后把需要暴露的数据和方法交出去)
注意hook的命名规范为: useXxxxx
useSum.ts
useDog.ts
3.在vue中调用hook
<template>
<h2>{{ sum }}</h2>
<button @click="addSum">点我sum+1</button>
<hr>
<img v-for="(dog,index) in dogs" :key="index" :src="dog.src">
<br>
<button @click="addDog">点我狗狗+1</button>
</template>
<script lang="ts" setup>
/* 引入 */
import useSum from '../hooks/useSum';
import useDog from '../hooks/useDog';
/* 调用 */
let {sum,addSum}=useSum()
let {dogs,addDog}=useDog()
</script>
<style>
img{
height: 100px;
margin-left: 5px;
}
</style>
十分清爽,这里体现了vue3组合式api的思想
Vue中的路由
用路由器实现一个基本的切换效果
首先先安装路由器依赖
1.在src文件夹中创建一个router文件夹
2.准备好需要的vue们
这里准备了一个Main.vue作为主界面,要在这里面切换Community,Friend,Home三个组件
2.写一个router
MyRouter.ts
import { createRouter,createWebHistory } from "vue-router";
import community from "../components/Community.vue";
import friend from "../components/Friend.vue";
import home from "../components/Home.vue";
const router=createRouter({
history:createWebHistory(),
routes:[
{
path:"/community",
component:community
},
{
path:"/friend",
component:friend
},
{
path:"/home",
component:home
}
]
})
3.在main.ts里面注册这个router
import { createApp } from "vue";
import App from "./App.vue";
//引入路由器
import router from "./router/MyRouter";
const app=createApp(App)
app.use(router)//注册路由器
app.mount('#app')
4.在主界面上用RouteLink组件绑定对路由路径的响应
Main.vue
<!-- 登录成功后的主界面 -->
<template>
<h2>登录成功后的主界面</h2>
<div class="app">
<!-- 导航区 -->
<div class="navigate">
<RouterLink to="/community" active-class="active" class="navi-style">社区</RouterLink>
<RouterLink to="/friend" active-class="active" class="navi-style">交友</RouterLink>
<RouterLink to="/home" active-class="active" class="navi-style">个人中心</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup>
import {RouterLink,RouterView} from 'vue-router'
</script>
<style>
.active{
color: red;
}
.navi-style{
margin-left: 10px;
}
</style>
Community.vue(其他两个结构同这个一样)
<!-- 社区模块 -->
<template>
<h2>社区模块</h2>
</template>
<script lang="ts" setup>
</script>
<style>
</style>
效果
两个注意点
1.路由组件通常存放在pages
或 views
文件夹,一般组件通常存放在components
文件夹。(ps:路由组件就是通过路由器才能被渲染的组件,也就是写在RouterLink里面的组件)
2.通过点击导航,视觉效果上“消失” 了的路由组件,默认是被卸载掉的,需要的时候再去挂载。
路由器工作模式
history模式
hash模式
怎么写要跳转的路由(三种方法)
to的两种写法
<!-- 第一种:to的字符串写法 -->
<router-link active-class="active" to="/home">主页</router-link>
<!-- 第二种:to的对象写法 -->
<router-link active-class="active" :to="{path:'/home'}">Home</router-link>
命名路由(后面可以用来简化路由跳转和传参的问题)
在路由器中给路由规则命名
在Main.vue中这样写
可以看到第一种是to的字符串写法,后面两种是to的对象写法
<!-- <RouterLink to="/community" active-class="active" class="navi-style">社区</RouterLink> -->
<!-- 里面只能用单引号 -->
<!-- <RouterLink :to="{path:'/community'}" active-class="active" class="navi-style">社区</RouterLink> -->
<RouterLink :to="{name:'shequ'}" active-class="active" class="navi-style">社区</RouterLink>
嵌套路由
要实现的效果:点击社区模块显示所有的帖子,点击帖子标题在下方显示帖子的详情
1.编写Post.vue
2.将Post.vue 配置成Community.vue的子路由
const router=createRouter({
history:createWebHistory(),
routes:[
{
path:"/community",
name:"shequ",
component:community,
//配置子路由
children:[
{
path:"post",
name:"tiezi",
component:post
}
]
},
{
path:"/friend",
component:friend
},
{
path:"/home",
component:home
}
]
})
export default router
3.在Community中写跳转
前两种都要加完整路径
<!-- 社区模块 -->
<template>
<h2>社区模块</h2>
<li v-for="post in postList" :key="post.postId">
<!-- <RouterLink to="/community/post">{{ post.title }}</RouterLink> -->
<!-- <RouterLink :to="{path:'/community/post'}">{{ post.title }}</RouterLink> -->
<RouterLink :to="{name:'tiezi'}">{{ post.title }}</RouterLink>
</li>
<router-view></router-view>
</template>
<script lang="ts" setup>
import { onMounted,onUnmounted } from 'vue';
let postList=[
{postId:"1",title:"今天天气真好",content:"我想出去玩"},
{postId:"2",title:"最近有电影好看?",content:"推荐一下"},
{postId:"3",title:"再也不万元神了",content:"是不可能的"},
{postId:"4",title:"一眼顶针",content:"鉴定为假"},
{postId:"5",title:"哈哈哈哈",content:"1234889"}
]
//切换到这里挂载,切换到别的view被卸载
onMounted(()=>{
console.log("community被挂载了")
})
onUnmounted(()=>{
console.log("community被卸载了")
})
</script>
路由传参
query参数传递
传递参数
<!-- 传死数据 -->
<!-- <RouterLink to="/community/post?postId=1&title=去干嘛&content=114514">{{ post.title }}</RouterLink> -->
<!-- 模板字符串嵌入js -->
<!-- <RouterLink :to="`/community/post?postId=${post.postId}&title=${post.title}&content=${post.content}`">{{ post.title }}</RouterLink> -->
<!-- 携带query参数 -->
<RouterLink :to="
{
path:'/community/post',
query:
{
postId:post.postId,
title:post.title,
content:post.content
}
}
">{{ post.title }}</RouterLink>
接收参数
<template>
<div class="post-content">
<ul>
<li>id: {{route.query.postId }}</li>
<li>标题: {{ route.query.title }}</li>
<li>内容: {{ route.query.content }}</li>
</ul>
</div>
</template>
<script lang="ts" setup>
//注意是useRoute 不是useRouter
import { useRoute } from 'vue-router';
const route=useRoute()
</script>
param参数传递
传递参数
首先需要在路由规则里面添加占位
//param参数占位版
children:[
{
path:"post/:postId/:title/:content?", //content加个?表示可选
name:"tiezi",
component:post
}
]
<!-- 传死数据 -->
<!-- <RouterLink to="/community/post/1/出去玩/谁一起">{{ post.title }}</RouterLink> -->
<!-- content是可选的 -->
<!-- <RouterLink to="/community/post/1/出去玩">{{ post.title }}</RouterLink> -->
<!-- 模板字符串嵌入js -->
<!-- <RouterLink :to="`/community/post/${post.postId}/${post.title}/${post.content}`">{{ post.title }}</RouterLink> -->
<!-- 携带param参数 用param传递参数时,必须使用 name -->
<RouterLink :to="
{
name:'tiezi', //养成习惯,用单引号
params:{
postId:post.postId,
title:post.title,
content:post.content
}
}
">{{ post.title }}</RouterLink>
接收参数
<template>
<div class="post-content">
<ul>
<li>id: {{route.params.postId }}</li>
<li>标题: {{ route.params.title }}</li>
<li>内容: {{ route.params.content }}</li>
</ul>
</div>
</template>
<script lang="ts" setup>
//注意是useRoute 不是useRouter
import { useRoute } from 'vue-router';
const route=useRoute()
</script>
路由的props配置
作用:用于简化接收数据的写法
1.param
2.query (当然param也可以这么写)
replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式。
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
:
push
是追加历史记录(默认值)。replace
是替换当前记录。
在默认的push模式下,可以点击按钮回滚
开启replace
回滚不了了
路由:编程式路由导航
我们前面写的代码,只能通过<RouterLink>标签实现路由跳转,假设我们要实现一个登录功能,会发现我们根本实现不了这个功能,因为<RouterLink>是个标签,不能放在函数里,而我们进行逻辑判断肯定要用到方法。
一个小案例
用button实现和下面<RouteLink>一样的效果
<button @click="showPost(post)">使用编程式导航来跳转路由,显示帖子内容</button>
<RouterLink :to="
{
path:'/community/post',
query:
{
postId:post.postId,
title:post.title,
content:post.content
}
}
">{{ post.title }}</RouterLink>
1.拿到路由器对象
import { useRouter } from 'vue-router';
const router=useRouter()
2.要跳到哪里,传什么参数(原来 :to 里面怎么写,这个里面就怎么写)
function showPost(post:any){
router.push( {
path:'/community/post',
query:
{
postId:post.postId,
title:post.title,
content:post.content
}
})
}
选择router.push 或router.replace 决定是否能回溯
测试:点击按钮一样能实现之前的效果
路由:重定向
作用:将特定的路径,重新定向到已有路由。
案例:
需求:刚进来啥也没有,我想要刚进来就显示社区的内容
在路由器中定义一条新路由, 这条路由会在访问
的时候重定向路由到
这样,我们一登录成功就能看到社区的帖子了
pinia
搭建pinia环境
1.引入依赖
npm i pinia
2.在main.ts中引入pinia
准备一个基本的效果
<template>
<h2>测试一下pinia</h2>
<br>
<span>当前求和为:{{ sum }}</span>
<br>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">加</button>
<button @click="minus">减</button>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
let sum=ref(0)
let n=ref(1)
function add(){
sum.value+=n.value
}
function minus(){
sum.value-=n.value
}
</script>
1.存储,读取数据
在src文件夹下定义一个store文件夹
count.ts
import { defineStore } from "pinia";
//定义并暴露一个store 第一个参数是id
export const useCountStore=defineStore('count',{
//数据
state() {
return{
sum:0,
n:1
}
},
//方法
actions:{
},
})
用pinia读取数据
2.修改数据
修改数据的三种方式如下
<template>
<h2>测试一下pinia</h2>
<br>
<span>当前求和为:{{ countStore.sum }}</span>
<br>
<select v-model.number="countStore.n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">加</button>
<button @click="minus">减</button>
<br>
<button @click="change">修改pinia里面的数据</button>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useCountStore } from '../store/count';
import { sum } from 'element-plus/es/components/table-v2/src/utils.mjs';
let countStore=useCountStore()
function add(){
countStore.sum+=countStore.n
}
function minus(){
countStore.sum-=countStore.n
}
function change(){
//1.直接修改
//countStore.sum=666
//2.批量修改
/* countStore.$patch(
{
sum:999,
n:88
} ) */
//3.借助action修改(action中可以编写一些业务逻辑)
countStore.Multiply(10)
}
</script>
count.ts
import { defineStore } from "pinia";
//定义并暴露一个store 第一个参数是id
export const useCountStore=defineStore('count',{
//数据
state() {
return{
sum:0,
n:1
}
},
//方法
actions:{
Multiply(value:number){
this.sum*=value
}
},
})
3.storeToRefs(toRefs)
- 借助
storeToRefs
将store
中的数据转为ref
对象,方便在模板中使用。 - 注意:
pinia
提供的storeToRefs
只会将数据做转换,而Vue
的toRefs
会转换store
中的所有进行转换(包括数据和方法,而方法是不需要我们进行转换的)
4.getters(computed)
定义
使用
这个东西相当于前面学过的computed
5.$subscribe的使用(watch)
通过 store 的 $subscribe()
方法侦听 state
及其变化
//mutate:本次修改的信息
//state:发生了什么变化
countStore.$subscribe((mutate,state)=>{
console.log("这个仓库里面的数据发生了改变",mutate,state)
})
点击”加“按钮
这个东西相当于前面学过的watch
tips
1.快捷键
alt+shift 多行写
alt+shift+a 注释
shift+( 用()包裹
2.别名插件
引入一个拓展插件,让Hello.vue被引入App.vue的时候可以起别名
在vite.config.js中引入
3.v-model(适用表单元素)和v-bind(适用表单属性)
数据绑定方向
- v-model:是双向数据绑定。它不仅能够将 Vue 实例中的数据渲染到页面上,还能够监听用户的输入,并将用户输入的数据更新回 Vue 实例中的数据。这种双向绑定特别适用于表单元素,如
<input>
、<select>
和<textarea>
。
- v-bind:是单向数据绑定。它主要用于将 Vue 实例中的数据绑定到 HTML 元素的属性上,但不会自动将用户输入的数据更新回 Vue 实例。虽然通过配合事件处理可以实现双向绑定,但
v-bind
本身只负责单向的数据流。
适用范围
- v-model:主要用于表单控件或自定义组件的双向数据绑定。它会自动根据控件类型(如文本、单选按钮、复选框、选择框等)选择正确的方法来更新元素。
- v-bind:几乎可以用于绑定任何 HTML 元素的属性,包括但不限于
class
、style
、href
、src
等。此外,它还可以用于绑定表达式和 HTML 内容(尽管绑定 HTML 内容时需要使用不同的语法)。