### ref 和 reactive 的区别
ref:用来定义 : 基本类型数据 、 对象类型数据
reactive 用来定义 : 对象类型数据
区别:
1、ref 创建的变量必须使用.value (可以使用volar插件自动添加.value)
2、reactive 重新分配一个新的对象 , 会失去响应式 (可以使用Object.assign去整体替换)
使用原则
1、如果需要一个基本类型的响应式数据,必须使用ref
2、如果需要一个响应式对象,层级不深 ref reactive都可以
3、如果需要一个响应式对象,并且层次不深,推荐使用reactive
## 接口回调更新reactive
```
ler car = reactive({name:'奔驰',price:100})
function handlerChangeCar(){
// Object.assign(obj1,obj2,obj3) 把obj2 和 obj3 里面的东西都加到obj1里面去 这么写 页面可以更新
Object.assign(car,{name:'奥迪',price:1})
}
```
reactive 具有局限性 -> reactive定义的对象不能整体修改
```
<script>
import { reactive } from 'vue'
let person = reactive({
name:'zhangsan',
age:18
})
// 修改name
function handlerChangeName(){
person.name = 'lisi'
}
// 修改age
function handlerChangeAge(){
person.age = 20
}
function handlerChangeAll(){
<!-- 整体修改 是不可以的 不能整体修改-->
person = {name:'lisia',age:92}
<!-- 但是可以用Object.assign() 整体修改reactive定义的对象 但是对象的地址值没有发生改变 只是字面量覆盖 -->
Object.assign(person,{name:'lisia',age:92})
}
</script>
```
如果是用ref的话 直接改就可以了
```
let car = ref({name:'奔驰',price:12})
car.value = {name:'奥迪',price:10}
```
### toRefs 用于解构赋值 把reactive相应是对象变成ref响应式对象
```
<script setuo lang='ts'>
import { reactive , toRefs } from 'vue'
let person = reactive({
name:'张三',
age:'19'
})
// 解构赋值
let { name , age } = toRefs(person) // toRefs 将reactive对象结构拿出来变成 ref的相应是对象 toRef只能解构一个对象
function handlerChangeName(){
name.value = '李四'
}
function handlerChangeAge(){
age.value = 112
}
</script>
```
从响应式对象的身上直接解构属性 获得的属性必定失去响应式 所以就需要toRefs()
<!-- 获取路由的query参数 需要引入 useRoute 从 vue-router里面 -->
```
<script lang='ts' setup>
import { useRoute } from "vue-router"
let route = useRoute()
let { query } = toRefs(route)
<!-- route是一个响应式对象 直接解构相应是对象的话数据会丢失响应式 所以需要用toRefs()包裹 -->
</script>
```
## 计算属性 computed
```
<script setup lang='ts'>
import { ref , computed } from "vue"
let firstName = 'zhang'
let lastName = 'san'
// 计算属性 只读 不可修改 需要写成箭头函数的形式
let fullName = computed(()=>{
return firstName + lastName
})
//可以修改
let fullName = computed({
get(){
return firstName + lastName
},
set(val){
let [ str1 , str2 ] = val
firstName.value = str1
lastName.value = str1
}
})
<!-- btn点击修改名字 -->
function handlerChangeName(){
fullName.value = 'li-si'
}
// 点击按钮把值传给 fullName 然后计算属性的set会接受 并且把值赋值给定义的响应式数据 实现数据修改
</script>
```
## watch
作用:监视数据的变化(和vue2中的watch作用是一样的)
特点:Vue3中的watch只能监视以下四种数据:
1、ref定义的数据
2、reactive定义的数据
3、函数返回一个值(getter函数)
4、一个包含上述内容的数组
### 监视情况1:监视ref定义的基本类型数据
```
<script>
import { ref , watch } from 'vue'
let sum = ref(0)
function handlerChangeSum(){
sum.value ++
}
<!-- 监视 watch(谁? , 回到函数()) -->
watch(sum,(newValue,oldValue)=>{
console.log('sum的值发生了变化',newValue,oldValue)
})
// 结束监视 自己回调自己 结束监视
let stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('newValue',newValue);
if(newValue > 10){
stopWatch()
}
})
</script>
```
### 监视情况2 监视ref定义的[对象类型]数据:直接写数据名,坚实的是对象的[地址值],如果想要监视对象内部的数据,需要手动开启深度监视
```
<script>
import { ref , watch } from "vue"
let person = ref({
name:'张三',
age:18
})
function handlerChangeNmae(){
person.value.name += '~~'
}
function handlerChangeAge(){
person.value.age += 1
}
// 监视person对象里面的属性值 需要开启深度监视
// 如果不开启深度监视 则监视不到对象里属性的变化 除非修改了整个ref对象
watch(person,(newValue,oldValue)=>{
console.log("@@@newValue",newValue)
console.log("@@@oldValue",oldValue)
},{deep:true})
</script>
```
注意:
* 如果修改的是ref定义的对象中的属性,newValue和oldValue监测到的都是新值,因为他们都是同一个对象
* 如果修改的是整个ref定义的对象,newValue是新值,oldValue是旧值,因为不是同一个对象了
### 监视3 reactive 默认开启深度监视 监视[对象类型]的数据
```
<template>
<buttom @click='handlerChangeBtn'>点击修改</buttom>
</template>
<script>
import { reactive,watch } from 'vue'
let person = reactive({
a:{
b:{
c:600
}
}
})
function handlerChangeBtn(){
person.a.b.c = 800
}
watch(person,(newValue)=>{
console.log(newValue)
})
</script>
```
### 情况四: 监视ref或者是reactive定义的[对象类型]数据中的某个属性 -> 注意以下几点
监视 ref或者是reactive里面的某一个值
* 如果该属性不是[对象类型]的数据,需要写成函数形式
* 如果该属性依然是[对象类型],可直接写,也可以写成函数,不过建议写成函数
![Alt text](image.png)
需要监听一个getter函数
![Alt text](image-1.png)
```
<script>
import { reactive,watch } import 'vue'
let person = reactive({
name:'zhangsan',
age:18,
car:{
car1:'宝马',
car2:'奔驰'
}
})
<!-- 监视响应式对象中的某个属性 ,并且属性是基本类型的,要写成函数形式 -->
<!-- 监视相应是对象当中的某个值 -->
watch(()=>{return person.name},(newValue,oldValue)=>{
console.log
})
<!-- 简写 不用写 return 和 { } 这个时候就只会监视person里面的name属性-->
watcch(()=> person.name ,(newValue,oldValue)=>{
console.log(newValue)
})
<!-- 此时person.name属性不是对象属性 是一个字符串 所以需要写成函数形式 ()=>{} -->
</script>
```
```
<script>
let person = reactive({
name:'zhangsan',
age:19,
car:{
car1:'奔驰',
car2:'奥迪'
}
})
function changeC1(){
person.car.car1 = '大众'
}
function changeC2(){
person.car.car1 = '雅迪'
}
function changeCar(){
person.car = { car1:'1122' , car2:'3332' }
}
watch(person.car,(newValue,oldValue)=>{
console.log("@@@newValue",newValue)
})
<!-- 监视属性值car 应为car是一个对象类型 所以监视car的时候可以不用写成函数的形式 ,但是这种情况只能监视car1和car2的变化 不能检测到整体的变化 因为car1 和 car2 变化的时候 person.car的地址值没有发生改变 所以能检测到 但是如果是整体发生了改变 即对象的地址值发生了改变 person.car就监测不到了 所以建议写成函数形式 () => person.car -->
<!-- 写成函数 watch监视的就是地址值 能检测整体的变化 但是修改car1和car2监测不到了 所以要写成以下形式-->
watch(() => person.car , (newValue) => {
console.log('@@@res',res)
},{deep:true})
<!-- 写成函数式能检测整体的变化 写深度监视 也能检测到car1和car2某个属性的改变 -->
</script>
```
### 监视5:监视多个数据
<!-- 用数组 , 并且开启深度监视-->
```
let person = {
name:'zhangsan',
age:19,
car:{
car1:'奔驰',
car2:'宝马'
}
}
watch([()=>person.name,()=>person.car.car1],(newValue)=>{
console.log('newValue')
},{deep:true})
```
## watchEffect() 自动监视所有属性
watchEffect是一个立即执行函数,并且是一个响应式的追踪依赖,并在依赖更改时重新执行函数
* watch和watchEffect对比
1.都能监视响应式数据的变化,不同的是监听数据变化的方式不同
2.watch需要指明索要监视的对象
3.watchEffect不用指明需要监视的数据(函数中用到哪些属性,就监视哪些属性)
* 代码示例
```
<template>
<!-- 温度超过60度 或者是水位高度超过80就请求接口 -->
<h1>当前温度{{ temp }}</h1>
<h1>当前水位高度{{ height }}</h1>
<button @click='handlerAddTemp'>点击温度+10</button>
<button @click='handlerAddHeight'>点击水位+10</button>
</template>
<script>
import { ref , watchEffect } from 'vue'
let temp = ref(10)
let height = ref(0)
function handlerAddTemp(){
temp.value += 10
}
function handlerAddHeight(){
height.value += 10
}
<!-- 通过watch监视 temp 和 height 的变化 监听多个参数需要数组-->
watch([temp,height],(newValue)=>{
console.log("newValue",newValue)
})
<!-- 用watchEffect 不需要写参数 自动监听变化的属性-->
watchEffect(()=>{
if(temp.value > 60 || height.value >80){
console.log('@@@)
}
})
</script>
```
## ref 写在标签属性里面 用于存储ref标记的内容
```
<template>
<div ref='title1'>中国</div>
<button @click='handlerClickRef'>点我</button>
</template>
<script>
import { ref } from "vue"
let title1 = ref()
function handlerClickRef(){
console.log(title1.value)
}
</script>
```
如果ref 放在组件标签上 打印出来的东西是组件的实例化对象
person.vue -> ```
<template>
</template>
<script setup>
import { ref , defineExport } from "vue"
let a = ref(1)
let b = ref(2)
let c = ref(3)
defineExport({ a , b , c })
<script>
```
App.vue -> ```
<template>
<person ref='title1'/>
<botton @click='clickRef'>点我</botton>
</template>
<script setup>
import { ref } from "vue"
let title1 = ref()
function clickRef(){
console.log(title1.value)
}
</script>
```
按理来说 能打印出以下结果
![Alt text](image-2.png)
2024/1/19 控制台提示信息
defineExpose() is a compiler-hint helper that is only usable inside <script setup> of a single file component. Its arguments should be compiled away and passing it at runtime has no effect.
## 泛型
泛型 用于规范js的数据类型
创建 types->index.ts 用于存储变量的类型
```
<!-- interface // 定义一个接口,用于限制person对象的具体属性 定义完之后把他暴漏出去 -->
export interface Persons {
id:string,
name:string,
age:number
}
<!-- 规范数组的时候也可以用以下两种写法 -->
export type personList = Array<Persons>
export type personList2 = Persons[]
```
person.vue
```
<script lang='ts' setup>
<!-- 需要写type -->
import { type Persons , type personList } from "@/types"
<!-- 限制创建变量的类型 -->
let person:Person = { id:'asddsww',name:'zhangsan',age:18 }
<!-- 如果要规范一个数组 -->
<!-- 下面的意思是 创建一个personList数组并且数组当中的每一个值都符合Person规范 -->
let personList:Array<Person> = [
{ id:'asddsww',name:'zhangsan',age:18 },
{ id:'ffsxxww',name:'lisi',age:32 },
{ id:'ttgscc',name:'wangwu',age:8 }
]
let personList2:personList = [
{ id:'asddsww',name:'zhangsan',age:18 },
{ id:'ffsxxww',name:'lisi',age:32 },
{ id:'ttgscc',name:'wangwu',age:8 }
]
</script>
```
## Props父子传值
App.vue ->
```
<template>
<Person a='哈哈' :list='personList' />
</template>
<script lang='ts' setup>
let personList = [
{id:'xxsas',name:'zhangsan',age:39},
{id:'vvaec',name:'lisi',age:2},
{id:'44gsc',name:'wangwu',age:12},
]
</script>
```
传递到子组件 person.vue ->
```
<template>
<div>{{ a }}</div>
<div>
<ul>
<li v-for='item in list'>{{ item.name }} -- {{ item.age }}</li>
</ul>
</div>
</template>
<script lang='ts' setup>
import { type Persons } from "@/types"
import { withDefault } from "vue"
<!-- 第一种接收方式 只是接收 不能打印 -->
defineProps(['a','list'])
<!-- 第二种接收方式 defineProps有返回值 用变量接收可以打印 -->
let x = defineProps(['a'])
console.log('@@@@x',x)
<!-- 第三种接收方式 接收并且限制类型 -->
defineProps<{list:Persons}>()
defineProps<{list?:Persons}>() // 父组件可以传递也可以不传递
<!-- 接收list + 限制类型 + 限制必要性 + 指定默认值(默认值用withDefaults) -->
<!-- 指定默认值用withDefault(xxx,{默认值}) -->
withDefault(defineProps<{list?:Persons}>(),{
list:()=>[{id:'scjjsa',name:'zhangskkkd',age:83}]
})
{
list:[{name:'zhangsan',age:32}] // 这种写法不行 需要写成函数形式 返回一个函数
}
// vue3中 defineXXX 是宏函数 不需要引入 直接用就行
</script>
```
## Vue2生命周期
创建(创建前 beforeCreate 创建完毕created)
挂载(挂载前 beforeMount 挂载完毕mounted) 把组件放到页面里面
更新(更新前 更新完毕)
销毁(销毁前 销毁完毕)
不包括路由的生命周期
## vue3生命周期
vue3生命周期中没有创建的钩子 setup替代了vue2的创建生命周期 不再区分创建前还是创建后
创建 setup
生命周期使用时需要引入
vue3生命周期相比于vue2来说前面需要加-on 并且调用的是生命周期指定的回调函数
挂载前vue3给你调用onBeforeMount指定的那个回调函数
onBeforeMount(()=>{
})
生命周期都需要传入指定的回调函数
销毁不用destory 用 onUnmounted 叫卸载
<!-- 父子组件生命周期挂载的顺序是子组件先挂载 父组件再挂载 符合深度优先遍历 App.vue是最后挂载完的 -->
## 用 await 请求接口时候如何处理异常 用try catch
v3示例 ->
```
async function handlerTestRequest(){
try{
let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
console.log('@@@@',result)
}catch(error){
alert(error)
}
}
<!-- 一般异常处理都是写在拦截器里面 -->
```
## 自定义hooks 让一个功能的数据和方法都放在同一个容器里面 类似于v2的mixin 实现组合式api 封装 + 引用(解构赋值)
引入的是一个函数
需要创建一个hooks文件夹 如果是跟学生相关的文件就写 usePerson.js/.ts 如果是跟狗相关的就写useDog.js/.ts
---hooks
----useDog.ts
----useSum.ts
---components
----person.vue
---App.vue
useDog.ts->
```
import { reactive } from 'vue'
import axios from 'axios'
export default function(){ // 写成一个函数 然后抛出 不需要写函数名
let dogList = reactive([])
async function addDogPage(){
try{
let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
dogList.push(result.data.message)
}catch(err){
alert(err)
}
}
return { dogList , addDogPage } // 需要有返回值
}
```
person.vue ->
```
<template>
<button @click='addDogPage'>点击增加图片</button>
<div>
<img v-for= "(item,index) in dogList" :src='item'></img>
</div>
</template>
<script lang='ts' setup>
import useDog from "@/hooks/useDog"
let { dogList,addDogPage } = useDog()
</script>
```
## vue3路由
引入路由 npm install vue-router
router->index.ts
```
<!-- 第一步 引入createRouter -->
import { createRouter } from "vue-router"
<!-- 引入要呈现的组件 -->
import Home from "@/components/Home.vue"
<!-- 第二步 创建路由 -->
const router = createRouter({ // 这样创建路由会报错 vue3需要规定路由的工作模式 通过引入createWebHistory/createWebHashHistory 分别对应history和hash两种工作模式
routes:[
{
path:'/Home',
components:'Home'
}
]
})
<!-- 正确写法 -->
import { createRouter , createWebHistory,createWebHashHistory } from "vue-router"
const router = createRouter({ // 引入路由工作模式
history:createWebHistory()
routes:[ // 一个一个路由规则
{
path:'/Home',
component:'Home' // 这个地方是component 没有s 不要加s
}
]
})
export default router // 暴露出去
```
main.js中需要引入
```
import router = from "./router"
<!-- 挂载到app当中 -->
const app = createApp(App)
app.use(router)
app.mount('#app')
```
用的时候需要引入 RouterView 和 RouterLine // 现在不需要引入了 直接写RouterView 和 RouterLink就行了
App.vue ->
```
<template>
<!-- 跳转区 -->
<RouterLink to='/home'>我的</RouterLink>
<RouterLink to='/news'>新闻</RouterLink>
<RouterLink to='/xxx'>xxx</RouterLink>
<!-- 展示区 -->
<RouterView></RouterView>
</template>
<script lang='ts' setup>
import { RouterLink,RouterView } from 'vue-router'
</script>
```
## 两种路由模式 hash(默认)和history模式
history模式
优点:url更加美观 不带# 更接近传统的网站url
缺点:后期项目上线,需要服务端配合处理路径问题,否则会有404报错
hash模式(默认形式)
优点:兼容性好,不需要服务端处理路径
缺点:URL带有 # 不太美观,并且再SEO优化方面相对较差
** Vue2处理路由两种模式 **
```
export default new VueRouter({
mode:'history' / 'hash'(默认不用写)
routers:[
{
xxz
}
]
})
```
** Vue3处理路由两种模式 **
```
import { createRouter,createWebHistory,createWebHashHistory }
const router = createRouter({
history:createWebHistory() / createWebHashHistory()
routes:[
{
xxx
}
]
})
```
** vue3 必须要配置路由模式 vue2不需要(默认是hash) **
一般后台用hash模式 不需要考虑美观 前台项目例如商城等项目用history模式比较多
### router注意点
** 路由组件通常放在pages或者是views文件夹下 一般组件通常放在components文件夹 **
** 通过点击导航,视觉效果上消失了路由组件 默认是被销毁掉了 需要的时候再去挂载 **
但是vue2中可以用 keep-alive 缓存路由组件 保持挂载不被销毁 vue3暂时不知道
## routet的query传参以及嵌套路由
Vue3需要用到路由的话 需要引入useRoute 跟vue2直接打印this.$route是有区别的
Vue3中 import { uesRoute } from "vue-router"
useRoute是一个hooks函数
detail.vue ->
```
<template>
<ul>
<li>编号:{{ query.id }}</li>
<li>标题:{{ query.title }}</li>
<li>内容:{{ query.content }}</li>
</ul>
</template>
<script>
import { toRefs } from "vue"
import { useRoute } from "vue-router"
let { useRoute } from "vue-router"
let route = useRoute()
let { query } = toRefs(route)
</script>
```
news.vue ->
```
<!--
* @FilePath: News.vue
* @Author: 是十九呐
* @Date: 2024-01-25 16:20:31
* @LastEditTime: 2024-01-29 17:07:43
-->
<template>
<div class="container">
新闻
<div class="content-box">
<div>
<ul>
<li v-for="item of newList" :key="item.id">
<RouterLink :to="{path:'/news/detail',query:{
id:item.id,
title:item.title,
content:item.content
}}"> {{ item.title }} </RouterLink>
</li>
</ul>
</div>
<div>
<RouterView />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive } from "vue";
let newList = reactive([
{ id: 'asdcc22', content: '新闻内容1112111', title: '标题1' },
{ id: '34sffe', content: '测试呐aaa', title: '标题2' },
{ id: 'kkxmj2', content: '69', title: '69' },
])
</script>
<style>
.content-box{
display: flex;
}
</style>
```
* !!!Vue3接收route里面的参数时候 必须要 import { useRoute } from 'vue-router' *
## query参数和params参数的区别 + params参数传值
1、query参数在拼接字符串传递时 可以-> to='/news/detail?id=xxsff2&title=测试标题&content=测试内容'
1、params参数在拼接字符串传递时 不需要key-value的形式 to='/news/detail/cajjw23/测试标题/测试内容' 但是需要在配置路由的时候占位
count router = createRouter({
routes:[
{
path:'/home',
component:() => import('@/components/Home.vue') // 路由懒加载
},{
path:'/news',
component:() => import('@/components/News.vue') // 路由懒加载
children:[
{
path:'detail/:id/:title/:content', // params参数传递时需要占位 !!!!
component: () => import('@/component/detail.vue')
}
]
},{
path:'/about',
component:() => import('@/components/About.vue') // 路由懒加载
}
]
})
* params两个注意点 *
1、params传参时在router.js的路径上需要占位
2、params参数在对象传值中不能用path,只能用name
```
<RouterLink :to='{ name:'xiang',params:{id:'2332ddds',title:'测试标题',content:'测试内容'} }'></RouterLink>
<!-- 这个地方只能用name 不能用 path 否则就会报警告 忽视命名式路由 -->
```
![Alt text](image-4.png)
![Alt text](image-5.png)
![Alt text](image-6.png)
用params传值方式如果遇到可传可不传的参数在后面写一个?即可
children:[
{
path:'detail/:id/:title/:content?',
component:() => import('@/components/detail.vue')
}
]
![Alt text](image-7.png)
## 路由规则的props配置 不建议用 因为代码不好追溯
![Alt text](image-8.png)
在router.js里面配置props
children:[
{
path:'detail/:id/:title?/:content?',
component:()=>import('xxx')
<!-- 第一种写法 将路由收到的所有params参数作为props传递给路由组件 -->
props:true // 👈 只能用于params传值
},
{
name:'xiang',
component:()=>import('xxx')
<!-- 第二种写法 可以自己决定将什么作为props给路由组件 需要写成函数的形式并且有返回值,query和Params传值方式都可以用 -->
props(route){
return route.query/params // 👈 params和query传值方式都可以用
}
}
]
接收参数时 都是下面的方式用defineProps 👇👇👇
在相应的组件中用defineProps接收就跟Vue3的父子传值一样
defineProps(['id','title','content'])