Day01
官方文档地址:cn.vuejs.org/guide/quick...
创建项目
使用 vite 构建工具进行创建项目
vite:基于原生 ES 模块,不进行打包。在开发服务启动的时候只会进行必要的编译,当浏览器在请求到某个模块的时候才会继续按需编译(利用了原生浏览器对原生 ESM 的支持)
webpack:也是一个构建工具,但是它是在启动开发服务之前就对整个项目进行了打包和编译,因此在项目庞大的时候会非常的耗时
在 cmd 输入 npm create vue@latest
进行创建项目,这边的 latest
是指最新稳定的一个版本,也可以更换进行指定版本号: npm create vue@3.12.0
在构建项目完成之后,输入 npm install
进行项目依赖的下载
在项目的 package.json
文件中查看运行项目的短命令是什么
虽然说 index.html
文件才是入口文件,但是这个入口文件中引入了 main.ts
文件,所以直接解读 main.ts
文件
ts
// 引入了 css 样式
import './assets/main.css'
// 从 vue 包中导入 createApp 函数,用来创建应用实例的方法
import { createApp } from 'vue'
// 导入根组件 App.vue
import App from './App.vue'
/*
* 使用 createApp 函数创建了根组件的实例,并且挂载到 id 为 app 的 DOM 元素上
* 就好像你在一个编号为 app 的地基上搭建了一个叫做 App 的房子框架出来
* */
createApp(App).mount('#app')
在 mian.ts
文件中创建了 App.vue
组件的实例,所以重写这个组件,让页面的内容被我指定
html
<!--
可以把 vue 文件看成是一个 html 文件,里面有结构、交互、样式
setup 代表使用 vue3 组合式API,会自动进行暴露给模板不需要手动 return。
lang="ts"代表的是这个标签中的代码使用的是 TypeScript 进行编写的
-->
<script setup lang="ts">
</script>
<template>
<div class="app">
<h1>Hello World</h1>
</div>
</template>
<!--
scoped 代表着这个样式的作用域只在当前组件。当组件编译后,样式后面可能会多出一些这种东西:data-v-xxxxxx
-->
<style scoped>
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
模板指令
如果想要==控制 HTML 元素的属性==,可以使用 v-bind 它有一个简写形式 :
html
<template>
<h2 :class="titleClass">v-bind:绑定类和属性</h2>
<div :style="boxStyle">这是一个动态样式的 div。</div>
<img :src="imgSrc" alt="Logo" width="100" />
</template>
如果想要通过条件==显示或者隐藏==DOM 元素,可以使用 v-if\v-else-if\v-else
或者 v-show
通过创建、销毁的形式
HTML
<template>
<button @click="changeScore">改变分数</button>
<p v-if="score > 90"> 恭喜你,成绩优秀! </p>
<p v-else-if="score > 60"> 成绩合格,继续努力! </p>
<p v-else> 成绩不理想,需要加油了! </p>
</template>
通过 display 进行控制
html
<template>
<button @click="toggleDisplay">切换显示</button>
<div v-show="isVisible">
<p>这是一个 `v-show` 渲染的元素。当条件为 false 时,元素只是被隐藏。</p>
</div>
</template>
==遍历数组或者对象中的数据==,通过 v-for
html
<template>
<h2>v-for:列表渲染</h2>
<h3>水果列表</h3>
<ul>
<li v-for="(fruit, index) in fruits" :key="index">
{{ index + 1 }}. {{ fruit }}
</li>
</ul>
<h3>用户信息</h3>
<ul>
<li v-for="(value, key) in userInfo" :key="key">
{{ key }}:{{ value }}
</li>
</ul>
</template>
想要==双向数据绑定==,比如说表单输入的时候数据也会发生变化,使用 v-model
如果是在<select>
中使用的话。那么 selected
会失效,这个时候就得通过 v-model
的值来定义默认选中哪个
html
<template>
<h2>v-model:表单双向绑定</h2>
<input type="text" v-model="name" placeholder="请输入姓名" />
<p>你的姓名是:{{ name }}</p>
</template>
==监听 DOM 事件==,比如说点击事件等等;使用 v-on 简写形式 @
html
<template>
<h2>v-on:事件绑定</h2>
<h3>点击事件</h3>
<button @click="incrementCount">点击我</button>
<p>你已经点击了 {{ count }} 次。</p>
<h3>鼠标事件</h3>
<div class="box" @mouseenter="showHint" @mouseleave="hideHint">
<p v-if="isHovering">鼠标移入啦!</p>
</div>
<h3>键盘事件</h3>
<input @keyup.enter="handleEnterKey" placeholder="按 Enter 键触发事件" />
</template>
setup函数
setup
函数是组合式 API 的入口;执行时机比 beforeCreate
还要早
在 setup
函数中不能使用 this
,如果使用的话会出现 undefined
,因为 setup
函数的执行实际比组件创建还早,这个时候组件实例还没有创建(数据都在 setup
函数中,它需要在组件创建之前将数据返回给模板使用)
它可以和 data、methods 进行共存,但是data、methods 可以碰到 setup
函数中的数据,反过来的话不行(如果想要碰到数据,需要使用 this
)
ref、reactive
使用 ref
、reactive
可以让数据变成响应式,但是需要引入: import {ref,reactive} from 'vue'
使用 ref
的时候,在 js 中想要碰到数据的话需要 .value
。template
中不需要,因为会自动解包
如果说在 reactive
中的一个数据被 ref
所包裹了,那么也是不用 .value
的,因为 reactive
会自动进行深度监视,进行解包
ref
、reactive
都可以定义对象类型的数据类型响应式,但是 ref
还可以定义基本类型的响应式数据;不过 ref
在定义对象类型响应式数据时是找 reactive
进行帮助的
如果是层级较深或者是表单相关的话,那么就使用 reactive
,其他时候 使用 ref
reactive
定义的响应式数据在重新赋值的时候会失去响应式,这个时候可以使用 Object.assign(对象名,{key:value,key:value})
进行重新赋值,或是一个一个赋值(但是这样的话如果这个对象有非常多的属性那么效率会非常的慢)
对于 reactive 定义的响应式数据重新赋值会丢失响应式的理解:
txt
person 是一个响应式对象,它的结构是这样的
person---------->0x0000000(地址)
地址中的数据:
name: 0x0000001(地址) -----> "张三"
age: 0x0000002(地址) -----> 18
如果直接进行重新赋值的话,那么等于说是把 0x0000000(地址) 直接修改成了 0x0000003(地址),但是我们数据源中的地址还是 0x0000001 和 0x0000002 所以会失去响应式
而 ref
定义的对象类型的响应式数据不会,因为 ref
是通过 value 进行管理值的
HTML
<template>
<h2>{{ sum }}</h2>
<button @click="sumAdd">sum+1</button>
<br/>
<h2>{{ person.name }}</h2>
<h2>{{ person.age }}</h2>
<button @click="nameAdd">姓名增加</button>
<button @click="ageAdd">年龄增加</button>
<button @click="modifyPerson">修改整个人</button>
</template>
<script setup lang="ts">
import {ref,reactive} from "vue";
let sum = ref(0);
let person = reactive({
name:"张三",
age:18
})
function sumAdd(){
sum.value += 1;
}
function nameAdd(){
person.name += '~'
}
function ageAdd(){
person.age += 1;
}
function modifyPerson(){
Object.assign(person,{name:"李四",age:28});
}
</script>
toRefs、toRef
如果把一个响应式对象中的值取出来的时候,那么这个值会失去响应式。但是如果使用的是 toRefs
进行取出的话,那么这个值会自动的被 ref
实现。并且这个值会和源数据保持同步
而这两者的区别就是,一个是针对于单独的值,一个是把所有的值都进行解构构出来
toRef
在解构的时候,需要两个参数:对象名、属性名称
computed
如果需要进行创建一个==具有响应式、可缓存的值==,那么就可以使用 computed
函数进行创建,这个值是根据其他的响应式数据进行创建的
computed
分为两种形式:可读、可读写。如果是想要可读写那么就需要用到get
、set
想要让 set 方法被执行,那么就要调用
computed
返回的ref
数据的.value
进行触发才行
html
<template>
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName" >
全名:<span>{{ fullName }}</span>
<br/>
<button @click="modifyName">修改全名</button>
</template>
<script setup lang="ts">
import {computed, ref} from "vue";
let firstName = ref("zhang")
let lastName = ref("san")
// 不可以修改,只能接收
// let fullName = computed(()=>{
// return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) +
// '-' +
// lastName.value.slice(0,1).toUpperCase() + lastName.value.slice(1)
// })
let fullName = computed({
get(){
return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) +
'-' +
lastName.value.slice(0,1).toUpperCase() + lastName.value.slice(1)
},
set(val){
let [str1,str2] = val.split('-');
firstName.value = str1
lastName.value = str2
}
})
function modifyName(){
fullName.value = "li-si"
}
</script>
Day02
watch
watch
可以监听四种数据(ref、reactive、函数返回的一个值、包含上诉内容的数组),它可以监听数据的变化
需要先引入才可以使用: import {watch} from 'vue'
然后要告诉它,监视谁,以及回调函数
在 watch
中监视 ref 的时候不需要 .value
,因为默认解包, watch
其实监听的是 ref.value
中的值 而不是 ref
本身!
如果要解除监视的话,那么只需要接收返回值,然后在调用一下返回值函数即可
html
<template>
<h2>{{ sum }}</h2>
<button @click="sumAdd">增加</button>
</template>
<script setup lang="ts">
import {ref, watch} from "vue";
let sum = ref(0)
let stopWatch = watch(sum,()=>{
console.log(sum.value)
// 解除监听
stopWatch()
})
function sumAdd(){
sum.value += 1
}
</script>
watch
在监视 ref
定义的对象类型的时候,如果是直接写对象名,那么只能在对象的地址发生变化的时候才生效;因为 ref
是通过 .value
来管理的。如果想要监视内部数据的变化,那么需要开启深度监视,第三个参数: {deep:true}
也可以让它马上监视: {immediate:true}
html
<template>
<h2>{{ person.name }}</h2>
<h2>{{ person.age }}</h2>
<button @click="modifyName">修改名称</button>
</template>
<script setup lang="ts">
import {ref, watch} from "vue";
let person = ref({
name:"张三",
age:18,
})
watch(person,()=>{
console.log("发生变化")
},{
// 开启深度监视
deep:true,
// 开启立即监视
immediate:true
})
function modifyName(){
person.value.name = "李四"
}
</script>
watch
函数会接收到两个值(新值、旧值);但是有的时候这两个值是相同的,是因为我们在修改值的时候,先修改的数据源然后才被监视到
在监听 reactive
创建的响应式数据时,默认开启了深度监视的,并且无法关闭
如果想要监听对象中的某一个属性变化的话,那么就需要用到 getter 函数。 watch(()=>xxxxx,()=>{})
如果需要监视多个数据的话,那么就需要使用数组: watch([()=>xxxx,()=>xxxx],{})
Day03
watchEffect
watchEffect
和 wacth
的区别就是,它可以==自动的监听回调函数中被使用到的响应式数据,并且不接受任何值== 并且在一开始的时候就会被执行一下,就好像==自带了 immediate
一样==
当我们的需求是==需要维护多个依赖的响应式数据,或是组件在场景时就需要执行一次回调函数,或是不关心数据的新旧值==的时候可以使用
html
<template>
<h2>{{ person.name }}</h2>
<h2>{{ person.age }}</h2>
<button @click="modifyName">修改名称</button>
</template>
<script setup lang="ts">
import {reactive, watch, watchEffect} from "vue";
let person = reactive({
name:"张三",
age:18,
})
watchEffect(()=>{
console.log(person.name+"发生了变化")
})
function modifyName(){
person.name = "李四"
}
</script>
ref标签
如果我们想要直接使用 JavaScript
直接操作 DOM
元素的话,那么可以使用 ref
模板引用,而不是使用 id
、 class
等形式,这样可以避免冲突
html
<template>
<h2 ref="hello">Hello world</h2>
<button @click="showLog">输出H2</button>
</template>
<script setup lang="ts">
import {ref} from "vue";
let hello = ref()
function showLog() {
console.log(hello.value)
}
</script>
如果 ref
标签不是放在 HTML 元素上,是放在 组件上,那么会拿到组件实例,但是无法拿到这个组件实例中的数据。如果想要让父组件拿到实例中的数据的话,那么需要使用 defineExpose({})
(也就是说 ref
标签,配合上 defineExpose({})
可以形成==子组件给父组件传递数据==)
子组件
html
<template>
</template>
<script setup lang="ts">
import {ref} from "vue";
let a = ref(0)
let b = ref(1)
let c = ref(2)
defineExpose({
a,b
})
</script>
父组件
html
<template>
<person ref="p"/>
<button @click="showLog">输出组件实例上的ref标记内容</button>
</template>
<script setup lang="ts">
import Person from "@/component/Person.vue";
import {ref} from "vue";
let p = ref()
function showLog(){
console.log(p.value);
}
</script>
TS中的接口、泛型、自定义类型
TypeScript 的接口跟 Java 的接口核心概念是一样的:定义一个规范,避免后面代码的错乱
这边使用分别暴露进行暴露接口
除了分别暴露之外还有统一暴露、默认暴露;其中除了默认暴露另外两个暴露在导入的时候需要使用 { } 进行导入,并且不能给导入的模块取名字
在接口中,不能使用 默认暴露,因为这个暴露是需要暴露一个值的,但是 接口属于一种类型的定义,在被编译的时候它是不存在的
ts
export interface PersonInter {
id: string,
name: string,
age: number
}
在引用接口的时候需要使用到关键字 type
html
<template>
<h2>{{ person.id }}---{{ person.name }}---{{ person.age }}</h2>
</template>
<script setup lang="ts">
import {type PersonInter} from "@/types/PersonInter.ts";
let person:PersonInter = {
id:"aahakjda1",
name:"张三",
age:18
}
</script>
并且不管是 ref
或是 reactive
在使用的时候,也都是可以使用泛型进行约束数据的类型是什么
html
<template>
<h2>{{ person.id }}---{{ person.name }}---{{ person.age }}</h2>
</template>
<script setup lang="ts">
import {ref} from "vue";
import type {PersonInter} from "@/types/PersonInter.ts";
let person = ref<PersonInter>({
id: "akdha23",
name: "李四",
age: 0
})
</script>
也可以自定义一个必须是 PersonInter
的数组类型出来,用法的话是一样的
ts
export interface PersonInter {
id: string,
name: string,
age: number
}
// 两种写法
// export type Persons = PersonInter[]
export type Persons = Array<PersonInter>;
props
如果==父组件想要给子组件==传递数据的话,那么可以使用 props
父组件给子组件传递数据的时候,可以直接在组件标签上进行传递,而子组件可以使用 defineProps
进行接收,接收的是一个数组,数组中的值跟传递过来的 key
是一样的
有的时候父组件可能传递错误的类型,这个时候子组件可以使用 :
进行限定
如果想要让这个传递的值可传可不传,那么就可以使用 ?
也可以使用 withDefaults
进行设置默认值
父组件
html
<template>
<person :pList="personList" />
</template>
<script setup lang="ts">
import Person from "@/component/Person.vue";
import {type Persons} from "@/types/PersonInter.ts";
let personList:Persons = [
{id:"ajdakl1", name:"张三", age:18},
{id:"ajdakl2", name:"李四", age:28},
{id:"ajdakl3", name:"王五", age:38}
]
</script>
子组件
html
<template>
<ul>
<li v-for="p in pList" :key="p.id">
{{ p.name }} ------ {{ p.age }}
</li>
</ul>
</template>
<script setup lang="ts">
// 只接收
// defineProps(['pList'])
// 也可以 接收并且保存这个对象
// let per = defineProps(['pList'])
import type {Persons} from "@/types/PersonInter.ts";
// 限定传递过来的值类型和是否必要
// defineProps<{pList?:Persons}>()
// 设置默认值
withDefaults(defineProps<{pList?:Persons}>(),{
pList:()=>[{id:"ahdnajkd1",name:"老七",age:88}]
})
</script>
Day04
Vue2生命周期
生命周期一共有四个阶段: 创建、挂载、更新、销毁
有的人会说三个,那是因为把创建和挂载放到了一起
每个阶段对应两个钩子,也就是说八个钩子(不包含路由、守卫等)
生命周期函数的另外叫法:钩子
创建前: beforeCreate
、创建完毕:created
挂载前: beforeMount
、挂载完毕: mounted
更新前: beforeUpdate
、更新完毕: updated
,更新钩子会被调用几次是更加被更新的次数来定的
销毁前: beforeDestroy
、 销毁完毕: destroyed
Vue3生命周期
Vue2 的生命周期 和 Vue3的生命周期基本上一样,只是要注意以下几个点:
创建前、创建完毕的生命周期函数被: setup
函数取代
setup
函数的创建时机比beforeCreate
还要早,所以setup
函数已经取代了beforeCreate
和created
函数
不再是我们直接使用这些生命周期函数,而是调用生命周期函数,并且传递一个回调函数给它,由它去执行回调函数
每一个生命周期函数前面等新增了一个 on
的单词
Vue3 中的销毁阶段变成了卸载阶段,生命周期函数也变成了 onBeforeUnmount
、onUnmounted
任何一个组件都是有自己的生命周期
父和子的挂载生命周期是由子先开始的;这就是Vue 的工作原理
父组件
html
<template>
<person v-if="personShow" />
<button @click="show">卸载person</button>
</template>
<script setup lang="ts">
import Person from "@/component/Person.vue";
import {onBeforeMount, onMounted, ref} from "vue";
let personShow = ref(true)
function show(){
personShow.value = false
}
console.log("App创建前")
console.log("App创建完毕")
onBeforeMount(()=>{
console.log("App挂载前")
})
onMounted(()=>{
console.log("App挂载完毕")
})
</script>
子组件
html
<template>
<h2>sum:{{ sum }}</h2>
<button @click="add">sum+1</button>
</template>
<script setup lang="ts">
import {onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, ref} from "vue";
let sum = ref(0)
function add() {
sum.value++
}
console.log("Person创建前")
console.log("Person创建完毕")
onBeforeMount(()=>{
console.log("Person挂载前")
})
onMounted(()=>{
console.log("Person挂载完毕")
// 通过 debugger 暂停可以看到控制台上的 App挂载完毕还没有输出,从而证明了 是先由内再到外
debugger;
})
onBeforeUpdate(()=>{
console.log("更新前")
})
onUpdated(()=>{
console.log("更新完毕")
})
onBeforeUnmount(()=>{
console.log("卸载前")
})
onUnmounted(()=>{
console.log("卸载完毕")
})
</script>
自定义Hooks
当数据和方法多了之后不好找到数据对应的方法。这个时候就可以使用 hooks
,它可以让数据和方法贴合到一块
所谓的 hooks
就是一个 使用js
或是 ts
进行编写的函数方法,里面放着互相关联的数据和方法,命名规范为 useDog
,use
后面的单词代表这这个文件的作用
相关的数据和方法需要放到一个函数中,如果使用的是默认暴露的话,那么就不需要命名,分别暴露的话需要给这个方法取个名字。并且这个方法需要给外部提供东西
并且在 hooks
中也可以使用生命周期
useDog.ts 文件
ts
import {reactive} from "vue";
import axios from "axios";
export default function (){
let dogList = reactive([
"https://images.dog.ceo/breeds/pembroke/n02113023_4544.jpg"
])
// async 代表的这是一个异步函数
async function getDog(){
try{
// await 代表等待请求响应
let res = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
dogList.push(res.data.message)
}catch (error){
alert(error)
}
}
return {dogList,getDog}
}
useSum.ts 文件
ts
import { ref} from "vue";
export default function (){
let sum = ref(0)
function add() {
sum.value++
}
return {sum,add}
}
在组件中使用这两个 ts
文件
html
<template>
当前sum: {{ sum }}
<button @click="add">sum+1</button>
<hr>
<img v-for="(dog,index) in dogList" :src="dog" :key="index" alt="狗的图片">
<br>
<button @click="getDog">获取狗狗</button>
</template>
<script setup lang="ts">
import useDog from "@/hooks/useDog.ts";
import useSum from "@/hooks/useSum.ts";
const {dogList,getDog} = useDog();
const {sum,add} = useSum();
</script>
<style scoped>
img {
padding: 10px;
height: 100px;
}
</style>
Day05
路由的概念
路由就是一组 key-value
的对应关系,而多个路由需要由路由器来管理
在前端的 SPA 应用中会经常的使用到这个路由(SPA single page web application)
想要使用路由需要进行下载依赖:npm install vue-router
只要这个项目涉及到了路由,那么就需要一个文件夹 router
进行管理
模块化的工程都是有自己的文件规范的,而路由则是放在 router
文件夹下
在 router
下创建一个ts
文件并且进行引用方法:createRouter
,使用这个方法进行创建路由器
并且需要告诉路由器它需要管理的路由有哪些,在方法中写入,同时还需要告诉路由器使用哪种工作模式
路由的工作模式分为两种:
history
和hash
模式
history
模式:不带 #
,url
更加的美观,但是后期项目上线的时候需要服务器配合处理路径问题,不然会404
hash
模式:兼容性更好,后端不需要处理路径问题,url 中带 #
但是对于 SEO 优化来说较差
按照模块化工程的规范来说,一般组件是放在 components
文件夹下,而路由组件是放在pages
或是 views
文件夹下
如果是一般组件,那么就需要我们
<person />
如果是路由组件,那么就是由路由进行渲染的:{page:'/home',component:Home}
ts
// 引入
import {createRouter,createWebHistory} from "vue-router";
// 引入路由中路径对应的组件
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import About from "@/pages/About.vue";
// 创建路由器并且交出去
export default createRouter({
// 路由的工作模式
history: createWebHistory(),
// 一个一个的路由规则
routes:[
{
path:'/home',
component: Home
},
{
path:'/news',
component: News
},
{
path:'/about',
component: About
}
]
})
当我们创建成功一个路由器并且配置了路由规则和工作模式之后,还需要在main.ts
进行引入使用才行,这样项目中才可以集成到这个路由
ts
import { createApp } from 'vue'
import App from './App.vue'
// 引入路由器,因为router的文件名是 index 所以不需要写具体的文件名,默认就是它
import router from './router'
createApp(App)
// 使用路由器
.use(router)
.mount('#app')
这个时候当我们的路径变成了 /home
或是 /news
或是 /about
的时候,路由器已经监测到了变化并且取出了路径对应的组件,但是我们没有告诉它应该放到哪进行展现,所以页面中还是没有内容,这个时候可以使用 RouterView
进行解决这个问题
html
<template>
<!-- 展示区-->
<div>
<RouterView></RouterView>
</div>
</template>
<script setup lang="ts">
import {RouterView} from "vue-router";
</script>
在实际的开发中不可能让用户去手动输入地址然后引起内容变化,所以还需要一个导航区让用户进行点击,使用RouterLink
进行解决这个问题(跟a
标签差不多的作用)
RouterLink 是一个组件,可以使用
to
进行定义要跳转到的地址位置,使用active-class
进行定义当被激活时的样式
展示区中的组件消失,实际是直接让这个组件进行卸载了
html
<template>
<!-- 导航区-->
<div>
<router-link to="/home" active-class="fontColor">首页</router-link>
<router-link to="/news" active-class="fontColor">新闻</router-link>
<router-link to="/about" active-class="fontColor">关于</router-link>
</div>
<!-- 展示区-->
<div>
<RouterView></RouterView>
</div>
</template>
<script setup lang="ts">
import {RouterView,RouterLink} from "vue-router";
</script>
<style scoped>
.fontColor{
font-weight: bold;
font-size: 26px;
color: #ff0000;
}
</style>
路由-to的两种写法
字符串写法: <router-link to="/about" active-class="fontColor">关于</router-link>
对象写法: <router-link :to="{path:'/about'}" active-class="fontColor">关于</router-link>
路由的命名
使用 name
在路由器中对路由进行命名
这个时候也可以使用 to
的第二种写法:对象写法进行跳转: <router-link :to="{name:'xinwen'}" active-class="fontColor">新闻</router-link>
要注意,虽然说重命名了路由但是不代表地址就会发生变化,这个重命名的名字是用来内部管理使用的!但是真正浏览器地址还是根据 path
进行的!
嵌套路由
回到路由器的管理页面,然后在想要被嵌套的路由中使用 children
就可以创建它的子路由了
子路由的路径是不需要 /
的
在使用的时候和它的父路由是一样的,但是路径要从最外层的路径开始写
路由管理页面
ts
// 引入
import {createRouter,createWebHistory} from "vue-router";
// 引入路由中路径对应的组件
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import About from "@/pages/About.vue";
import Detail from "@/pages/Detail.vue";
// 创建路由器并且交出去
export default createRouter({
// 路由的工作模式
history: createWebHistory(),
// 一个一个的路由规则
routes:[
{
path:'/home',
component: Home
},
{
name:'xinwen',
path:'/news',
component: News,
children:[
{
path:'detail',
component: Detail
}
]
},
{
path:'/about',
component: About
}
]
})
使用页面
html
<template>
<h2>我是News</h2>
<!-- 我是导航区-->
<div>
<ul>
<li v-for="news in newsList" :key="news.id">
<router-link to="/news/detail"> {{news.title}} </router-link>
</li>
</ul>
</div>
<!-- 我是展示区-->
<div>
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
import {reactive} from "vue";
let newsList = reactive([
{id:'ajkdajda1',title:"我是新闻1",content:"我是新闻1"},
{id:'ajkdajda2',title:"我是新闻2",content:"我是新闻2"},
{id:'ajkdajda3',title:"我是新闻3",content:"我是新闻3"}
])
</script>
路由-query参数
query的传参形式,在地址中 会带有 ?
,并且每组数据属于 key-value
的形式,中间使用&
进行衔接。
比如说这样:http://localhost:5173/news/detail?id=ajkdajda1&title=a&content=aaaaaaaaa
第一种写法:使用字符串的形式进行,在 ?
后面进行传参
父组件
html
<template>
<h2>我是News</h2>
<!-- 我是导航区-->
<div>
<ul>
<li v-for="news in newsList" :key="news.id">
<!-- 使用模板字符串, 用 ` 进行包裹字符串,然后在 ${} 中使用 js -->
<router-link :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`"> {{news.title}} </router-link>
</li>
</ul>
</div>
<!-- 我是展示区-->
<div>
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
import {reactive} from "vue";
let newsList = reactive([
{id:'ajkdajda1',title:"我是新闻1",content:"我是新闻1"},
{id:'ajkdajda2',title:"我是新闻2",content:"我是新闻2"},
{id:'ajkdajda3',title:"我是新闻3",content:"我是新闻3"}
])
</script>
第二种写法:使用对象的形式进行传参
父组件
html
<template>
<h2>我是News</h2>
<!-- 我是导航区-->
<div>
<ul>
<li v-for="news in newsList" :key="news.id">
<router-link :to="{
path:`/news/detail`,
query:{
id:news.id,
title:news.title,
content:news.content
}
}">
{{news.title}}
</router-link>
</li>
</ul>
</div>
<!-- 我是展示区-->
<div>
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
import {reactive} from "vue";
let newsList = reactive([
{id:'ajkdajda1',title:"我是新闻1",content:"我是新闻1"},
{id:'ajkdajda2',title:"我是新闻2",content:"我是新闻2"},
{id:'ajkdajda3',title:"我是新闻3",content:"我是新闻3"}
])
</script>
不管是哪一种方式的传参,但是子组件中接收方式都是一样的,使用 useRoute
进行接收,数据都在 query
中
子组件
html
<template>
<ul>
<li>id:{{ router.query.id }}</li>
<li>title:{{ router.query.title }}</li>
<li>content:{{ router.query.content }}</li>
</ul>
</template>
<script setup lang="ts">
import {useRoute} from "vue-router";
let router = useRoute();
</script>
路由-param参数
必须先在路由器的配置中对路由的path
进行提前的占位,形式为:/:xxx/:xxx
,如果有的参数是可有可没有,那么只需要在后面加上一个 ?
即可:/:xxx/:xxx?
路由器配置页面
ts
// 引入
import {createRouter,createWebHistory} from "vue-router";
// 引入路由中路径对应的组件
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import About from "@/pages/About.vue";
import Detail from "@/pages/Detail.vue";
// 创建路由器并且交出去
export default createRouter({
// 路由的工作模式
history: createWebHistory(),
// 一个一个的路由规则
routes:[
{
path:'/home',
component: Home
},
{
name:'xinwen',
path:'/news',
component: News,
children:[
{
name:'xiangqing',
path:'detail/:id/:title/:content',
component: Detail
}
]
},
{
path:'/about',
component: About
}
]
})
第一种写法:字符串拼接的形式
html
<template>
<h2>我是News</h2>
<!-- 我是导航区-->
<div>
<ul>
<li v-for="news in newsList" :key="news.id">
<router-link :to="`/news/detail/${news.id}/${news.title}/${news.content}`">
{{ news.title }}
</router-link>
</li>
</ul>
</div>
<!-- 我是展示区-->
<div>
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
import {reactive} from "vue";
let newsList = reactive([
{id:'ajkdajda1',title:"我是新闻1",content:"我是新闻1"},
{id:'ajkdajda2',title:"我是新闻2",content:"我是新闻2"},
{id:'ajkdajda3',title:"我是新闻3",content:"我是新闻3"}
])
</script>
第二种写法:对象的形式写法,这个时候就不能使用 path
进行编写了!==必须得用 name
的形式==
html
<template>
<h2>我是News</h2>
<!-- 我是导航区-->
<div>
<ul>
<li v-for="news in newsList" :key="news.id">
<router-link :to="{
name: 'xiangqing',
params: {
id: news.id,
title: news.title,
content: news.content,
}
}">
{{ news.title }}
</router-link>
</li>
</ul>
</div>
<!-- 我是展示区-->
<div>
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
import {reactive} from "vue";
let newsList = reactive([
{id:'ajkdajda1',title:"我是新闻1",content:"我是新闻1"},
{id:'ajkdajda2',title:"我是新闻2",content:"我是新闻2"},
{id:'ajkdajda3',title:"我是新闻3",content:"我是新闻3"}
])
</script>
子组件接收页面
html
<template>
<ul>
<li>id:{{ route.params.id }} </li>
<li>title: {{ route.params.title }} </li>
<li>content: {{ route.params.content }} </li>
</ul>
</template>
<script setup lang="ts">
import {useRoute} from "vue-router";
let route = useRoute()
</script>
路由规则的props
第一种写法:将路由收到的的所有 param
参数(==不包含query!==)作为props
进行传递
在路由器的配置页面找到需要配置props
的路由,然后直接将值设置为 true
即可。
当设置为 true
的时候就有点像父组件给子组件传递数据的样子了 <组件名 key=value key=value />
而这些key
就是占位符,value
就是我们传递的值,子组件只需要使用 defineProps
进行接收即可
ts
// 引入
import {createRouter,createWebHistory} from "vue-router";
// 引入路由中路径对应的组件
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import About from "@/pages/About.vue";
import Detail from "@/pages/Detail.vue";
// 创建路由器并且交出去
export default createRouter({
// 路由的工作模式
history: createWebHistory(),
// 一个一个的路由规则
routes:[
{
path:'/home',
component: Home
},
{
name:'xinwen',
path:'/news',
component: News,
children:[
{
name:'xiangqing',
path:'detail/:id/:title/:content',
component: Detail,
props: true
}
]
},
{
path:'/about',
component: About
}
]
})
子路由接收页面
html
<template>
<ul>
<li>id:{{ id }} </li>
<li>title: {{ title }} </li>
<li>content: {{ content }} </li>
</ul>
</template>
<script setup lang="ts">
defineProps(['id','title','content'])
</script>
<style scoped>
</style>
第二种写法:自己决定将什么作为 props
传递给子组件
将配置页面的 props 修改成 函数式,它会接收一个 route
对象,我们可以取出里面对应的query
进行返回
路由器配置页面
ts
// 引入
import {createRouter,createWebHistory} from "vue-router";
// 引入路由中路径对应的组件
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import About from "@/pages/About.vue";
import Detail from "@/pages/Detail.vue";
// 创建路由器并且交出去
export default createRouter({
// 路由的工作模式
history: createWebHistory(),
// 一个一个的路由规则
routes:[
{
path:'/home',
component: Home
},
{
name:'xinwen',
path:'/news',
component: News,
children:[
{
name:'xiangqing',
path:'detail',
component: Detail,
props(route) {
return route.query
},
}
]
},
{
path:'/about',
component: About
}
]
})
父组件页面
html
<template>
<h2>我是News</h2>
<!-- 我是导航区-->
<div>
<ul>
<li v-for="news in newsList" :key="news.id">
<router-link :to="{
path: '/news/detail',
query: {
id: news.id,
title: news.title,
content: news.content,
}
}">
{{ news.title }}
</router-link>
</li>
</ul>
</div>
<!-- 我是展示区-->
<div>
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
import {reactive} from "vue";
let newsList = reactive([
{id:'ajkdajda1',title:"我是新闻1",content:"我是新闻1"},
{id:'ajkdajda2',title:"我是新闻2",content:"我是新闻2"},
{id:'ajkdajda3',title:"我是新闻3",content:"我是新闻3"}
])
</script>
还有一个第三种写法:对象写法,但是会把数据写死
路由的replace属性
路由默认使用的是 push
属性,也就是先进后出
如果想要修改成 replace
属性,那么只需要在导航区的 router-link
上加上这个关键字即可
<router-link replace to="/home" active-class="fontColor">首页</router-link>
路由-编程式导航
RouterLink
在被编译到浏览器上的时候就一个 a
标签,如果这个时候我们是需要有一个按钮,然后点击一下会跳转的话是没办法实现的,而编程式导航其实就是脱离RouterLink
实现路由跳转
导入 useRouter
,有了这个那么我们就可以随意的进行跳转到想要进入的路由中,因为它是路由器
在types
中新建一个接口页面
ts
// 定义一个接口来规范它
export interface NewsInter{
id:string,
title:string,
content:string,
}
父组件传递数据的页面
html
<template>
<h2>我是News</h2>
<!-- 我是导航区-->
<div>
<ul>
<li v-for="news in newsList" :key="news.id">
<button @click="jump(news)">点我查看{{news.title}}</button>
</li>
</ul>
</div>
<!-- 我是展示区-->
<div>
<router-view></router-view>
</div>
</template>
<script setup lang="ts">
import {reactive} from "vue";
import {type NewsInter} from "@/types/NewsInter.ts";
import {useRouter} from "vue-router";
let router = useRouter()
let newsList = reactive([
{id:'ajkdajda1',title:"我是新闻1",content:"我是新闻1"},
{id:'ajkdajda2',title:"我是新闻2",content:"我是新闻2"},
{id:'ajkdajda3',title:"我是新闻3",content:"我是新闻3"}
])
// 参数这边可以news:any 也可以使用接口
function jump(news:NewsInter){
router.push({
path: '/news/detail',
query: {
id: news.id,
title: news.title,
content: news.content,
}
})
}
</script>
路由的重定向
当打开页面的时候,第一个页面的路径是 http://localhost:5173/
是没有路径的,这个时候控制台会报错,只需要进行重定向到一个页面上就可以解决了
重新配置一个配置项
路由器配置页面
ts
// 引入
import {createRouter,createWebHistory} from "vue-router";
// 引入路由中路径对应的组件
import Home from "@/pages/Home.vue";
import News from "@/pages/News.vue";
import About from "@/pages/About.vue";
import Detail from "@/pages/Detail.vue";
// 创建路由器并且交出去
export default createRouter({
// 路由的工作模式
history: createWebHistory(),
// 一个一个的路由规则
routes:[
{
path:'/home',
component: Home
},
{
name:'xinwen',
path:'/news',
component: News,
children:[
{
name:'xiangqing',
path:'detail',
component: Detail,
props(route) {
return route.query
},
}
]
},
{
path:'/about',
component: About
},
{
path:'/',
redirect:'/home'
}
]
})
Day06
Pinia
Pinia 是一个集中式状态(数据)管理工具。 也就是说专门用来管理组件之间共享的数据
需要先 npm install pinia
然后在 main.ts
文件中进行引用、创建、使用
ts
import { createApp } from 'vue'
import App from './App.vue'
// 引入
import {createPinia} from "pinia";
createApp(App)
// 创建、使用
.use(createPinia())
.mount('#app')
pinia-存储并读取数据
根据工程化项目的标准,pinia
有关的东西应该放在文件夹 store
中。并且命名规则是组件一样的,但是文件类型是 ts
使用 defineStore
进行定义这个仓库是跟什么类型有关的,并且要进行暴露出去
Count.ts 文件,存储数据
ts
import {defineStore} from "pinia";
/**
* useCountStore 命名和 hooks 相同,这是命名规范
* **/
export const useCountStore = defineStore('Count',{
state(){
return{
sum:0
}
}
})
Count.vue,取出数据
html
<template>
<h2>{{ countStore.sum }}</h2>
<select v-model="value">
<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 setup lang="ts">
import {ref} from "vue";
import {useCountStore} from "@/store/Count.ts";
const countStore = useCountStore();
// select 的默认选中是根据这个值来决定的,因为使用了 v-model 所以 selected 会失效
let value = ref(3)
function add() {
countStore.sum += value.value
}
function minus() {
countStore.sum -= value.value
}
</script>
pinia-修改数据
第一种方式:拿到数据之后直接修改
ts
<script setup lang="ts">
import {useCountStore} from "@/store/Count.ts";
const countStore = useCountStore();
function add() {
countStore.sum += value.value
}
function minus() {
countStore.sum -= value.value
}
</script>
第二种方式:当数据很多的时候。它是根据key
进行修改的,也就是说如果有的数据不想修改,直接不写即可
ts
countStore.$patch({
sum: value.value,
name:'lisi',
age:90
})
第三种方式: 当想要让一个方法也被复用的时候
Count.ts 文件
ts
import {defineStore} from "pinia";
export const useCountStore = defineStore('Count',{
state(){
return{
sum:0
}
},
actions:{
increment(value: number){
this.sum += value
}
}
})
Count.app 组件
html
<template>
<h2>{{ countStore.sum }}</h2>
<select v-model="value">
<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 setup lang="ts">
import {ref} from "vue";
import {useCountStore} from "@/store/Count.ts";
const countStore = useCountStore();
// select 的默认选中是根据这个值来决定的,因为使用了 v-model 所以 selected 会失效
let value = ref(3)
function add() {
countStore.increment(value.value)
}
function minus() {
countStore.sum -= value.value
}
</script>
pinia-storeToRefs
现在我们取出数据的写法是:<h2>{{ countStore.sum }}</h2>
这样有一个问题,当我们的数据量大的时候总不能一直写前面的 countStore
,所以就需要进行解构赋值
直接解构的话,那么数据会失去响应式。如果使用 toRefs
的话,会把里面的东西全部变成响应式。但是我们只想把数据变成响应式,所以这个时候可以使用 storeToRefs
pinia-getters
当 state
中的数据需要被加工在使用的时候,就可以使用这个
Count.ts 文件
ts
import {defineStore} from "pinia";
export const useCountStore = defineStore('Count',{
state(){
return{
sum:0,
name:'zhangsan'
}
},
actions:{
increment(value: number){
this.sum += value
}
},
getters:{
bigSum:state => state.sum * 10,
upperName():string{
return this.name.toUpperCase()
}
}
})
Count.vue 组件
html
<template>
<h2>{{ sum }}</h2>
<h2>{{ bigSum }}</h2>
<h2>{{ upperName }}</h2>
<select v-model="value">
<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 setup lang="ts">
import {ref} from "vue";
import {useCountStore} from "@/store/Count.ts";
import {storeToRefs} from "pinia";
const countStore = useCountStore();
const {sum,bigSum,upperName} = storeToRefs(countStore);
// select 的默认选中是根据这个值来决定的,因为使用了 v-model 所以 selected 会失效
let value = ref(3)
function add() {
countStore.increment(value.value)
}
function minus() {
sum.value -= value.value
}
</script>
pinia-subscribe
有点类似于 watch
当数据发生变化的时候触发。会接收到两个值 mutate
本次修改的信息 state
真正的数据
可以实现一个效果,当数据发生变化的时候,将值存储到浏览器中
html
<template>
<h2>{{ sum }}</h2>
<h2>{{ bigSum }}</h2>
<h2>{{ upperName }}</h2>
<select v-model="value">
<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 setup lang="ts">
import {ref} from "vue";
import {useCountStore} from "@/store/Count.ts";
import {storeToRefs} from "pinia";
const countStore = useCountStore();
const {sum,bigSum,upperName} = storeToRefs(countStore);
// select 的默认选中是根据这个值来决定的,因为使用了 v-model 所以 selected 会失效
let value = ref(3)
function add() {
countStore.increment(value.value)
}
function minus() {
sum.value -= value.value
}
countStore.$subscribe(()=>{
// 将数据存储到浏览器的本地缓存中
localStorage.setItem("number", JSON.stringify(sum.value))
})
</script>
Counts.ts 文件中,让值的数据从本地进行读取
ts
import {defineStore} from "pinia";
export const useCountStore = defineStore('Count',{
state(){
return{
sum:JSON.parse(localStorage.getItem('sum') as string ) || 0,
name:'zhangsan'
}
},
actions:{
increment(value: number){
this.sum += value
}
},
getters:{
bigSum:state => state.sum * 10,
upperName():string{
return this.name.toUpperCase()
}
}
})
pinia-组合式api
将这些代码
ts
import {defineStore} from "pinia";
export const useCountStore = defineStore('Count',{
state(){
return{
sum:JSON.parse(localStorage.getItem('sum') as string ) || 0,
name:'zhangsan'
}
},
actions:{
increment(value: number){
this.sum += value
}
},
getters:{
bigSum:state => state.sum * 10,
upperName():string{
return this.name.toUpperCase()
}
}
})
改为组合式 api
ts
import {defineStore} from "pinia";
import {ref, computed} from "vue";
export const useCountStore = defineStore('Count', () => {
// 状态 (State)
const sum = ref(JSON.parse(localStorage.getItem('sum') as string ) || 0);
const name = ref('zhangsan');
// 方法 (Actions)
function increment(value: number) {
sum.value += value;
}
// 计算属性 (Getters)
const bigSum = computed(() => sum.value * 10);
const upperName = computed(() => name.value.toUpperCase());
// 必须返回所有暴露给外部的属性和方法
return {sum, name, increment, bigSum, upperName};
});
Day07
组件通信-props
用来父子之间的通信。
如果是父传递给子的数据,那么就是直接在组件标签上写入数据,而子通过 defineProps
进行接收。
如果是子传递给父的数据,那么父需要先传递一个方法给子,然后子通过这个方法进行传递数据。
父组件
html
<template>
<children :car="car" :sendToy="getToy"/>
孩子的玩具:{{ toy }}
</template>
<script setup lang="ts">
import Children from './components/Children.vue';
import {ref} from "vue";
let car = ref("奥迪")
let toy = ref()
function getToy(value:string){
toy.value = value
}
</script>
子组件
html
<template>
父亲给的汽车:{{ car }}
<button @click="sendToy('迪迦奥特曼')">发送玩具给父</button>
</template>
<script setup lang="ts">
defineProps(['car','sendToy'])
</script>
组件通信-自定义事件
实现了子传递数据给父。
自定义事件的命名要使用肉串命名法:xxx-xxx
的形式
子组件要通过 defineEmits
进行触发事件
父组件
html
<template>
<h2 v-show="toy">{{ toy }}</h2>
<children @send-toy="getToy" />
</template>
<script setup lang="ts">
import Children from "@/components/Children.vue";
import {ref} from "vue";
let toy = ref()
function getToy(value:string) {
toy.value = value
}
</script>
子组件
html
<template>
<button @click="emit('send-toy',toy)">给父传递数据</button>
</template>
<script setup lang="ts">
import {ref} from "vue";
let emit = defineEmits(['send-toy'])
let toy = ref("迪迦奥特曼")
</script>
组件通信-mitt
可以用于任何的组件通信。
它是一个工具,所以需要先: npm install mitt
然后在 utils
或是 tools
文件夹中新建一个 ts
文件
绑定事件: 对象名.on()
,解除事件:``对象名.off(),触发事件:
对象名.emit(),获取全部事件:
对象名.all()`
在组件==卸载之前==,要先把这个组件绑定的事件进行==解绑==
mitt
就好像一个第三方的自定义事件库一样
本质上就是设立了一个代理商,组件可以在委托这边放置事件,也可以触发事件
如何快速的知道谁来绑定事件?需要数据的组件需要进行委托事件,而提供数据的组件进行触发事件。就好像下单流程一样
emitter.ts 文件
ts
import mitt from "mitt";
export default mitt()
父组件文件
html
<template>
<children />
<button @click="send">发送玩具</button>
</template>
<script setup lang="ts">
import emitter from "@/utils/emitter.ts";
import Children from "@/components/Children.vue";
function send() {
emitter.emit("send-toy","迪迦奥特曼")
}
</script>
子组件文件
html
<template>
接收到的玩具:{{ toy }}
</template>
<script setup lang="ts">
import {onUnmounted, ref} from "vue";
import emitter from "@/utils/emitter.ts";
let toy = ref()
emitter.on('send-toy',(value)=>{
toy.value = value
})
onUnmounted(()=>{
emitter.off("send-toy")
})
</script>
组件通信-v-model
在实际开发中很少会使用到,但是在UI组件库中会经常使用到!
可以用来父传子,也可以子传父
v-model
的本质就是一个 :value
实现了数据到页面上的变化 再加上一个 @input
实现了页面到数据上的变化
也就是说==通过 props
和 事件 进行实现==的
html
<template>
<!-- <input type="text" v-model="username">-->
<input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value" />
</template>
<script setup lang="ts">
import {ref} from "vue";
let username = ref('zhangsan')
</script>
当v-model
用在组件上的时候
这边的事件是自定义事件;update:
是固有的一个形式
html
<template>
<!-- <children v-model="username" />-->
<!-- 本质上就是这段代码
modelValue 和 update:modelValue 是 Vue3 的默认约定名字。
当我们在组件是直接使用 v-model 的时候那么会自动去找子组件中 props 和 事件 的名字为:modelValue。
如果我们修改了名字,那么就需要再写 v-model:你定义的名字="" 这样才行
-->
<children :modelValue="username" @update:modelValue="username = $event" />
</template>
<script setup lang="ts">
import Children from "@/components/Children.vue";
import {ref} from "vue";
let username = ref('zhangsan')
</script>
而子组件想要实现双向绑定,只需要这样
html
<template>
<input type="text" :value="modelValue" @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value) ">
</template>
<script setup lang="ts">
defineProps(['modelValue'])
let emit = defineEmits(['update:modelValue'])
</script>
组件通信-$attrs
可以用来父传孙,也可以实现孙传父(通过方法的形式)
当父传递的值没有被子进行接收的时候,那么就会存放在 $attrs
中,而子就可以直接把这个$attrs
传递给孙(如果接收的话那么就会出现在 props
中)
父组件
html
<template>
<h1> 我是父组件</h1>
<h2>{{ a }}</h2>
<h2>{{ b }}</h2>
<h2>{{ c }}</h2>
<h2>{{ d }}</h2>
<h2>孙子给的玩具{{ toy }}</h2>
<hr/>
<children :a="a" :b="b" :c="c" :d="d" :sendToy="getToy"/>
</template>
<script setup lang="ts">
import {ref} from "vue";
import Children from "@/components/Children.vue";
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)
let toy = ref()
function getToy(value:string){
toy.value = value
}
</script>
子组件
html
<template>
<h1>我是子组件</h1>
<hr/>
<!-- 不可以使用 :
因为 : 用来绑定单个属性的
但是 $attrs 是一个内置对象,会冲突的,而使用 v-bind 的话 那么会自动解构这个对象中的属性进行绑定
-->
<grandChildren v-bind="$attrs" />
</template>
<script setup lang="ts">
import GrandChildren from "@/components/GrandChildren.vue";
</script>
孙组件
html
<template>
<h1>我是孙组件</h1>
{{ a }}
{{ b }}
{{ c }}
{{ d }}
<button @click="sendToy('迪迦奥特曼')">给父组件传递数据</button>
</template>
<script setup lang="ts">
defineProps(['a', 'b', 'c', 'd','sendToy']);
</script>
组件通信-refs和\parent
父组件可以使用 ref
标记组件实例,在配合上组件的 defineExpose
进行暴露数据,从而实现子传父。
可是当子组件的数据多的时候,一个一个写就很麻烦,这个时候就可以使用 $refs
父组件
html
<template>
<Children1 ref="c1"/>
<Children2 ref="c2"/>
<button @click="change($refs)">修改玩具</button>
</template>
<script setup lang="ts">
import Children1 from './components/Children1.vue';
import Children2 from './components/Children2.vue';
import {ref} from "vue";
let c1 = ref()
let c2 = ref()
// value:any 也可以写成 value:{[key:string]:any}
function change(value:any){
// 为什么要用 循环? 因为 value 返回的是一个 c1:{} c2:{}
for (let key in value) {
value[key].toy += '~'
}
}
</script>
Children1 组件
html
<template>
<h2>c1的玩具:{{ toy }}</h2>
</template>
<script setup lang="ts">
import {ref} from "vue";
let toy = ref('迪迦奥特曼')
defineExpose({toy})
</script>
Children2 组件
html
<template>
<h2>c2的电脑:{{ toy }}</h2>
</template>
<script setup lang="ts">
import {ref} from "vue";
let toy = ref('小猪佩奇')
defineExpose({toy})
</script>
而如果是父传子的话,那么就是使用 $parent
并且父组件也得使用 defineExpose
进行暴露
父组件
html
<template>
<h2>私房钱:{{ money }}</h2>
<Children1 ref="c1"/>
<Children2 ref="c2"/>
<button @click="change($refs)">修改玩具</button>
</template>
<script setup lang="ts">
import Children1 from './components/Children1.vue';
import Children2 from './components/Children2.vue';
import {ref} from "vue";
let c1 = ref()
let c2 = ref()
let money = ref('10000')
// value:any 也可以写成 value:{[key:string]:any}
function change(value:any){
// 为什么要用 循环? 因为 value 返回的是一个 c1:{} c2:{}
for (let key in value) {
value[key].toy += '~'
}
}
defineExpose({money})
</script>
Children1
html
<template>
<h2>c1的玩具:{{ toy }}</h2>
<button @click="getMoney($parent)">拿私房钱</button>
</template>
<script setup lang="ts">
import {ref} from "vue";
let toy = ref('迪迦奥特曼')
// 有一个问题 如果不使用 value:any 那要怎么写?
function getMoney(value:any){
value.money -= 100
}
defineExpose({toy})
</script>
组件通信-provide和inject
专门用来实现 爷组件和子组件之间的通信,并且不会打扰到中间的组件
其实 provide
的意思是可以让这个组件给自己的所有后代提供数据
当我们想要拿到被提供的数据的时候,只需要使用 inject
进行注入即可
爷组件
html
<template>
<h2>养老钱:{{ money }}</h2>
<h2>有一辆{{ car.name }},价值:{{ car.price }}</h2>
<Children />
</template>
<script setup lang="ts">
import Children from './components/Children.vue';
import {provide, reactive, ref} from "vue";
let money = ref(10000)
let car = reactive({
price: 100,
name:'奔驰'
})
function updateMoney(value: number) {
money.value -= value;
}
provide("car",car)
// 不能写 .value 不然会失去响应式
provide("moneyContent",{money,updateMoney})
</script>
Children组件:用来引入孙组件
html
<template>
<GrandChildren />
</template>
<script setup lang="ts">
import GrandChildren from "@/components/GrandChildren.vue";
</script>
孙组件
html
<template>
爷爷给的车:{{car.name}} 价值:{{car.price}}
爷爷还有:{{ money }}
<button @click="updateMoney(100)">花爷爷的钱</button>
</template>
<script setup lang="ts">
import {inject} from "vue";
// 设置一个默认值,不然 ts 的推断不确定到底有没有这些属性
let car = inject("car",{name:'没有',price:0})
let {money,updateMoney} = inject("moneyContent",{money:0,updateMoney:(value:number)=>{}})
</script>
Day08
默认插槽
当我们复用子组件的时候,只需要在父组件上直接用即可,但是如果这个子组件有部分相同,但是内容不相同的时候,就可以使用这个插槽
在组件中写入多个数据,然后使用 <slot>
进行确定展示的位置
父组件
html
<template>
<children title="我是a">
我是A里面的内容
</children>
<children title="我是b">
我是B里面的内容
</children>
<children title="我是c">
我是C里面的内容
</children>
</template>
<script setup lang="ts">
import Children from './components/Children.vue';
</script>
子组件
html
<template>
<h2>{{title}}</h2>
<slot></slot>
</template>
<script setup lang="ts">
defineProps(['title'])
</script>
具名插槽
通过给 <slot>
添加 name
属性进行取名,而在使用的时候,只可以用在==组件==或是 template
上面
默认插槽也是有名字的:defalut
v-slot
的简写形式是 #
父组件
html
<template>
<children>
<template v-slot:s2>
我是A里面的内容
</template>
<template v-slot:s1>
<h2>我是a</h2>
</template>
</children>
</template>
<script setup lang="ts">
import Children from './components/Children.vue';
</script>
子组件
html
<template>
<slot name="s1">我是默认内容</slot>
<slot name="s2">我是默认内容</slot>
</template>
<script setup lang="ts">
</script>
作用域插槽
常用在作用域插槽中
特点是数据什么的都在插槽中,但是结构是在父组件中进行决定的
父组件
html
<template>
<children #="params">
<h2>{{params.title}}</h2>
<ol>
<li v-for="item in params.games" :key="item.id">{{ item.name }}</li>
</ol>
</children>
<children #="params">
<h2>{{params.title}}</h2>
<ul>
<li v-for="item in params.games" :key="item.id">{{ item.name }}</li>
</ul>
</children>
<children #="params">
<h2>{{params.title}}</h2>
<h3 v-for="item in params.games" :key="item.id">{{ item.name }}</h3>
</children>
</template>
<script setup lang="ts">
import Children from './components/Children.vue';
</script>
子组件
html
<template>
<slot :games="games" title="游戏列表"></slot>
</template>
<script setup lang="ts">
import {reactive} from "vue";
let games = reactive([
{id:'akjhd1',name:"CSGO"},
{id:'akjhd2',name:"英雄联盟"},
{id:'akjhd3',name:"穿越火线"},
{id:'akjhd4',name:"魔兽世界"}
])
</script>
shallowRef和shallowReactive
shallow
代表的就是浅层次的意思
shallowRef
和 shallowReactive
它只会处理第一层的数据;也就是说 xxx.xxx
只有一个 .
当关注的数据只在表面的时候可以使用它,避免性能的浪费
其实数据是有变化的,但是页面中没有变化而已(因为我们使用的是浅层次,所以Vue没有检测到这个数据的变化导致页面中的数据没有变化!)
shallowRef
的测试页面
html
<template>
<h2>{{person.name}}</h2>
<h2>{{sum}}</h2>
<button @click="changeName">改变名字</button>
<button @click="changeSum">改变数字</button>
</template>
<script setup lang="ts">
import {shallowRef} from "vue";
let person = shallowRef({
name: "John"
})
let sum = shallowRef(10)
function changeName(){
person.value.name = "tom"
}
function changeSum(){
sum.value ++
}
</script>
shallowReactive
的测试页面
html
<template>
<h2>{{person.name}}</h2>
<h2>{{person.info.age}}</h2>
<button @click="changeName">改变名字</button>
<button @click="changeAge">改变年龄</button>
</template>
<script setup lang="ts">
import {shallowReactive} from "vue";
let person = shallowReactive({
name: "John",
info:{
age: 18
}
})
function changeName(){
person.name = "tom";
}
function changeAge(){
person.info.age = 10;
}
</script>
readonly和shallowReadonly
readonly
将会限制数据只读不能修改,并且接收的数据也只能是响应式类型的数据
当有一些关键数据不希望被修改的时候,那么就可以用readonly
进行复制一份出来
而 shallowReadonly
代表的就是第一层不能被修改,但是第二层就可以修改
toRaw和markRaw
toRaw
就是把一个响应式数据变成了原始数据,失去了响应式
markRaw
会标记一个数据,让它永远不会变成响应式
customRef
用来自定义一个 ref
,当我们想要定义一个响应式数据,并且具有自己的规则的时候,那么就可以使用它
在真实的业务中,都会封装到 hooks
中
比方说,现在要实现一个效果:当用户输入后,延迟三秒页面在发生变化
需要先定义一个原始数据进行存放,然后使用 customRef
传入一个回调函数,这个回调函数要求具有返回值,这个返回值中要有get
和 set
方法
同时需要==使用 track
和 trigger
进行持续跟踪和触发==,这样customRef
才可以起作用
在设置计时的时候,要及时的清除计时器ID,不然在短时间内多次输入会产生bug
实现代码
html
<template>
<h2>{{ msg }}</h2>
<input type="text" v-model="msg" />
</template>
<script setup lang="ts">
import {customRef} from "vue";
// 默认值
let initValue = '你好'
// 设置一个存储计时器ID的值
let timer:number
let msg = customRef((track, trigger)=>{
return {
get(){
track()
return initValue
},
set(value){
// 清除计时
clearTimeout(timer)
// 重新赋值
timer = setTimeout(()=>{
initValue = value
trigger()
},3000)
}
}
})
</script>
封装成 hooks
ts
import {customRef} from "vue";
export default function(initValue:string,delay:number){
// 设置一个存储计时器ID的值
let timer:number
return customRef((track, trigger) => {
return {
get() {
track()
return initValue
},
set(value) {
// 清除计时
clearTimeout(timer)
// 重新赋值
timer = setTimeout(() => {
initValue = value
trigger()
}, delay)
}
}
})
}
调用 hooks
html
<template>
<h2>{{ msg }}</h2>
<input type="text" v-model="msg" />
</template>
<script setup lang="ts">
import useMsg from "@/hooks/useMsg.ts";
let msg = useMsg("你好",3000)
</script>
Teleport
当我们使用了filter
之后,那么 position
就会根据父容器进行定位而不是整个 body
这个时候就可以使用 teleport
, 只需要把结构放在这个标签中进行包裹,然后在属性 to
中写入需要传送到的位置即可
suspense
当我们在一个组件使用了异步任务,并且这个异步任务的数据我们还需要进行使用的时候,就可以使用 suspense
它是一个插槽,default
是代表这当加载成功之后才展示的数据,而在加载期间可以使用 fallback
中的内容
全局api
使用 app.component
就可以进行全局注册组件。 app.config
设置全局配置对象。app.directive
注册全局指令。
app.mount
挂载。app.umount
卸载。app.use
安装,使用