路由
- 路由是一组key—value的对应关系
- 多个路由(route)需要经过路由器(router)的管理
基本操作
首先需要安装路由
npm i vue-router
安装后建立router文件夹,index.ts文件内写配置
//创建路由并且暴露出去
//第一步:引入createRouter
import { createRouter,createWebHistory } from "vue-router";
//导入要呈现的组件
import Home from '@/components/home.vue'
import Person from "@/components/person.vue";
//第二步:创建路由器
const router = createRouter({
history:createWebHistory(), //路由器的工作模式
routes:[ //一个个路由的路由规则
{
path:'/home',
component: Home
},
{
path:'/person',
component: Person
}
]
})
//暴露出去
export default router
建立好路由后再main.ts中挂载路由
//导入一个盆
import { createApp } from "vue"
import router from '@/router'
//导入根
import App from "./App.vue"
//引用
const app =createApp(App)
//挂载路由
app.use(router)
app.mount('#app')
在app.vue中使用 router-view来引用页面,使用router-link实现页面跳转
<router-link to="/home">home</router-link>
<br>
<router-link to="/person">person</router-link>
<br>
<router-view></router-view>
两个注意点
在vue中
路由组件通常放在pages和views文件夹中,一般组件通常存放在components文件夹中
(通过标签引入的则为一般组件 如:,使用route的为路由组件)
其中通过点击导航,视觉上消失的组件,默认是卸载,需要的时候再去挂载
工作模式
history模式:不带#,url更加美观,项目上线后需要服务端配合处理路径问题,不然刷新会出现404
createWebHistory
hash模式:兼容性更好,不需要服务器配合处理路径问题,但是不美观,seo优化方面较差
createWebHashHistory
前台项目推荐history,后台项目推荐hash
to的有两种写法
<router-link :to="{path:'/home'}">home</router-link>
<br>
<router-link to="/person">person</router-link>
命名路由
使用name给组件进命名,跳转时可以使用name来跳转
name:'zhuye',
path:'/home',
component: Home
//------------------
<router-link :to="{name:'zhuye'}">home</router-link>
嵌套路由
路由配置中引用children
name:'zhuye',
path:'/home',
component: Home,
children:[{
path:'detail',
component: Detail
}]
//------------
<router-view></router-view>//展示
query参数
在路径中传输,如同get请求传参
接收使用useRoute,其数据会在其query属性中
import { useRoute } from 'vue-router';
let route = useRoute()
params传参
使用path:需要提前在地址上占好位置
name:'de',
path:'detail/:id/:name',
component: Home
当访问地址(to)为:detail/哈哈/呵呵
会自动进行匹配
除此之外(to)还有另一种写法,使用name
<router-link :to="{name:'de',params:{
id:'1',
name:'uu'
}}" ></router-link>
总计:如果使用to的对象写法,则需要配置name,不可用path
如果使用params传递参数时,需要提前占位
路由的props配置
一共由三种写法
children:[
name:xinwen,
path:'xiangqing',
componet:Detail,
//第一种写法:将路由接受到的所有params参数作为props传给路由组件
//props:true
//第二种写法:函数写法,自己决定将什么作为props传给路由组件
/*props(route){
returen route.query
}*/
//第三种写法:对象写法,自己决定将什么作为props传给路由组件
/*props:{
a:'shu1',
b:'shu2',
c:'shu3'
}*/
]
接受是使用defineProps方法
defineProps(['a','b','c'])
replace属性
作用:控制路由跳转时操作浏览器历史记录的模式
浏览器记录的写入模式由两种:分别是push与replace
push:追加历史记录,默认时这种,即进入新页面后可以回退到原来的页面
replace:替换当前记录,即不可回退
在导航标签上使用
<RouterLine replace/>
编程式导航
使用useRouter
const router = useRouter()
router.push({
name:'xinagq',
query:{
id:news.id,
title:nes.title,
content:news.content
}
})
push里放对象,其内容位标签种to中的内容
重定向
在index.ts种新增一个路由,使用redirect实现
{
path:'/',
redirect:'/home'
}
pinia
vue.js的状态管理库,是vuex的轻量级替代
创建搭建
首先项目安装pinia
npm i pinia
其次在mian.ts文件中创建跟使用
//pinia 引用
import { createPinia } from 'pinia'
//创建pinia
const pinia = createPinia()
//使用pinia
app.use(pinia)
存储+读取数据
存储数据:
在项目中建立store文件夹,该文件夹下写相关文件,如 count.ts
import { defineStore } from 'pinia'
export const useCountStore = defineStore('count',{
//真正存储数据的地方
state () {
return {
sum:8
}
}
})
使用则直接导入使用,如上方定义了useCountStore
import { useCountStore } from '@/store/count';
const countStore = useCountStore()
console.log('cs',countStore) //打印出来是一个reactive的对象
console.log('sum',countStore.sum) //sum是一个ref的用响应式数据
上面之所以不使用value是存在reactive包裹的ref数据,可以不需要用value就能直接打印出来
修改数据(三种方式)
第一种是直接修改
countStore.sum += 1;
第二种是使用patch进行碎片化的修改
countStore.$patch({
sum:11
})
第三种使用actions,其action的配置在对应的ts文件中
import { defineStore } from 'pinia'
export const useCountStore = defineStore('count',{
actions: {
add () {
this.sum ++;
}
},
//真正存储数据的地方
state () {
return {
sum:8
}
}
})
引用这是直接调用方法名即可,如上面所示则countStore.add()为引用(在vue文件中)
其次在ts文件中有this
storeToRefs
使用storeToRefs方法可以定向的接收对应属性,且保证响应式
const countStore = useCountStore()
const {sum} = storeToRefs(countStore)
getters的使用方法
在ts文件中编写,可以选择写构造函数,或普通函数,其中趋同函数可以使用this
getters:{
bigsum:state=> state.sum * 10,
bigSum1 (state) {
return state.sum * 20
}
}
$subscribe (订阅)
可以监控数据的变化,具体使用如下
const countStore = useCountStore()
countStore.$subscribe((mutate,state)=>{
console.log('mutate是改前的数据',mutate)
console.log('state是改后的数据',state)
})
组合式写法
在ts文件中可以选择选项式写法与组合式写法,上面的是选项式,下面是组合式
export const useCountStore = defineStore('count',()=>{
const sum = ref(8);
function add () {
sum.value++;
}
return{sum,add};
})
组合式写法使用的箭头函数,无法使用this
组件通讯
pops
分别有子传父,父传子两种情况
父传子:
首先是父类需要在标签上进行一个定义(car)
<child :car="car"/>
其中就是将car传给子类种的car
子类使用defineProps接受数据与方法
defineProps(['car'])
子 传父:
通过方法将数据传输
<child :car="car" :send="get"/>
子类接收并调用send方法
defineProps(['car','send'])
父类则会调用自己的get方法,实现数据的接收
自定义事件
父类自定义事件,子类接收事件并且是使用事件 ,事件触发后父类调用回调函数
下面是自定义了haha事件,回调函数是xyz
<child @haha="xyz"/>
子类使用前需要使用defineEmits接收事件
const emit = defineEmits(['haha'])
定义好后,使用emit方法带上对应参数则会使用这个事件(后面可以到数据传回父类回调函数)
<button @click="emit('haha',6666)"></button>
注:自定义事件的命名推荐使用my-event 或 myevent,不推荐小驼峰
mit
mitt与pubsub订阅消息与发布消息功能类似,它可以实现在任意组件间的通信
安装&引用mitt
npm i mitt
安装好后在src文件夹下建立utills文件夹,在utills文件夹下建立emitter.ts
//引入mitt
import mitt from 'mitt'
//调用mitt,得到emitter,emitter可以绑定事件和触发事件
const emitter = mitt()
//暴露emitter
export default emitter
随后在main.ts中引用emitter
import emitter from '@/utils/emitter'
emitter四种用法
四种方法分别是:
on():用来绑定事件,接收两个参数,第一个参数是事件名,第二个参数是事件触发时的回调函数
emit():用来触发事件,参数为事件名
off():解绑事件 ,参数为事件名
all(): 具有clear属性,直接调用clear可以解绑全部事件
简单的数据传输方式
定义一个父组件,两个子组件
两个子组件分别都引入emitter
import emitter from '@/utils/emitter';
在子组件1中定义数据,以及设置事件触发的按钮
<button @click="emitter.emit('getBook',book)">将book信息发送给子组件2</button>
let book = ref('理想国')
在第二个组件中定义事件
//给emitter绑定getBook事件,传入回调函数,回调函数接收一个参数
emitter.on('getBook',(val:any)=>{
book.value= val.value
})
let book = ref('');
v-model
v-model在vue3中有许多的语法
单个绑定
父组件进行值的定义与传输
<!--定义value值,并且使用v-model传输给子组件-->
<child v-model="value"/>
子组件使用defineProps进行接收
其中因为父组件传输值时使用的是v-model=‘数据名’
所有接收的时候只能接收到modelValue
//下面只写相应的关键代码
// 接收
defineProps([
'modelValue', // 接收父组件使用 v-model 传进来的值,必须用 modelValue 这个名字来接收
]);
const emit = defineEmits(['update:modelValue']); // 必须用 update:modelValue
function handle() {
// 参数1:通知父组件修改值的方法名
// 参数2:要修改的值
emit('update:modelValue', '子改变值');
}
其中值改变后是一个双向改变,子类父类的值会同步
多个绑定
与当个不同的是我们会给对应的值定义名称
父组件
<Child v-model:msg1="val1" v-model:msg2="val2" />
子组件
// 接收
defineProps({
msg1: String,
msg2: String,
});
const emit = defineEmits(['update:msg1', 'update:msg2']);
function changeMsg1() {
emit('update:msg1', '蔬菜1');
}
function changeMsg2() {
emit('update:msg2', '蔬菜2');
}
v-model修饰符
v-model中内置了好几种修饰符如:trim、.number 和 .lazy,不过大多是时候需要自己定制修饰符
下面我将定义修饰符good,将内容改成very good
父
<Child v-model.good="val" />
子
//接收修饰符与数据
const props = defineProps(['modelValue', 'modelModifiers']);
const emit = defineEmits(['update:modelValue']);
//进行修饰符判断
onMounted(() => {
// 判断有没有good修饰符,有的话就执行 下面得方法 方法
if (props.modelModifiers.good) {
emit('update:modelValue', 'very good');
}
});
$attrs
在数据传输中attrs存放的是未被pops接收的数据
祖传孙
在多层组件嵌套下,如 父 子 孙 三层,当子要与孙进行通信时就可以用attrs解决
首先解释一下什么是未被接收的数据
父
<child a='10' b='20' c='30'/>
子
//只接收a
definePops(['a'])
只接收a,那么a数据会存在于pops中,b c数据会在attrs
对此我们可以通过这样由子传给孙
子
<grandchild $attrs/>
上面的代码类似于
<grandchild b='20' c='30'/>
所以在孙模块中可以接收到b c
孙
defineProps(['b','c'])
孙传祖
现在父模块内定义方法,并且传输到孙模块即可实现
默认定义了 a b c
父
<Child :a="a" :b="b" :c="c" :changeA="changeA"/>
function changeA(value:number){
a.value += value
}
子
<grandchild :="$attrs"/>
孙
defineProps(['a','b','c','changeA'])
要改变数值只需要调用changeA()即可
$refs与$parent
$refs用于父传子,$parent用于子传父
$refs
ref在普通DOM标签上的作用是获取DOM节点,在组件上的作用是获取组件的示例对象
父
<Child ref="a"/>
let a = ref()
子
//子组件通过defineExpose将数据交出去
defineExpose({name,age})
父
//改变值的方法
function changeA(){
a.value.name= '卡卡西'
}
父组件调用changeA的时候可以改变子组件暴露出来的name值
$parent
$parent的用法与$refs用法类似,$parent获取的是父组件的实例对象
父
//暴露数据
defineExpose({money})
子
<!--调用方法传入 $parent--->
<button @click="minusMoney($parent)">减少父组件存款</button>
//定义方法操作数据
function minusMoney(parent:any){
parent.money -= 1
}
provide与inject
provide与inject 前者提供数据,后者注入接收是使用
祖传孙
父
import {reactive,provide} from 'vue'
//提供数据
provide('person',person)
孙
import { inject } from 'vue';
//注入数据
let person= inject('person')
inject方法第一个参数是接收名,第二个参数可以定义默认值,当没有数据提供时也不会报错
孙传祖
父
//方法
function chuangeA(value:number){
person.age = value
}
//提供方法
provide('chuangeA',chuangeA)
孙
let discount = inject('chuangeA',(value:number)=>{})
<button @click="chuangeA(7)">改名年龄</button>
slot插槽
插槽分为三种:默认插槽,具名插槽,作用域插槽。
默认插槽
<slot>这是默认内容</slot>
普通的标签类似于div
具名插槽
父
<Child>
<!-- v-slot后面是冒号,冒号后面对应插槽名称 -->
<template v-slot:title>
<h2>热门游戏列表</h2>
</template>
<!-- steup语法糖,直接使用#代替v-slot: -->
<template #content>
<span>魔兽世界</span>
</template>
</Child>
子
<!-- 插槽1 -->
<slot name="title">这是默认内容</slot>
<!-- 插槽2 -->
<slot name="content">这是默认内容</slot>
通过具体名字,父类的定义会插入到子类的对位置
作用域插槽
用于子传父,其数据存在子组件,但是数据结构由父组件决定
子
<slot :games="games"></slot>
类似于pops的方式传输数据
父
<template v-slot="params">
<ul>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
父组件使用v-slot接收,也可使用语法糖#写法
此外该还有个在接收的时候进行了解构赋值
<!-- 在接收的时候进行了解构赋值-->
<template #default="{games}">
<h5 v-for="g in games" :key="g.id">{{ g.name }}</h5>
</template>