【vue3】入门基础知识点
Vue3核心语法
组合式API【vue3】与选项式API【vue2】
setup
- setup和data、methods同级别, 可与data等共存,data里面可以读取使用setup中声明的变量,而setup不能使用data中声明的变量(
setup加载时间早于beforeCreated
) - setup中的
this都是undefined
- 直接声明变量,其变量是
非响应式
的 - setup中
需要写返回值
,将声明的东西抛出去(返回值可以是对象,也可以是渲染函数)
非响应式写法
setup(){
let name = 'zs';
Let age = 18;
function changeName(){
name = '李四';
}
return {name,changeName}
}
setup的语法糖
- 在
script
标签里加上setup,即可不用写return
- vue3中一个vue文件可以有多个script标签,但是只有一个标签里有setup属性
- 在setup中
引入子组件时,不需要注册,即可使用
<script setup lang="ts">
let a = 'zhang-san'
</script>
如何声明组件名字?
- 与vue2类似,需要多写一组script, 且不能加setup
- 使用defineOptions定义name*【defineOptions({name:‘xx’})】
<script lang="ts">
export default {
name: "Person"
}
</script>
响应式数据ref,reactive
基本类型 - ref
- 经过ref方法声明的变量是响应式的,此时变量是一个
带有value属性的对象
,在template模块中可直接使用对象,若想在方法中修改变量值,需要xx.value去修改。
<template>{{name}}</template>
<script setup>
import {ref} from 'vue'
let a= ref('张三') // a是响应式的
changeName = >{
name.value = '李四'
}
</script>
对象类型-ref/reactive
- reactive:
只能
定义对象类型,声明的对象是深层次
的
<script setup>
// ref 写法
let car = ref({brand:'x',price:10})
changePrice = > {
car.value.price +=10
}
changeCarInfo = > {
car.value = {brand:'cc',price:20} // 此方法可以直接修改原car对象
car = ref({brand:'cc',price:20}) // 此时已为car重新分配了一个对象,因此原对象并不会改变
}
----------------------------------------------------------------------------------
// reactive写法
import {ref,reactive} from vue
let car = reactive({brand:'Aa', price:20})
changePrice => {
car.price +=10
}
changeCarInfo = > {
Object.assign(car,{brand:'cc',price:10}) // 采用object.assign()方法修改原car对象
car = reactive({brand:'cc',price:10}) // 此时已为car重新分配了一个新对象,因此原对象并不会改变
}
<script>
ref与reactive的区别
- ref创建的变量必须使用.value去修改(可以使用Volar插件去自动添加.value)
- ref声明对象类型,实际上底层也是用了reactive
- reactive重新分配一个新对象,会失去响应式【
修改reactive定义的变量:若为reactive创建的变量重新分配一个新对象,此时变量已指向新对象,并不会被修改(可以使用Object.assign去整体替换)
】
toRef与toRefs
- toRefs:使对象中的
每个属性变成响应式
【toRefs(对象)】 - toRef:
指定
对象中的属性变成响应式【toRef(对象,‘属性’)】 - ⚠️:使用toRefs/toRef声明的响应式变量,实际和原变量指向同一个对象!!
<script>
let person = reactive({name:'xx',age:18})
let {name,age} = person
console.log(name,age) // 此时的name和age是非响应式的
let {name,age} = toRefs(preson)
console.log(name.value) // 此时的name和age是响应式
let age1 = toRef(preson,'age') // age1是响应式
changeAge = > {
age1.value + = 1
}
console.log(age1.value,person.age) // 此时person.age的值也发生了改变
⚠️:person.name/person.age是一个响应式的对象,使用toRefs/toRef声明的响应式变量,实际和person.name/age指向同一个对象!!
</script>
计算属性computed
- 计算属性:也是一个ref所定义的一个响应式数据,可通过set修改值
有缓存
,只会依据所计算的东西改变而变化
<script>
import {computed} from 'vue'
let a= ref(1)
let b= ref(2)
// 利用计算属性做操作
let sum = computed(()=>{
return a.value + b.value
})
let sum1 = computed({
get(){} // 常用
set(val){} // 可修改计算属性的值,不常用
})
</script>
监听watch
第一个参数是监听器的【监视的数据源
】,第二个参数是一个【监视的回调函数
】,第三个参数一个【属性对象
】
- 这个【
源
】:分四类,ref定义的数据,reactive定义的数据,一个函数返回一个值(getter函数),由这几种类型的值组成的数组
情况一
监视【ref】定义的【基本类型】数据
- 直接写数据名,
监视的是其value值的改变
import {ref,watch} from 'vue'
let sum = ref(1)
const stopWatch = watch(sum,(newVal,oldVal)=>{
console.log('sum变化',newVal,oldVal)
// 停止监听?
if(newVal > 10){
stopWtach() // 调一遍自己
}
})
情况二
监视【ref】定义的【对象类型】数据
- 直接写数据名,
监视的是对象的【地址值】
,若想监视对象内部的数据,要手动开启深度监视(deep:true)
- 若修改的是ref定义的对象中的属性,newValue和oldValue都是新值,因为指向同一个对象
- 若修改整个ref定义的对象,newValue是新值,oldValue是旧值,因为指向的不是同一个对象了
import {ref,watch} from 'vue'
let person = ref({name:'zx',age:18})
changeName(()=>{
person.value.name = 'ls'
})
changePerson(()=>{
person.value = {name:'ls',age:24} // 改变整个person对象
})
// 监视person对象,若想监听name属性改变,设置第三个参数:{deep:true}
watch(person,(newVal,oldVal)=>{
// 进行一些操作
console.log('监视~',newVal,oldVal)
},{deep:true})
情况三
监听【reactive】定义的【对象类型】数据
默认开启深度监视,且无法关闭
(即对象内部属性变化,也会监听)
情况四
监听ref或reactive定义的【对象类型】数据中的【某个属性】值(源:getter函数)
- 需要写成
函数形式
,若属性是对象,则监视的是地址值,需要关注对象内部,需要手动开启深度监视
import {ref, watch } from 'vue'
let person = ref({
name:'az',
age:18,
car:{
a:1,
b:2
}
})
function changeName(){
person.value.name = 'ls'
}
fucntion changeCar(){
person.value.car.a = 3
}
function changeAllCar(){
// 这个是对象类型car属性,指向地址变化了
person.value.car = {a:4,b:5}
}
// 监视person的基本类型属性
watch(()=>{person.name},(newVal,oldVal)=>{
console.log('只监听name改变')
})
// 监视person的对象类型属性car
watch(()=>{person.car},(newVal,oldVal)=>{
console.log('只能监听到整个对象属性car的变化')
})
// 监视person的car中的属性
watch(()=>{person.car},(newVal,oldVal)=>{
console.log('能监听到整个car的变化,也能监听到car中的属性变化')
},{deep:true})
情况五
监视多个数据
数组形式
watch([()=>person.name,person.car],(newVal,oldVal)=>{
console.log('监视person对象的name属性,car属性')
},{deep:true})
监听watchEffect
- 立即运行一个
函数
,同时响应式的追踪
其依赖,全自动监视
- 不用明确指出监视的数据(
用到啥,就监视啥
)
import { ref,watchEffect } from 'vue'
let temp = ref(0);
let height = ref(10);
// 监视变化,无需明确指出监视的数据
watchEffect(()=>{
if(temp.value > 20){
consoe.log('温度达到了');
}
})
标签的ref属性
- 作用:用ref属性去
标记内容
- html标签上的ref:获取的是DOM节点
- 组件标签上的ref:获取的是组件实例对象
⚠️:父组件无法
直接拿到子组件内的数据,需要子组件通过defineExpose
方法去输出
<h2 ref = 'title' >html元素</h2>
<child ref = 'cd'/>
import {ref,defineExpose} from 'vue'
# Hmtl元素上的ref
let title = ref(); // 用于存储ref标记的内容
console.log(title.value) // 打印:h2标签元素
# 组件元素上的ref
let cd = ref();
console.log(cd.value); // 打印:child子组件,但获取不到任何child子组件的内容
# 若child子组件内
defineExpose({xx,xx1,xx2}) // 父组件能拿到此抛出的值
生命周期
- 和Vue2一样,也分四个阶段:
创建、挂载、更新、卸载
- 创建:setup内
- 挂载:onBeforeMount、onMounted
- 更新:onBeforeUpdate、onUpdated
- 卸载:onBeforeUnmount、onUnmounted
⚠️:子组件的生命周期比父组件的生命周期执行更早!!
import { onMounted } from "vue";
// 例子
onMounted(()=>{
console.log('页面挂载完毕')
})
hooks与mixin
- 作用:类似
vue2中的混合
- mixin,封装一个对象的东西 - 可以调用生命周期、计算属性等
import useDog from '@/hooks/useDog' // 封装的有关于"狗"的东西
# "狗"对象的属性,方法
const {dogList, getDog} = useDog
路由(createRouter)
- 存放在route文件夹中
- 环境搭建:npm i vue-route
如何使用
# 第一步:主页面注册
import router from './router.js'
app.use(router)
----------------------------------------------------------------
# 第二步:route文件夹中
import { createRouter, crateWebHistory } from 'vue-router'
// 引入即将使用的组件
import Home from '../components/Home.vue'
import Detail from '../components/detail.vue'
// 创建路由器实例
const router = createRouter({
history:crateWebHistory(), // 路由器的工作模式(history)
routes:[
{
name:'home', // 路由名字
path:'/home',
component:Home,
children:[
{
path:'deatil', // 嵌套子路由
component:Detail
}
]
}
]
})
// 暴露
export default router
-----------------------------------------------------------------
# 第三步:页面使用
import {RouterView,RouterLink} from 'vue-router'
// 路由跳转
<RouterLink to="/home" active-class="类型"></RouterLink>
<RouterLink to="/home/detail" active-class="类型"></RouterLink>
// 路由展示区
<RouterView></RouterView>
注意点
- 通过点击导航,视觉效果
“消失”的路由组件
,默认是被“卸载”
掉的,需要的时候再去“挂载”
to两种写法
<RouterLink to="/home"></RouterLink>
// 对象写法
<RouterLink :to="{name:'home'}"></RouterLink>
<RouterLink :to="{path:'/home'}"></RouterLink>
⚠️编程式路由导航
- 作用:可用于符合某些场景时,才能跳转的情况
- router.push/router.replace方法里写法和RouterLink的to属性一致
import { router } from 'vu-route' // 拿到路由器
// 例如点击时跳转
function showDetail(news){
# 可回退
router.push({
name:'xiang',
params:{
id:news.id
}
})
# 不可回退
router.replace({
name:'xiang',
params:{
id:news.id
}
})
}
传参方式
- query
# 传递
// 第一种
<RouterLink :to="`/news/detail?id=${item.id}&${item.name}`"></RouterLink>
// 第二种
<RouterLink
:to="{
path:"/news/detail",
query:{
id:item.id,
name:item.name
}
}"
></RouterLink>
# 接收
<div>{{router.query.id}}</div>
<div>{{query.id}}</div>
import {useRoute} from 'vue-router'
import {toRefs} from 'vue'
let route = useRoute()
let query = toRefs(route) // 将对象中所有属性变成响应式
- params
- 需要
提前
在路由处配置占位符(/:xx)
- 使用to的对象写法,
必须使用name
配置项,不能用path
- 使用?表示,参数非必传
- 需要
# 路由配置
path: 'detail/:id/:name?' // 占位, name非必传
# 传递
// 第一种
<RouterLink :to="`/news/detail/${item.id}/${item.name}`"></RouterLink>
// 第二种
<RouterLink
:to="{
name: 'xiang',// 只能有name
params:{
id:item.id,
name:item.name
}
}"
>
</RouterLink>
# 接收
<div>{{parmas.name}}</div>
import {useRoute} from 'vue-route'
import {toRefs} from 'vue'
let route = useRoute()
let params = toRefs(route)
路由的props配置
- 布尔值写法:将路由收到的所有params参数,作为props传给路由组件
- 函数写法:自己决定将什么作为props传给路由组件,可以获取参数
- 对象写法:自己决定将什么作为props传给路由组件,参数写死的【少用,不重要】
route:[
{
name:'xiang',
path:'detail',
component:Detail,
path:true // 第一种
path:(route){ // 第二种
return route.query | route.params
}
path:{ // 第三种
a:xx,b:xx
}
}
]
路由重定向
- 作用:给予默认值,
重新定于路由 (redirect)
routes:[{
path:'/',
redirect:'/home'
}]
路由器工作模式
- history模式
URL更美观,不带有# ,更接近传统网站,但后期项目上线,需要服务器配合处理路径问题
import {createWebHistory} from 'vue-router'
const router = createRouter({
history:createWebHistory()
})
- hash模式
兼容性更好,因为不需要服务端优化处理,但URL带有 “#” 不美观,且在SEO优化方面相对较差
import {createWebHashHistory} from 'vue-router'
const router = createRouter({
history:createWebHashHistory()
})
路由跳转历史记录模式
- push模式: 默认此方式,追加历史记录,浏览器
可回退
- replace模式:替换当前记录,
不可回退
# 开启replace模式
<RouterLink replace .....> xxxx <RouterLink>
Pinia(defineStore)
- 定义:
集中式状态管理
,每个组件都可以读取、写入它。即vue2中的vuex
- 搭建环境: npm i pinia
- 主要存放在store文件夹
如何使用
# 第一步:主页面mian中引入
import {createPinia} from 'pinia'
const pinia = createPinia()
app.use(pinia)
--------------------------------------------------------------------
# 第二步:store文件中
import {defineStore} from 'pinia'
exprot const useCountStore = defineStore('count',{
// 真正存储数据的地方
state(){
return {
sum:6,
str:'xxxxx'
}
}
})
--------------------------------------------------------------------
# 第三步:页面使用
import {useCountStore} from '@/store/Count'
// 拿取数据,是一个reactive对象
const countState = useCountStore()
let count1 = countState.sum || countState.$state.sum
⚠️修改数据的方法
直接修改
- 批量修改
$patch
- 调用state中
actions里定义的方法
(里面的this指向当前store)
import {useCountStore} from '@/store/Count'
const countState = useCountStore()
# 直接修改
countState.sum +=1
# 批量修改$patch
countState.$patch({
sum: 999,
str:'aaa'
})
# 调用state中actions里定义的方法
exprot const useCountStore = defineStore('count',{
actions:{
increment(value){
console.log('====',value) // 传递的参数
// this指向当前的store
this.sum + = value
}
},
// 真正存储数据的地方
state(){
return {
sum:6,
str:'xxxxx'
}
}
})
countState.increment(3)
storeToRefs
- 作用:
只关注store里的数据
,并可将其变成响应式
的
import {useCountStore} from '@/store/Count'
import {storeToRefs} from 'pinia'
const countState = useCountStore()
const {sum} = storeToRefs(countState)
getters
- 定义:
类似计算属性
,用于处理state中的数据
- 默认
携带参数
,参数指向当前store
exprot const useCountStore = defineStore('count',{
// 真正存储数据的地方
state(){
return {
sum:6,
str:'xxxxx'
}
}
// 计算
getters:{
bigSum(state){
return state.sum * 10
}
upperSchool():string{
return this.str.toUpperCase()
}
}
})
$subscribe
- 定义:
类似watch
,用于监听state中数据的变化
- 主要关注state参数,指向当前
store
import {useCountStore} from '@/store/Count'
const countState = useCountStore()
countState.$subscribe((mutate,state)){
// state指向当前store
console.log(state)
}
组件通信
props传参
- props是使用频率最高的一种通信方式,常用于:
父 <-> 子
-
- 若父传子:属性值是非函数
- 若子传父:属性值是函数
- defineProps方法:接受父组件传递的值(宏观方法,不引用也能用)
- withDefaults方法:赋予默认值
自定义事件emit
- 常用于
子给父通信
- defineEmits方法:定义
发射事件
mitt
- 第三方包,事件订阅,用于
任意组件间通信
- 拥有四个内置函数:on、emit、off、all
npm i mitt
# utils/emitter文件中
import mitt from 'mitt'
const emitter = mitt()
export default emitter
$attrs
- 常用于
祖孙通信
- 它是一个对象,包含所有父组件传入的标签属性,会自动排除props中声明的属性
# 祖组件
<Child :a="a" :b="b" v-bind="{x:100,y:200}" :update="update"></Child>
import {ref} from 'vue'
let a = ref(1)
function update(value){
a.value+=value
}
--------------------------------------------------------------------
# 父组件
<GrandChild v-bind="$attrs"></GrandChild>
// 若父组件声明了a,则$attars对象中不包含a属性
definePoprs(['a'])
--------------------------------------------------------------------
# 孙组件
<h3>a:{{a}}</h3>
<button @click="update(6)"></button> // 孙给组传递
defineProps(['a','b','update'])
$refs、parent
- refs:
父传子
(也就是父可以通过$refs拿到子组件实例
) - parent:
子传父
(子可以通过$parent拿到父组件实例
)
⚠️ 无论父子组件,其属性在被对方获取前,需要通过defineExpose抛出
# 父组件
<button @click="getAllChild($refs)">获取所有的子组件实例对象</button>
<Child1 ref="c1"></Child1> // 实例名字
<Child2 ref="c2"></Child2>
import {ref} from 'vue'
let c1 = ref()
let c2 = ref()
let house = ref(4)
function getAllChild(refs){
console.log(refs) // 两个子组件实例对象
}
defineExpose({house}) // 抛出属性,允许子组件获取
--------------------------------------------------------------------
# 子组件
<button @click="getParent($parent)"></button>
import {ref} from 'vue'
let book = ref(4)
function getParent(parent){
console.log(parent) // 父组件的实例对象
parent.house -=1
}
defineExpose({book}) // 抛出属性,允许子组件获取
provide、inject
- 用于
祖孙传递
- ref定义的变量,直接传递,不要传递x.value,会失去响应式
v-model
- 底层:属性和事件组合
- 可以更换value,可以在组件标签上多次使用v-model
# 父组件
<AtguiguInput v-model="userName" />
// v-model实际底层如下
<AtguiguInput :modelValue = "userName" @update:modelValue="userName = $event" />
# 子组件AtguiguInput
<input :value="modelValue" @input="emit('update:modelValue',xx)">
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
------------------------------------------------------------------
# 将value更换成abc名称/xyz名称
<AtguiguInput v-model:abc="userName" v-model:xyz="passWord" />
// 子组件
<input :value="abc" @input="emit('update:abc',xx)">
defineProps(['abc'])
const emit = defineEmits(['update:abc'])
插槽slot
默认插槽
- 无名,仅占位置。
<slot>
- 使用
具名插槽
- 给插槽命名,插入的内容需指定对应插槽名。
<slot name="xxx></slot>
v-slot: xx
只能用在组件标签/template标签上
,语法糖:#xx
作用域插槽
- 使用场景:数据在子那边,但根据数据生成的结构,由于父组件决定
- 可以给插槽传递值,
v-slot="xx"
只能用在组件标签/template标签
上。
其他API
shallowRef与shallowReactive
- 作用&区别:通过使用shallowRef()和shallowRactive()来
绕开深度响应
,浅层式API创建的状态只在其顶层
是响应式的,对所有深度的对象
不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提高性能。 - 使用
import {shallowRef, shallowReactive} from 'vue'
let myVal = shallowRef(0)
let myObj = shallowReactive({
name:'xx', // 这是顶层,是响应式的
age:18,// 这是顶层,是响应式的
car:{brand:"奔驰",money:18} // 无法对此进行更改,其为深层次
})
readonly与shallowReadonly
readonly
- 作用:用于创建一个对象的
深只读副本
- 特点:
-
- 对象的
所有嵌套属性
都将变成只读 - 任何尝试修改这个对象的操作都会被阻止
- 对象的
- 应用场景
-
- 创建不可变的状态快照
- 保护全局状态或配置不可被修改
shallowReadonly
- 作用:只将
对象的顶层属性
变成可读
- 特点
-
- 只将对象的顶层属性设置为只读,对象内部嵌套的属性任然可变
- 适用于只需要保护对象顶层属性的场景
import {ref,reactive, readonly,shallowReadonly} from 'vue'
let a = ref(0)
let a1 = readonly(a) // 仅可传入一个响应式数据
let b = reactive({
name:'xx', // b1中只读
car:{ // b1中可被修改
brand:'xx',
price:100
}
})
let b1 = shallowReadonly(b)
roRaw与markRaw
customRef
- 作用:创建一个
自定义的ref
,并对其依赖项跟踪和更新触发进行逻辑控制(类似消息的发布与订阅
) - 结构:customRef内部是一个函数,
有track和trigger两个参数
,需返回**get()和set()**方法 - 重点:
track()和trigger()参数
- track: 监听数据,持续关注,发生变化就去更新
- trigger: 在数据更新时,通知track
Teleport
- 定义:是一种能够将我们的
组件html结构移动到指定位置的技术
to
中可以写入元素标签/类/id等
- 使用:将如下html结构移动到body的位置。
<teleport to="body">
<div class="modal" v-show="isShow">
<h2>我是一个弹窗</h2>
</div>
</teleport>
Suspense
- https://cn.vuejs.org/guide/built-ins/suspense.html#suspense
全局API转移到应用对象
- vue2中通过vue.xx的东西全都转移到app.xx上
- https://v3-migration.vuejs.org/zh/breaking-changes/global-api.html
面试点:非兼容改变章节
- https://v3-migration.vuejs.org/zh/breaking-changes/