Vue3笔记

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

使用 refreactive 可以让数据变成响应式,但是需要引入: import {ref,reactive} from 'vue'

使用 ref 的时候,在 js 中想要碰到数据的话需要 .valuetemplate 中不需要,因为会自动解包

如果说在 reactive 中的一个数据被 ref 所包裹了,那么也是不用 .value 的,因为 reactive 会自动进行深度监视,进行解包

refreactive 都可以定义对象类型的数据类型响应式,但是 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 分为两种形式:可读、可读写。如果是想要可读写那么就需要用到getset

想要让 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

watchEffectwacth 的区别就是,它可以==自动的监听回调函数中被使用到的响应式数据,并且不接受任何值== 并且在一开始的时候就会被执行一下,就好像==自带了 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 模板引用,而不是使用 idclass 等形式,这样可以避免冲突

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 函数已经取代了beforeCreatecreated 函数

不再是我们直接使用这些生命周期函数,而是调用生命周期函数,并且传递一个回调函数给它,由它去执行回调函数

每一个生命周期函数前面等新增了一个 on 的单词

Vue3 中的销毁阶段变成了卸载阶段,生命周期函数也变成了 onBeforeUnmountonUnmounted

任何一个组件都是有自己的生命周期

父和子的挂载生命周期是由子先开始的;这就是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 进行编写的函数方法,里面放着互相关联的数据和方法,命名规范为 useDoguse 后面的单词代表这这个文件的作用

相关的数据和方法需要放到一个函数中,如果使用的是默认暴露的话,那么就不需要命名,分别暴露的话需要给这个方法取个名字。并且这个方法需要给外部提供东西

并且在 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 ,使用这个方法进行创建路由器

并且需要告诉路由器它需要管理的路由有哪些,在方法中写入,同时还需要告诉路由器使用哪种工作模式

路由的工作模式分为两种: historyhash 模式

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 代表的就是浅层次的意思

shallowRefshallowReactive它只会处理第一层的数据;也就是说 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 传入一个回调函数,这个回调函数要求具有返回值,这个返回值中要有getset 方法

同时需要==使用 tracktrigger 进行持续跟踪和触发==,这样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 安装,使用


Vue2和Vue3的区别

v3-migration.vuejs.org/zh/

相关推荐
一个专注api接口开发的小白几秒前
手把手教程:使用 Postman 测试与调试淘宝商品详情 API
前端·数据挖掘·api
织_网27 分钟前
Electron 核心 API 全解析:从基础到实战场景
前端·javascript·electron
vvilkim39 分钟前
深入理解 Spring Boot Starter:简化依赖管理与自动配置的利器
java·前端·spring boot
艾小码1 小时前
前端安全防护手册:对抗XSS、CSRF、点击劫持等攻击
前端·安全·xss
2401_837088501 小时前
setup 语法糖核心要点
开发语言·前端·javascript
用户3379044802172 小时前
HTML5语义化标签详解
前端
唐某人丶2 小时前
教你如何用 JS 实现一个 Agent 系统(1)—— 认识 Agentic System
前端·人工智能
丘山子2 小时前
分享链接格式不统一,rel="share-url" 提案试图解决这个问题
前端·面试·html
JustHappy3 小时前
「Versakit攻略」🔥Pnpm+Monorepo+Changesets搭建通用组件库项目和发包流程
前端·javascript·vue.js
紫金龙腾3 小时前
EDGE 、chrome、浏览器显示“由你的组织管理”
前端·chrome·edge