1.创建Vue3工程
npm create vue@latest
或者
npm init vue@latest
输入项目名和需要的工具后进入项目
如果项目报错 使用命令安装Node.js的项目依赖包
npm i
启动vue项目,查看项目是否创建完成
npm run dev
直接删掉src
然后创建src文件夹,在该文件夹中创建main.ts和App.vue文件
在src中有两个文件必不可少,分别是
main.ts
App.vue
App.vue基本内容:
<style>
</style>
<template>
</template>
<script lang="ts">
</script>
那么main.ts的基本内容:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
2.写一个简单的效果
使用插件
创建components文件夹,然后再创建Person.vue文件
components/Person.vue:
<style>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="showTel">显示联系方式</button>
</div>
</template>
<script lang="ts">
export default{
name :'Person',
data(){
return {
name:'张三',
age:18,
tel:'13888888888'
}
},
methods:{
showTel(){
alert(this.tel)
},
changeName(){
this.name = 'zhang-san'
},
changeAge(){
this.age += 1
}
}
}
</script>
App.vue:
<style scoped>
.app{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
<template>
<div class="app">
<h1>App你好</h1>
<PersonVue/>
</div>
</template>
<script lang="ts">
import PersonVue from "./components/Person.vue";
export default{
name:'App',
components:{PersonVue}
}
</script>
这说明了Vue3可以写Vue2的代码
上面是选项式API,我们将其改为组合式API,如下:
在setup函数中的this是undefined
如果遇到组件名和文件名不一致的情况,如:
为了使用一个script标签标识setup的同时命名文件的组件名,则需要安装插件
npm i vite-plugin-vue-setup-extend -D
然后再vite.config.ts中加入
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
然后在plugins中追加VueSetupExtend()
如下:
所修改后的组合式API:
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="showTel">显示联系方式</button>
</div>
</template>
<script lang="ts" setup name="Person234">
let name = '张三'
let age = 18
let tel = '13888888888'
function changeName(){
name = 'zhang-san'
}
function changeAge(){
age += 1
}
function showTel(){
alert(tel)
}
</script>
这还不是完整的修改,因为变量name和age,无法通过调用函数的方式显示改变后变量的值,同时还没使用到ref,下面的修改会解决这些问题
3.ref创建,基本类型的响应式数据
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">修改年龄</button>
<button @click="showTel">显示联系方式</button>
</div>
</template>
<script lang="ts" setup name="Person">
import { ref } from "vue"
let name = ref('张三')
let age = ref(18)
let tel = '13888888888'
function changeName(){
name.value = 'zhang-san'
}
function changeAge(){
age.value += 1
}
function showTel(){
alert(tel)
}
</script>
4.reative创建,对象类型的响应式数据
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<h2>一辆{{car.brand}}车,价值{{car.price}}万</h2>
<button @click="changePrice">修改汽车的价格</button>
</div>
<br>
<h2>游戏列表:</h2>
<ul>
<li v-for="game in games" :key="game.id">{{game.name}}</li>
</ul>
<button @click="changeFirstGame">修改第一个游戏名称</button>
</template>
<script lang="ts" setup name="Person">
import { reactive } from "vue"
let car = reactive({brand:'奔驰',price:100})
let games = reactive([
{id:'game01',name:'王者'},
{id:'game02',name:'原神'},
{id:'game03',name:'三国'}
])
function changePrice(){
car.price += 10
}
function changeFirstGame(){
games[0].name = "荣耀"
}
</script>
5.将reactive修改为ref的方式
修改的结果如下:
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<h2>一辆{{car.brand}}车,价值{{car.price}}万</h2>
<button @click="changePrice">修改汽车的价格</button>
</div>
<br>
<h2>游戏列表:</h2>
<ul>
<li v-for="game in games" :key="game.id">{{game.name}}</li>
</ul>
<button @click="changeFirstGame">修改第一个游戏名称</button>
</template>
<script lang="ts" setup name="Person">
import {ref } from "vue"
let car = ref({brand:'奔驰',price:100})
let games = ref([
{id:'game01',name:'王者'},
{id:'game02',name:'原神'},
{id:'game03',name:'三国'}
])
function changePrice(){
car.value.price += 10
}
function changeFirstGame(){
games.value[0].name = "荣耀"
}
</script>
6.volar工具
ref要定义的对象要加上.value改变值
可以使用volar工具,这样不用刻意去记哪个是ref,那个是reactive定义的对象
下载插件后,勾选扩展功能
7.处理数据返回
对于返回来reactive定义对象的数据,如果要更新原本的数据,可以使用Object.assign这么处理
8.toRefs和toRef
toRefs和toRef都是拿响应式数据解构出来,使其具有响应式能力,都是ref对象,所以需要.value
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}},n1:{{ n1 }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive, ref, toRef, toRefs } from "vue"
let person = reactive({
name:'张三',
age:18
})
let {name,age} = toRefs(person)
let n1 = toRef(person,'age')
function changeName(){
name.value += '~'
}
function changeAge(){
age.value += 1
}
</script>
9.computed计算属性
定义computed 想可读可写的方式如下所示
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
姓:<input type="text" v-model="firstName"/>
<br>
名:<input type="text" v-model="lastName"/>
<br>
姓名:<span>{{fullName}}</span>
<button @click="changeFullName">修改名字</button>
</div>
</template>
<script lang="ts" setup name="Person">
import { computed, ref } from "vue"
let firstName = ref('张')
let lastName = ref('三')
//这么定义的fullName是一个计算属性,只可读
/* let fullName = computed(()=>{
return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
}) */
//这么定义的fullName是一个计算属性,可读可写
let fullName = computed({
get(){
return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + lastName.value
},
set(val){
const [str1,str2] = val.split('-')
firstName.value = str1
lastName.value = str2
}
})
function changeFullName(){
fullName.value = 'li-si'
}
</script>
10.watch监视
10.1watch监视ref定义基本类型的数据
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<h2>sum:{{sum}}</h2>
<button @click="changeSum">sum++</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
let sum = ref(0)
function changeSum(){
sum.value += 1
}
const stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
//定义停止监听条件
if(newValue >= 10){
stopWatch()
}
})
</script>
10.2watch监视ref定义的对象类型数据
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<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:20}
}
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue)
},{deep:true})
</script>
10.3watch监视reactive定义对象类型数据,且默认开启了深度监听
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<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 {reactive,watch} from 'vue'
let person = reactive({
name:'张三',
age:18
})
function changeName(){
person.name = '李四'
}
function changeAge(){
person.age += 1
}
function changePerson(){
Object.assign(person,{name:'小刘',age:20})
}
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue)
})
</script>
10.4watch监视ref或reactive定义对象类型数据中某个属性
如果监听响应式对象中的某个属性,若该属性是基本类型属性,要写成函数式
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<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 {reactive,watch} from 'vue'
let person = reactive({
name:'张三',
age:18
})
function changeName(){
person.name += '~'
}
function changeAge(){
person.age += 1
}
function changePerson(){
Object.assign(person,{name:'小刘',age:20})
}
watch(()=>person.name,(newValue,oldValue)=>{
console.log(newValue,oldValue)
})
</script>
10.5watch监听多个属性
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<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 {reactive,watch} from 'vue'
let person = reactive({
name:'张三',
age:18
})
function changeName(){
person.name += '~'
}
function changeAge(){
person.age += 1
}
function changePerson(){
Object.assign(person,{name:'小刘',age:20})
}
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log(newValue,oldValue)
})
</script>
10.6watchEffect
watchEffect不用明确指出监听的数据(函数中用到哪些属性,那就监听哪些属性)
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<h2>水温:{{temp}}</h2>
<h2>水位:{{height}}</h2>
<button @click="changeTemp">提高水温+10</button>
<button @click="changeHeight">提高水位+10</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive,ref,watch, watchEffect} from 'vue'
let temp = ref(10)
let height = ref(0)
function changeTemp(){
temp.value += 10
}
function changeHeight(){
height.value += 10
}
//监听 -- watch
// watch([temp,height],(value)=>{
// let [newTemp,newHeight] = value
// if(newTemp>=30 || newHeight>=50){
// console.log('服务器发起请求')
// }
// })
//监听 -- watchEffect
watchEffect(()=>{
if(temp.value >= 30 || height.value >=50){
console.log('服务器发起请求')
}
})
</script>
11.标签的ref属性
创建一个title2,用于存储ref标记的内容
Person.vue:
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<h2 ref="title2">北京</h2>
<button @click="showLog">点击我输出h2元素</button>
</div>
</template>
<script lang="ts" setup name="Person">
import { ref } from 'vue';
let title2 = ref()
function showLog(){
console.log(title2.value)
}
</script>
App.vue:
<style scoped>
.app{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
<template>
<h2 ref="title2">你好</h2>
<button @click="showLog">点击我输出h2</button>
<PersonVue/>
</template>
<script lang="ts" setup name="App">
import PersonVue from "./components/Person.vue";
import { ref }from 'vue'
let title2 = ref()
function showLog(){
console.log(title2.value)
}
</script>
如果父组件想获取子组件中的元素,则可以使用defineExpose
Person.vue:
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<h2 ref="title2">北京</h2>
<button @click="showLog">点击我输出h2元素</button>
</div>
</template>
<script lang="ts" setup name="Person">
import { ref,defineExpose } from 'vue';
let title2 = ref()
let a = ref(0)
let b = ref(1)
function showLog(){
console.log(title2.value)
}
defineExpose({a,b})
</script>
App.vue:
<style scoped>
.app{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
<template>
<h2 ref="title2">你好</h2>
<button @click="showLog">点击我输出</button>
<PersonVue ref="ren" />
</template>
<script lang="ts" setup name="App">
import PersonVue from "./components/Person.vue";
import { ref }from 'vue'
let title2 = ref()
let ren = ref()
function showLog(){
console.log(ren.value)
}
</script>
12.TS中的接口、泛型、自定义类型
如果引入的是数据类型,而不是值的话,则需要使用type来标识
创建types/index.ts
//定义一个接口,用于限制person对象的具体属性
export interface PersonInter {
id:string,
name:string,
age:number
}
//一个自定义类型
// export type Persons = Array<PersonInter>
export type Persons = PersonInter[]
Person.vue:
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
</div>
</template>
<script lang="ts" setup name="Person">
import {type PersonInter,type Persons } from '@/types'
// let person:PersonInter = {id:'dasads01',name:'zhangsan',age:20}
let person:Persons = [
{id:'dasads01',name:'zhangsan',age:20},
{id:'dasads02',name:'lisi',age:22},
{id:'dasads03',name:'wangwu',age:30}
]
</script>
13.props的使用
如果子组件想获取父组件中的元素,则可以使用defineProps
Person.vue:
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
</style>
<template>
<div class="person">
<ul>
<li v-for="personObj in list" :key="personObj.id">
{{ personObj.name }} -- {{ personObj.age }}
</li>
</ul>
</div>
</template>
<script lang="ts" setup name="Person">
import { Persons } from '@/types'
//只接收list
// defineProps(['list'])
//接收list + 限制类型
// defineProps<{list:Persons}>()
//接收list + 限制类型 + 限制必要性 + 指定默认值
//在list后打上?,表示可以不传入数据
withDefaults(defineProps<{list?:Persons}>(),{
list:()=>[{id:'asdasas01',name:'康之福',age:23}]
})
</script>
App.vue:
<style scoped>
.app{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
<template>
<PersonVue a="哈哈" :list="personList"/>
</template>
<script lang="ts" setup name="App">
import PersonVue from "./components/Person.vue";
import {reactive} from 'vue';
import { type Persons } from "./types";
let personList = reactive<Persons>([
{id:'dasads01',name:'zhangsan',age:20},
{id:'dasads02',name:'lisi',age:22},
{id:'dasads03',name:'wangwu',age:30}
])
</script>
14.生命周期
14.1 Vue2的生命周期
分为四个阶段:创建、挂载、更新、销毁
创建前:beforeCreate()
创建完毕:created()
挂载前:beforeMount()
挂载完毕:mounted()
更新前:beforeUpdate()
更新完毕:updated()
销毁前:beforeDestroy()
销毁完毕:destroyed()
14.2 Vue3的生命周期
创建阶段:setup
挂载阶段:onBeforeMount、onMounted
更新阶段:onBeforeUpdate、onUpdated
卸载阶段:onBeforeUnmount、onUnmounted
常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)
15.自定义Hooks
安装axios
npm i axios
以两个函数为示例,这是没有使用hooks的方式
Person.vue:
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
img{
height: 100px;
margin: 10px;
}
</style>
<template>
<div class="person">
<h2>当前求和:{{ sum }}</h2>
<button @click="add">点我+1</button>
<br>
<button @click="getDog">换一只狗</button>
<img v-for="(dog,index) in dogList" :src="dog" :key="index">
</div>
</template>
<script lang="ts" setup name="Person">
import { reactive, ref, } from 'vue';
import axios from 'axios';
let sum = ref(0)
let dogList = reactive([
'https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg'
])
async function getDog(){
let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
dogList.push(result.data.message)
}
function add(){
sum.value += 1
}
</script>
使用hooks后的方式,先创建hooks文件夹,然后创建useDog.ts和useSum.ts文件,分别存放,Sum和Dog相关的代码块,使用方式:
useSum.ts:
import {computed, onMounted, ref } from 'vue';
export default function(){
let sum = ref(0)
let bigsum = computed(()=>{
return sum.value * 10
})
function add(){
sum.value += 1
}
onMounted(()=>{
sum.value += 1
})
return {sum,add,bigsum}
}
useDog.ts:
import { reactive} from 'vue';
import axios from 'axios';
export default function(){
let dogList = reactive([
'https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg'
])
async function getDog(){
let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
dogList.push(result.data.message)
}
return {dogList,getDog}
}
Person.vue:
<style scoped>
.person{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button{
margin: 0 10px;
}
img{
height: 100px;
margin: 10px;
}
</style>
<template>
<div class="person">
<h2>当前求和:{{ sum }},放大十倍后:{{bigsum}}</h2>
<button @click="add">点我+1</button>
<br>
<button @click="getDog">换一只狗</button>
<img v-for="(dog,index) in dogList" :src="dog" :key="index">
</div>
</template>
<script lang="ts" setup name="Person">
import useSum from '@/hooks/useSum'
import useDog from '@/hooks/useDog'
const {sum,add,bigsum} = useSum()
const {dogList,getDog} = useDog()
</script>
16.路由
16.1路由的基本切换效果
安装路由器
npm i vue-router
在components中创建Home.vue、News.vue、About.vue文件
创建文件夹router,然后在router中创建index.ts文件
path应该以斜杠 / 开头,以确保它是一个完整的路径
router/index.ts:
//引入createRouter
import { createRouter, createWebHistory } from "vue-router";
//引入组件
import Home from "@/components/Home.vue";
import About from "@/components/About.vue";
import News from "@/components/News.vue";
//创建路由器
const router = createRouter({
history:createWebHistory(), //路由器编译规则
routes:[{
path:'/home',
component:Home
},{
path:'/about',
component:About
},{
path:'/news',
component:News
}]
})
export default router
main.ts:
import { createApp } from "vue";
import App from './App.vue'
import router from "./router";
//创建一个应用
const app = createApp(App)
//使用路由器
app.use(router)
//挂载整个应用到app容器中
app.mount('#app')
App.vue:
<style scoped>
.app{
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
<template>
<div class="navigate">
<h1>navigate</h1>
<RouterLink to="/home">首页</RouterLink>
<RouterLink to="/news">新闻</RouterLink>
<RouterLink to="/about">关于</RouterLink>
</div>
<br>
内容:
<div>
<RouterView></RouterView>
</div>
</template>
<script lang="ts" setup name="App">
import {RouterView,RouterLink} from 'vue-router'
</script>
16.2路由两个注意点
注意:
(1)一般组件存放在components文件夹中,路由组件存放在pages或views文件夹中
(2)通过点击导航,视觉效果上“消失”了的路由组件,默认是被销毁的,需要的时候再去挂载
16.3 to的两种写法
16.4路由命名
16.5嵌套路由
注意:children下的path的开头是不需要加上斜杠 / 的
在pages中创建文件Detail.vue
pages/Detail.vue:
<template>
<div class="detail">
<h2>Detail</h2>
</div>
</template>
<script lang="ts" setup name="Detail">
</script>
<style scoped>
</style>
pages/News.vue:
<template>
<div class="news">
<h2>News</h2>
<RouterView></RouterView>
</div>
</template>
<script lang="ts" setup name="News">
</script>
<style scoped>
</style>
router/index.ts:
//引入createRouter
import { createRouter, createWebHistory } from "vue-router";
//引入组件
import Home from "@/pages/Home.vue";
import About from "@/pages/About.vue";
import News from "@/pages/News.vue";
import Detail from "@/pages/Detail.vue";
//创建路由器
const router = createRouter({
history:createWebHistory(), //路由器编译规则
routes:[{
name:'zhuye',
path:'/home',
component:Home
},{
name:'guanyu',
path:'/about',
component:About
},{
name:'xinwen',
path:'/news',
component:News,
children:[{
path:'detail',
component:Detail
}]
}]
})
export default router
main.ts:
import { createApp } from "vue";
import App from './App.vue'
import router from "./router";
//创建一个应用
const app = createApp(App)
//使用路由器
app.use(router)
//挂载整个应用到app容器中
app.mount('#app')
16.6路由query参数(传参)
App.vue:
<template>
<div class="app">
<h1>navigate</h1>
<ul>
<li v-for="news in newsList" :key="news.id">
<!-- 第一种方法 -->
<!-- <RouterLink :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`" >{{news.title}}</RouterLink> -->
<!-- 第二种写法 -->
<RouterLink :to="{
path:'/news/detail',
query:{
id:news.id,
title:news.title,
content:news.content
}
}">{{news.title}}</RouterLink>
</li>
</ul>
<RouterLink to="/home">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
<br>
内容:
<div>
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup name="App">
import { reactive } from 'vue';
import { RouterView, RouterLink } from 'vue-router';
const newsList = reactive([
{ id: 'asdad01', title: '标题1', content: '内容1' },
{ id: 'asdad02', title: '标题2', content: '内容2' },
{ id: 'asdad03', title: '标题3', content: '内容3' }
]);
</script>
<style scoped>
.app {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
pages/Detail.vue:
<template>
<div class="detail">
<h2>Detail</h2>
<ul>
<li>编号:{{route.query.id}}</li>
<li>标题:{{route.query.title}}</li>
<li>内容:{{route.query.content}}</li>
</ul>
</div>
</template>
<script lang="ts" setup name="Detail">
import { useRoute } from 'vue-router';
let route = useRoute()
</script>
<style scoped>
</style>
可以将pages/Detail.vue这么改:
<template>
<div class="detail">
<h2>Detail</h2>
<ul>
<li>编号:{{query.id}}</li>
<li>标题:{{query.title}}</li>
<li>内容:{{query.content}}</li>
</ul>
</div>
</template>
<script lang="ts" setup name="Detail">
import { toRefs } from 'vue';
import { useRoute } from 'vue-router';
let route = useRoute()
let {query} = toRefs(route)
</script>
<style scoped>
</style>
16.7路由params参数(传参)
注意:
(1)传递params参数时,使用to的对象写法,必须使用name配置项,不能用path
(2)传递params参数时,需要提前在规则中占位
App.vue:
<template>
<div class="app">
<h1>navigate</h1>
<ul>
<li v-for="news in newsList" :key="news.id">
<!-- 第一种方法 -->
<!-- <RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`" >{{news.title}}</RouterLink> -->
<!-- 第二种写法 -->
<RouterLink :to="{
name:'xiang',
params:{
id:news.id,
title:news.title,
content:news.content
}
}">{{news.title}}</RouterLink>
</li>
</ul>
<RouterLink to="/home">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
<br>
内容:
<div>
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup name="App">
import { reactive } from 'vue';
import { RouterView, RouterLink } from 'vue-router';
const newsList = reactive([
{ id: 'asdad01', title: '标题1', content: '内容1' },
{ id: 'asdad02', title: '标题2', content: '内容2' },
{ id: 'asdad03', title: '标题3', content: '内容3' }
]);
</script>
<style scoped>
.app {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
router/index.ts:
注意:params传参需要对路径名前加上:,表示所传递的参数,不然会当作子路径
//引入createRouter
import { createRouter, createWebHistory } from "vue-router";
//引入组件
import Home from "@/pages/Home.vue";
import About from "@/pages/About.vue";
import News from "@/pages/News.vue";
import Detail from "@/pages/Detail.vue";
//创建路由器
const router = createRouter({
history:createWebHistory(), //路由器编译规则
routes:[{
name:'zhuye',
path:'/home',
component:Home
},{
name:'guanyu',
path:'/about',
component:About
},{
name:'xinwen',
path:'/news',
component:News,
children:[{
name:'xiang',
path:'detail/:id/:title/:content',
component:Detail
}]
}]
})
export default router
pages/Detail.vue:
<template>
<div class="detail">
<h2>Detail</h2>
<ul>
<li>编号:{{route.params.id}}</li>
<li>标题:{{route.params.title}}</li>
<li>内容:{{route.params.content}}</li>
</ul>
</div>
</template>
<script lang="ts" setup name="Detail">
import { toRefs } from 'vue';
import { useRoute } from 'vue-router';
let route = useRoute()
</script>
<style scoped>
</style>
16.8路由props配置
router/index.ts:
一般使用第一种方法和第二种方法,第二种方法使用params方法传递参数所以return route.params,如果是使用query方法传递参数,则return route.query
//引入createRouter
import { createRouter, createWebHistory } from "vue-router";
//引入组件
import Home from "@/pages/Home.vue";
import About from "@/pages/About.vue";
import News from "@/pages/News.vue";
import Detail from "@/pages/Detail.vue";
//创建路由器
const router = createRouter({
history:createWebHistory(), //路由器编译规则
routes:[{
name:'zhuye',
path:'/home',
component:Home
},{
name:'guanyu',
path:'/about',
component:About
},{
name:'xinwen',
path:'/news',
component:News,
children:[{
name:'xiang',
path:'detail/:id/:title/:content',
component:Detail,
// 第一种写法:将路由收到的所有params参数作为props传给路由组件
// props:true
//第二种写法:函数写法,可以自己决定将什么作为props给路由组件
props(route){
return route.params
}
//第三种写法:对象写法,可以自己决定将什么作为props给路由组件
/* props:{
id:10,
title:'今天去哪里?',
content:'当然是北京'
} */
}]
}]
})
export default router
pages/Detail.vue:
<template>
<div class="detail">
<h2>Detail</h2>
<ul>
<li>编号:{{id}}</li>
<li>标题:{{title}}</li>
<li>内容:{{content}}</li>
</ul>
</div>
</template>
<script lang="ts" setup name="Detail">
defineProps(['id','title','content'])
</script>
<style scoped>
</style>
16.9路由replace属性
给RouterLink加上replace后就无法返回上一页的内容
16.9路由导航,路由跳转
有点类似<RouterLink/>
不带参数跳转:
pages/Home.vue:
<template>
<div class="home">
<h2>Home</h2>
</div>
</template>
<script lang="ts" setup name="Home">
import { onMounted } from "vue";
import {useRouter} from 'vue-router'
const router = useRouter()
onMounted(()=>{
setTimeout(()=>{
//跳转路由
router.push('/news')
},3000)
})
</script>
<style scoped>
</style>
带参数跳转:
App.vue:
<template>
<div class="app">
<h1>navigate</h1>
<ul>
<li v-for="news in newsList" :key="news.id">
<button @click="showNewsDetail(news)">查看新闻</button>
<!-- 第一种方法 -->
<!-- <RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`" >{{news.title}}</RouterLink> -->
<!-- 第二种写法 -->
<RouterLink :to="{
name:'xiang',
params:{
id:news.id,
title:news.title,
content:news.content
}
}">{{news.title}}</RouterLink>
</li>
</ul>
<RouterLink to="/home">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
<br>
内容:
<div>
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup name="App">
import { reactive } from 'vue';
import { RouterView, RouterLink, useRouter } from 'vue-router';
const newsList = reactive([
{ id: 'asdad01', title: '标题1', content: '内容1' },
{ id: 'asdad02', title: '标题2', content: '内容2' },
{ id: 'asdad03', title: '标题3', content: '内容3' }
]);
const router = useRouter()
//用于限制类型
interface NewsInter{
id:string,
title:string,
content:string
}
function showNewsDetail(news:NewsInter){
router.push({
name:'xiang',
params:{
id:news.id,
title:news.title,
content:news.content
}}
)
}
</script>
<style scoped>
.app {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
如果将router.push改为router.replace,就表示跳转到指定的路由后就无法返回了,将上面的函数的代码改为replace如下所示:
function showNewsDetail(news:NewsInter){
router.replace({
name:'xiang',
params:{
id:news.id,
title:news.title,
content:news.content
}}
)
}
16.10路由重定向
重定向(Redirect)是指通过各种方法将各种网络请求重新定向到其它位置的过程
17.Pinia
考虑多个组件共享数据
安装axios
npm i axios
安装nanoid(效果类似uuid)
npm i nanoid
17.1Pinia配置
安装pinia
npm i pinia
配置
mian.ts:
import { createApp } from "vue";
import App from './App.vue'
import { createPinia } from "pinia";
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
下面是一个效果,
17.2未使用Pinia前的方式
components/Count.vue:
<template>
<div class="count">
<h1>当前求和:{{sum}}</h1>
<select v-model.number="step">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">加</button>
<button @click="minus">减</button>
</div>
</template>
<script lang="ts" setup name="Count">
import { ref } from 'vue';
let sum = ref(0)
let step = ref(1)
function add(){
sum.value += step.value
}
function minus(){
sum.value -= step.value
}
</script>
<style scoped>
.count{
background-color: skyblue;
padding: 10px;
border-radius:10px;
box-shadow:0 0 10px;
}
select,button{
margin: 0 5px;
height: 25px;
}
</style>
components/LoveTalk.vue:
<template>
<div class="talk">
<button @click="getLoveTalk">获取一句话</button>
<ul>
<li v-for="talk in talkList" :key="talk.id">{{talk.title}}</li>
</ul>
</div>
</template>
<script lang="ts" setup name="LoveTalk">
import axios from 'axios';
import { reactive } from 'vue';
import {nanoid} from 'nanoid'
let talkList = reactive([
{id:'sadaohd01',title:'早上好!'},
{id:'sadaohd02',title:'中午好!'},
{id:'sadaohd03',title:'晚上好!'}
])
async function getLoveTalk(){
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
let obj = {id:nanoid(),title}
talkList.unshift(obj)
}
</script>
<style scoped>
.talk{
background-color: skyblue;
padding: 10px;
border-radius:10px;
box-shadow:0 0 10px;
}
</style>
App.vue:
<template>
<div>
<h1>App组件</h1>
<Count/>
<br>
<LoveTalk/>
</div>
</template>
<script lang="ts" setup name="App">
import Count from './components/Count.vue';
import LoveTalk from './components/LoveTalk.vue';
</script>
<style scoped>
</style>
17.3使用Pinia后的方式
创建文件夹store,然后在该文件夹中创建count.ts
store/count.ts:
import {defineStore} from 'pinia'
export const useCountStore = defineStore('count',{
actions:{
increment(value: number){
console.log('increment被调用了',value)
if(this.sum<10){
this.sum += value
}
}
},
//真正存储数据的地方
state(){
return{
sum:6,
school:'guigu',
address:'beijing'
}
}
})
components/Count.vue:
<template>
<div class="count">
<h1>当前求和:{{countStore.sum}}</h1>
<h1>学校:{{countStore.school}};地址:{{countStore.address}}</h1>
<select v-model.number="step">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">加</button>
<button @click="minus">减</button>
</div>
</template>
<script lang="ts" setup name="Count">
import { ref } from 'vue';
import { useCountStore } from '@/store/count';
const countStore = useCountStore()
//获取state数据的两种方式:
// console.log(countStore.sum)
// console.log(countStore.$state.sum)
let step = ref(1)
function add(){
//第一种方式修改
/* countStore.sum += 1
countStore.school = '硅谷'
countStore.address = '北京' */
//第二种方式批量修改
/* countStore.$patch({
sum:888,
school:'硅谷',
address:'北京'
}) */
//第三种方式,调用store里的函数
countStore.increment(step.value)
}
function minus(){
}
</script>
<style scoped>
.count{
background-color: skyblue;
padding: 10px;
border-radius:10px;
box-shadow:0 0 10px;
}
select,button{
margin: 0 5px;
height: 25px;
}
</style>
但是上面还没实现方法和调用storeToRefs(),实现后的完整版如下:
components/Count.vue:
<template>
<div class="count">
<h1>当前求和:{{sum}}</h1>
<h1>学校:{{school}};地址:{{address}}</h1>
<select v-model.number="step">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">加</button>
<button @click="minus">减</button>
</div>
</template>
<script lang="ts" setup name="Count">
import { ref } from 'vue';
import { useCountStore } from '@/store/count';
import { storeToRefs } from 'pinia';
const countStore = useCountStore()
//storeToRefs只会关注store中数据,不会对方法进行ref包裹
const {sum,school,address} = storeToRefs(countStore)
//获取state数据的两种方式:
// console.log(countStore.sum)
// console.log(countStore.$state.sum)
let step = ref(1)
function add(){
//第一种方式修改
/* countStore.sum += 1
countStore.school = '硅谷'
countStore.address = '北京' */
//第二种方式批量修改
/* countStore.$patch({
sum:888,
school:'硅谷',
address:'北京'
}) */
//第三种方式,调用store里的函数
countStore.increment(step.value)
}
function minus(){
countStore.sum -= step.value
}
</script>
<style scoped>
.count{
background-color: skyblue;
padding: 10px;
border-radius:10px;
box-shadow:0 0 10px;
}
select,button{
margin: 0 5px;
height: 25px;
}
</style>
store/count.ts:
import {defineStore} from 'pinia'
export const useCountStore = defineStore('count',{
actions:{
increment(value: number){
console.log('increment被调用了',value)
if(this.sum<10){
this.sum += value
}
}
},
//真正存储数据的地方
state(){
return{
sum:6,
school:'guigu',
address:'beijing'
}
}
})
components/LoveTalk:
<template>
<div class="talk">
<button @click="getLoveTalk">获取一句话</button>
<ul>
<li v-for="talk in talkStore.talkList" :key="talk.id">{{talk.title}}</li>
</ul>
</div>
</template>
<script lang="ts" setup name="LoveTalk">
import { useTalkStore } from '@/store/loveTalk';
import { storeToRefs } from 'pinia';
const talkStore = useTalkStore()
const {talkList} = storeToRefs(talkStore)
async function getLoveTalk(){
talkStore.getATalk()
}
</script>
<style scoped>
.talk{
background-color: skyblue;
padding: 10px;
border-radius:10px;
box-shadow:0 0 10px;
}
</style>
store/loveTalk.ts:
import {defineStore} from 'pinia'
import axios from 'axios';
import { reactive } from 'vue';
import {nanoid} from 'nanoid'
export const useTalkStore = defineStore('talk',{
actions:{
async getATalk(){
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
let obj = {id:nanoid(),title}
this.talkList.unshift(obj)
}
},
//真正存储数据的地方
state(){
return{
talkList:[
{id:'sadaohd01',title:'早上好!'},
{id:'sadaohd02',title:'中午好!'},
{id:'sadaohd03',title:'晚上好!'}
]
}
}
})
17.4 getters
getters是 Vuex store 中的计算属性。它们类似于 Vue 组件中的计算属性,但是用于从 store 中的状态派生值
store/count.ts:
import {defineStore} from 'pinia'
export const useCountStore = defineStore('count',{
actions:{
increment(value: number){
console.log('increment被调用了',value)
if(this.sum<10){
this.sum += value
}
}
},
//真正存储数据的地方
state(){
return{
sum:6,
school:'guigu',
address:'beijing'
}
},
getters:{
bigSum:state => state.sum * 10,
upperSchool():string{
return this.school.toUpperCase()
}
}
})
components/Count.vue:
<template>
<div class="count">
<h1>当前求和:{{sum}},放大:{{bigSum}}</h1>
<h1>学校:{{school}};地址:{{address}},大写:{{upperSchool}}</h1>
<select v-model.number="step">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">加</button>
<button @click="minus">减</button>
</div>
</template>
<script lang="ts" setup name="Count">
import { ref } from 'vue';
import { useCountStore } from '@/store/count';
import { storeToRefs } from 'pinia';
const countStore = useCountStore()
//storeToRefs只会关注store中数据,不会对方法进行ref包裹
const {sum,school,address,bigSum,upperSchool} = storeToRefs(countStore)
//获取state数据的两种方式:
// console.log(countStore.sum)
// console.log(countStore.$state.sum)
let step = ref(1)
function add(){
//第一种方式修改
/* countStore.sum += 1
countStore.school = '硅谷'
countStore.address = '北京' */
//第二种方式批量修改
/* countStore.$patch({
sum:888,
school:'硅谷',
address:'北京'
}) */
//第三种方式,调用store里的函数
countStore.increment(step.value)
}
function minus(){
countStore.sum -= step.value
}
</script>
<style scoped>
.count{
background-color: skyblue;
padding: 10px;
border-radius:10px;
box-shadow:0 0 10px;
}
select,button{
margin: 0 5px;
height: 25px;
}
</style>
17.5 $subscribe的使用
订阅,监视vuex文件里的修改
components/LoveTalk.vue:
<template>
<div class="talk">
<button @click="getLoveTalk">获取一句话</button>
<ul>
<li v-for="talk in talkStore.talkList" :key="talk.id">{{talk.title}}</li>
</ul>
</div>
</template>
<script lang="ts" setup name="LoveTalk">
import { useTalkStore } from '@/store/loveTalk';
import { storeToRefs } from 'pinia';
const talkStore = useTalkStore()
const {talkList} = storeToRefs(talkStore)
//$subscribe订阅
talkStore.$subscribe((mutate,state)=>{
console.log('talkStore里面保存的数据发生了变化',mutate,state)
localStorage.setItem('talkList',JSON.stringify(state.talkList))
})
async function getLoveTalk(){
talkStore.getATalk()
}
</script>
<style scoped>
.talk{
background-color: skyblue;
padding: 10px;
border-radius:10px;
box-shadow:0 0 10px;
}
</style>
store/loveTalk.ts:
import {defineStore} from 'pinia'
import axios from 'axios';
import { reactive } from 'vue';
import {nanoid} from 'nanoid'
export const useTalkStore = defineStore('talk',{
actions:{
async getATalk(){
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
let obj = {id:nanoid(),title}
this.talkList.unshift(obj)
}
},
//真正存储数据的地方
state(){
return{
talkList:JSON.parse(localStorage.getItem('talkList') as string) || []
}
}
})
17.6store里的组合式写法
store/loveTalk.ts:
import {defineStore} from 'pinia'
import axios from 'axios';
import { reactive } from 'vue';
import {nanoid} from 'nanoid'
/* export const useTalkStore = defineStore('talk',{
actions:{
async getATalk(){
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
let obj = {id:nanoid(),title}
this.talkList.unshift(obj)
}
},
//真正存储数据的地方
state(){
return{
talkList:JSON.parse(localStorage.getItem('talkList') as string) || []
}
}
}) */
//组合式
export const useTalkStore = defineStore('talk',()=>{
//talkList就是state
const talkList = reactive(
JSON.parse(localStorage.getItem('talkList') as string) || []
)
//getATalk函数相当于action
async function getATalk(){
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
let obj = {id:nanoid(),title}
talkList.unshift(obj)
}
return {talkList,getATalk}
})
18.组件通信
18.1props
可以进行父传子和子传父;
父传子,属性值是非函数
子传父,属性值是函数
18.2自定义事件
18.3mitt
安装mitt
npm i mitt
然后创建utils文件夹,里存放emitter.ts文件
18.4 v-model
18.5 $attrs
实现 祖传孙 和 孙传祖
18.6 $refs和$parent
$refs:父访问子的属性、方法、计算属性
$parent:子访问父的属性、方法、计算属性
18.7 provide 和 inject
在之前祖传孙需要使用$attrs,且打扰到了子组件
那么使用provide直接就可以传后代,通过inject获取