Vue3学习笔记(2)
ref&reactive
ref
可以针对基本数据类型,也可以针对对象类型。
用ref针对对象时,本质是在value里面用reactive实现数据响应。
引入ref:
import {ref} from 'vue'
函数里面调用被ref包裹的数据:
基本数据类型:数据名.value
对象类型:对象名.value.属性名(对象名.value[数组下标].属性名)
批量修改响应式数据
Object.assign(对象名,{替换/增加属性名:替换/增加属性,......})
Object.assign(car,{brand:'小米suv7',price:200})
reactive
只能针对对象类型
函数里面调用被reactive包裹的数据:
直接用对象名.属性名即可。
引入reactive:
import {reactive} from 'vue'
批量修改响应式数据
对象名.value={属性名:修改后的属性值,......}
toRefs(person)
取对象数据并具有响应式
引入toRef:import {reactive,toRef}
let nl=toRef(person,'age')
解构响应式数据
引入toRefs:import {reactive,toRefs}
let {name,age}=
使用场景
基本数据:ref
响应式对象:ref/reactive
层级较深的响应式对象(表单):reactive
计算属性computed
引入computed
import {ref,computed} from 'vue'
定义
可读但是不可写
let fullName=computed(()=>{
return firstName.value.slice(0,1).toUpperCase()+firstName.value.slice(1)+'-'+lastName.value
})
调用
{{fullName}}
特点:有缓存,如果数据不变,则就算调用多次也只会计算一次,然后把计算结果缓存,调用的时候直接访问的是缓存的数据(method是每次访问都会重新计算)。
watch监视
作用:监视数据的变化
特点:只能监视以下四种数据:
ref定义的数据
reactive定义的数据
函数返回一个值(getter函数)
一个包含上述内容的数组
引入watch
import {ref,watch} from 'vue'
监视ref定义的基本数据类型
定义
watch(监视的对象,回调函数(newValue,oldValue))
注:监视的是对象本身,而不是对象的value值,所以如果是ref包裹的对象,()里面不能加.value
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了');
})
停止监视
const stopWatch=watch(sum,(newValue,oldValue)=>{
console.log('sum变化了');
if(newValue>=10){
stopWatch()
}
})
监视ref定义的对象类型数据
情况一:只监视对象的地址值
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue);
})
情况二:深度监视,监视对象的属性
修改值的本质是直接把定义那里的属性值变化,所以watch输出的newValue和oldValue是相同的
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{deep:true})
</script>
立即执行:页面刷新,对象值还未改变先调用一次watch
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{deep:true,immediate:true})
监视reactive定义的对象类型数据
默认是深度监视,直接监视所有属性,并且无法变成浅层监听。
<script lang="ts" setup name="Person234">
import {reactive,watch} from 'vue'
// 数据
let person=reactive({
name:"张三",
age:18
})
// 方法
function changeName(){
person.name+='~'
}
function changeAge(){
person.age++
}
function changePerson(){
Object.assign(person,{name:"李四",age:80})
}
//监视:监视reactive定义的对象类型数据 默认开启深度监视
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue);
})
</script>
监视reactive定义的对象类型数据内部的某个属性
写成函数形式当做函数的返回值
基本数据类型
let person=reactive({
name:"张三",
age:18,
})
watch(()=>person.name,(newValue,oldValue)=>{
console.log('person.name',newValue,oldValue);
})
对象类型数据
监视的是对象在栈里面的地址,只改某个属性改的是堆里面的内容,person.car={c1:'迈巴赫',c2:'大众'}是将person.car指向其他地址,相当于栈改变了,如果要监视被监视的对象里面的其中一个属性的变化,要加deep:true
let person=reactive({
car:{
c1:'宝马',
c2:'奔驰'
}
})
function changeCar(){
// reactive修改值可以修改里面的值,相当于car是person的value值
person.car={c1:'迈巴赫',c2:'大众'}
}
watch(()=>person.car,(newValue,oldValue)=>{
console.log('person.car',newValue,oldValue);
},{deep:true})
监视reactive的多个数据
watch([()=>person.name,()=>person.car.c1],(newValue,oldValue)=>{
console.log('person.car',newValue,oldValue);
},{deep:true})
watchEffect监视
自动分析要监视的数据,不用支出坚实的数据,函数中用到哪些属性,就监视哪些属性。
watchEffect(()=>{
if(temp.value>=60||height.value>=80){
console.log('给服务器发送请求');
}
})
标签ref属性
标记内容
创建一个title2,用于存储ref标记的内容:
let title2=ref()
标记标签:
<h2 ref="title2">北京</h2>
获取标签:
console.log(title2.value);
特点:相当于局部标记,只会对同一个vue文件里面的标签有用,其他文件的标签如果有同名的标记不会和此文件冲突。
组件标记
在组件文件里面导出标记的数据:
//引入defineExpose
import {ref,defineExpose} from 'vue'
//将需要导出的标记的元素写在({})里面
defineExpose({a,b,c})
组件调用:
<Person ref="ren"/>
TS中的接口,泛型,自定义类型
作用:限制对象的名字,会报错
方式:
基本数据:
ts文件:
// 定义一个接口,用于限制person对象的具体属性
export interface PersonInter {//分别暴露
id:string,
name:string
age:number
}
引用ts文件:
import {type PersonInter,} from '../types/index'//ts文件相对位置
限制用法:
let person:PersonInter={id:'ashdlkha01',name:'张三',age:60}
复杂数据类型:
ts文件:
//一个自定义类型
// export type
//第一种
Persons=Array<PersonInter>
//第二种
export type Persons=PersonInter[]
限制用法:
第一种:
let personLIst:Array<PersonInter>=[//是一个数组,且数组里面的每一项都满足PersonInter的限制
{id:'lakjhlkjhl01',name:'张三',age:60},
{id:'lakjhlkjhl02',name:'李四',age:70},
{id:'lakjhlkjhl03',name:'王五',age:50}
]
第二种:
let personLIst:Persons=[//是一个数组,且数组里面的每一项都满足PersonInter的限制
{id:'lakjhlkjhl01',name:'张三',age:60},
{id:'lakjhlkjhl02',name:'李四',age:70},
{id:'lakjhlkjhl03',name:'王五',age:50}
]
props用法
导出数据:
<Person a="hh" :list="personList"/>
接收其他文件的数据:
import {defineProps} from 'vue'
defineProps(['a'])//就算只有一个也要包裹在[]里面
接收+将props保存:
let x=defineProps(['list'])
使用:
console.log(x.a);
接受+限制类型:
import {defineProps} from 'vue'
import {type Persons} from '../types'
defineExpose<{list:Persons}>()
接收+限制类型+限制必要性+指定默认值:
import {defineProps,withDefaults} from 'vue'
import {type Persons} from '../types'
withDefaults(defineProps<{list?:Persons}>(),{
list:()=>[{id:'sdfasdfas01',name:'康师傅',age:19}]
})
Vue生命周期
概念:Vue组件实例在创建时要经历一系列的初始化步骤,在此过程中 Vue会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子。
规律:
生命周期整体分为四个阶段,分别是:**创建、挂载、更新、销毁**,每个阶段都有两个钩子,一前一后。
Vue2的生命周期
创建阶段:beforeCreate、created
挂载阶段:beforeMount、mounted
更新阶段:beforeUpdate、updated
销毁阶段:beforeDestroy、destroyed
Vue3的生命周期
创建阶段:setup
挂载阶段:onBeforeMount、onMounted
更新阶段:onBeforeUpdate、onUpdated
卸载阶段:onBeforeUnmount、onUnmounted
常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)
<template>
<div class="person">
<h2>当前求和为:{{ sum }}</h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>
<!-- vue3写法 -->
<script lang="ts" setup name="Person">
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
// 数据
let sum = ref(0)
// 方法
function changeSum() {
sum.value += 1
}
console.log('setup')
// 生命周期钩子
onBeforeMount(()=>{
console.log('挂载之前')
})
onMounted(()=>{
console.log('挂载完毕')
})
onBeforeUpdate(()=>{
console.log('更新之前')
})
onUpdated(()=>{
console.log('更新完毕')
})
onBeforeUnmount(()=>{
console.log('卸载之前')
})
onUnmounted(()=>{
console.log('卸载完毕')
})
</script>
自定义hook
- 本质是一个函数,把 setup函数中使用的 Composition API进行了封装。
- 自定义 hook的优势:复用代码, 让 setup中的逻辑更清楚易懂。
useSum.ts
中内容如下:
import {ref,onMounted} from 'vue'
export default function(){
let sum = ref(0)
const increment = ()=>{
sum.value += 1
}
const decrement = ()=>{
sum.value -= 1
}
onMounted(()=>{
increment()
})
//向外部暴露数据
return {sum,increment,decrement}
}
useDog.ts
中内容如下:
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="increment">点我+1</button>
<button @click="decrement">点我-1</button>
<hr>
<img v-for="(u,index) in dogList.urlList" :key="index" :src="(u as string)">
<span v-show="dogList.isLoading">加载中......</span><br>
<button @click="getDog">再来一只狗</button>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
export default defineComponent({
name:'App',
})
</script>
<script setup lang="ts">
import useSum from './hooks/useSum'
import useDog from './hooks/useDog'
let {sum,increment,decrement} = useSum()
let {dogList,getDog} = useDog()
</script>
组件中具体使用:
<template>
<h2>当前求和为:{{sum}}</h2>
<button @click="increment">点我+1</button>
<button @click="decrement">点我-1</button>
<hr>
<img v-for="(u,index) in dogList.urlList" :key="index" :src="(u as string)">
<span v-show="dogList.isLoading">加载中......</span><br>
<button @click="getDog">再来一只狗</button>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
export default defineComponent({
name:'App',
})
</script>
<script setup lang="ts">
import useSum from './hooks/useSum'
import useDog from './hooks/useDog'
let {sum,increment,decrement} = useSum()
let {dogList,getDog} = useDog()
</script>
路由
基本切换效果
- Vue3
中要使用 vue-router
的最新版本,目前是 4
版本。
- 路由配置文件代码如下:
`main.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'
const router = createRouter({
history:createWebHistory(),
routes:[
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
]
})
export default router
main.ts代码如下:
import router from './router/index'
app.use(router)
app.mount('#app')
App.vue
代码如下:
<template>
<div class="app">
<h2 class="title">Vue路由测试</h2>
<!-- 导航区 -->
<div class="navigate">
<RouterLink to="/home" active-class="active">首页</RouterLink>
<RouterLink to="/news" active-class="active">新闻</RouterLink>
<RouterLink to="/about" active-class="active">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts" setup name="App">
import {RouterLink,RouterView} from 'vue-router'
</script>
注意点
路由组件通常存放在
pages
或views
文件夹,一般组件通常存放在components
文件夹。通过点击导航,视觉效果上“消失” 了的路由组件,默认是被*卸载**掉的,需要的时候再去**挂载**。
路由器工作模式
1、history模式
优点:`URL`更加美观,不带有 #
,更接近传统的网站 URL
。
缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有 404
错误。
const router = createRouter({
history:createWebHistory(), //history模式
/******/
})
2、hash
模式
优点:兼容性更好,因为不需要服务器端处理路径。
缺点:`URL`带有 #
不太美观,且在 SEO
优化方面相对较差。
const router = createRouter({ history:createWebHashHistory(), //hash模式 /******/ })
to的写法
<!-- 第一种:to的字符串写法 -->
<router-link active-class="active" to="/home">主页</router-link>
<!-- 第二种:to的对象写法 -->
<router-link active-class="active" :to="{path:'/home'}">Home</router-link>
命名路由
作用:可以简化路由跳转及传参。
给路由规则命名:
routes:[
{
name:'zhuye',
path:'/home',
component:Home
},
{
name:'xinwen',
path:'/news',
component:News,
},
{
name:'guanyu',
path:'/about',
component:About
}
]
跳转路由:
<!--简化前:需要写完整的路径(to的字符串写法) -->
<router-link to="/news/detail">跳转</router-link>
<!--简化后:直接通过名字跳转(to的对象写法配合name属性) -->
<router-link :to="{name:'guanyu'}">跳转</router-link>
嵌套路由
1、编写 News
的子路由:`Detail.vue`
2、配置路由规则,使用 children
配置项:
const router = createRouter({
history:createWebHistory(),
routes:[
{
name:'zhuye',
path:'/home',
component:Home
},
{
name:'xinwen',
path:'/news',
component:News,
children:[
{
name:'xiang',
path:'detail',
component:Detail
}
]
},
{
name:'guanyu',
path:'/about',
component:About
}
]
})
export default router
3、跳转路由(记得要加完整路径):
<router-link to="/news/detail">xxxx</router-link>
<!-- 或 -->
<router-link :to="{path:'/news/detail'}">xxxx</router-link>
4、记得去 Home
组件中预留一个 <router-view>
:
<template> <div class="news"> <nav class="news-list"> <RouterLink v-for="news in newsList" :key="news.id" :to="{path:'/news/detail'}"> {{news.name}} </RouterLink> </nav> <div class="news-detail"> <RouterView/> </div> </div> </template>
路由传参
query参数:
1、传递参数
<!-- 跳转并携带query参数(to的字符串写法) -->
<router-link to="/news/detail?a=1&b=2&content=欢迎你">
跳转
</router-link>
<!-- 跳转并携带query参数(to的对象写法) -->
<RouterLink
:to="{
//name:'xiang', //用name也可以跳转
path:'/news/detail',
query:{
id:news.id,
title:news.title,
content:news.content
}
}"
>
{{news.title}}
</RouterLink>
2、接收参数
import {useRoute} from 'vue-router'
const route = useRoute()
// 打印query参数
console.log(route.query)
params参数
1、传递参数
<!-- 跳转并携带params参数(to的字符串写法) -->
<RouterLink :to="`/news/detail/001/新闻001/内容001`">{{news.title}}</RouterLink>
<!-- 跳转并携带params参数(to的对象写法) -->
<RouterLink
:to="{
name:'xiang', //用name跳转
params:{
id:news.id,
title:news.title,
content:news.title
}
}"
>
{{news.title}}
</RouterLink>
2、接收参数
import {useRoute} from 'vue-router'
const route = useRoute()
// 打印params参数
console.log(route.params)
备注1:传递 params
参数时,若使用 to
的对象写法,必须使用 name
配置项,不能用 path
。
备注2:传递 params
参数时,需要提前在规则中占位。
路由的props配置
作用:让路由组件更方便的收到参数(可以将路由参数作为 props
传给组件)
{
name:'xiang',
path:'detail/:id/:title/:content',
component:Detail,
// props的对象写法,作用:把对象中的每一组key-value作为props传给Detail组件
// props:{a:1,b:2,c:3},
// props的布尔值写法,作用:把收到了每一组params参数,作为props传给Detail组件
// props:true
// props的函数写法,作用:把返回的对象中每一组key-value作为props传给Detail组件
props(route){
return route.query
}
}
replace属性
1、作用:控制路由跳转时操作浏览器历史记录的模式。
2、浏览器的历史记录有两种写入方式:分别为 ``push``和 ``replace``:
``push``是追加历史记录(默认值)。
replace
是替换当前记录。
3、开启replace模式
<RouterLink replace .......>News</RouterLink>
编程式导航
路由组件的两个重要的属性:`$route`和 $router
变成了两个 hooks
import {useRoute,useRouter} from 'vue-router'
const route = useRoute()
const router = useRouter()
console.log(route.query)
console.log(route.parmas)
console.log(router.push)
console.log(router.replace)
重定向
1、作用:将特定的路径,重新定向到已有路由。
2、具体编码:
{
path:'/',
redirect:'/about'
}