本文详细介绍了Vue3的计算属性与监视,生命周期,并与Vue2的写法做出分析对比。展示了自定义hook函数,toRef与toRefs的详细代码示例;也介绍不常用的Composition API
1)计算属性-简写(没有考虑计算属性被修改的情况)
import {computed} from "vue";
person.fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
2)计算属性-完整(考虑读和写)
person.fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(newValue){
const nameArr = newValue.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
watch:{
sum(newValue,oldValue){ //简单写法
console.log(newValue,oldValue)
},
sum:{ //完整写法
immediate:true,
deep:true,
handle(newValue,oldValue){
console.log(newValue,oldValue)
}
}
},
watch()函数的参数,参数1:监视的对象,参数2:监视的回调;参数3:监视的配置项
watch()函数的两个小“坑”:
1、监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
2、监视reactive定义的响应式数据中某个属性时:deep配置有效
代码展示
watch ([sum,msg],(newValue,oldValue)=> {
console.log('sum变了',newValue,oldValue)
},{immediate:true})
注意:1、此处无法正确获取oldValue; 2、强制开启深度监视(deep配置无效)
watch(pers,(newValue,oldValue)=>{
console.log('pers变化',newValue,oldValue)
})
watch(()=>pers.age,(newValue,oldValue)=>{
console.log('age变化',newValue,oldValue)
})
watch([()=>pers.age,()=>pers.name],(newValue,oldValue)=>{
console.log('name与age',newValue,oldValue)
})
watch(()=>pers.job,(newValue,oldValue)=>{
console.log('深度监视job',newValue,oldValue)
},{deep:true})
watch的套路是:既要指明监视的属性,也要指明监视的回调
wachEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
wachEffect像computed:
1)computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
2)而wachEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
watchEffect(()=>{
const x1 = sum.value
const x2 = pers.job.j1.salary
console.log('watchEffect的回调',x1,x2)
})
1)、Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy 改名为beforeUnmount
destroyed 改名为unmounted
2)、Vue3.0也提供了Composition API形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate ===>setup()
created =======>setup()
beforeMount===>onBeforeMount
mounted=======>onMounted
beforeUpdate ===>onBeforeUpdate
updated=======>onUpdated
beforeUnmount ==>onBeforeUnmount
unmounted=====>onUnmounted
代码
import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
setup(){
onBeforeMount(()=>{console.log('--onBeforeMount--')})
onMounted(()=>{console.log('--onMounted--')}),
onBeforeUpdate(()=>{console.log('--onBeforeUpdate--')}),
onUpdated(()=>{console.log('--onUpdated--')}),
onBeforeUnmount(()=>{console.log('--onBeforeUnmount--')}),
onUnmounted(()=>{console.log('--onUnmounted--')})
return{}
},
什么是hook:本质是一个函数,把setup函数中使用的Composition AP进行了封装。
类似于vue2.x中的mixin。
自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂。
代码实例:实现点击鼠标获取xy位置的功能
新建userPoint.js文件
import {onMounted,onBeforeUnmount,reactive} from 'vue'
export default function savePoint() { //实现鼠标打点
let point = reactive({x:0,y:0})
function savePoint(event){
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX,event.pageY)
}
onMounted(()=>{window.addEventListener('click',savePoint)})
onBeforeUnmount(()=>{window.removeEventListener('click',savePoint)})
return point
}
自定义hook函数引用
import usePoint from "../hooks/usePoint";
setup(){
let point = usePoint()
return{point}
}
作用:创建一个ref对象,其value值指向另一个对象中的某个属性值
语法:const name =toRef(person,'name')
应用:要将响应式对象中的某个属性单独提供给外部使用时
扩展:toRefs 与toRef功能一致,但可以批量创建多个ref对象
语法:...toRefs(person)
代码
<h3>姓名:{{name}}</h3>
<h3>薪水:{{job.j1.salary}}</h3>
setup(){
let person = reactive({
name:'张三',
job:{j1:{salary:10}}
})
return{ ...toRefs(person),
}
}
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
shallowRef:只处理基本数据类型的响应式,不处理对象的响应式。
*如果有一个对象数据,结构比较深,但变化时只是外层属性变化,使用shallowReactive。
*如果有一个对象数据,后续功能不会修改该对象中的属性,而是用新的对象来替换,使用shallowRef。
let person = shallowReactive({//只考虑第一层数据
name:'张三',
age:18,
job:{
j1:{salary:10}
}
})
let num = shallowRef({
y:0 //处理对象类型无响应式
})
<h3>{{num.x}}</h3> //只能进行整体替换
<el-button @click="num = {x:33}">切换num</el-button>
readonly:让一个响应式数据变为只读的(深只读);
shallowReadonly:让一个响应式数据变为只读的(浅只读);
应用场景:不希望数据被修改时:
person = readonly(person) //person不会被改变
person = shallowReadonly(person) //person第一层数据不会被改变
作用:将一个由reactive生成的响应式对象转为普通对象;
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新;
const p = toRaw(person)
作用:标记一个对象,使其永远不会再成为响应式对象;
使用场景:
1.有些值不应被设置为响应式的,例如复杂的第三方类库等;
2.当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能;
function addCar() {
let car ={name:'奔驰',price:40}
person.car = markRaw(car)
}
作用:
1)用于自定义返回一个ref对象,可以显式地控制依赖追踪和触发响应,接受工厂函数;
2)两个参数分别是用于追踪的 track 与用于触发响应的 trigger,并返回带有 get 和 set 属性的对象;
注意:通过 customRef 返回的 ref 对象,和正常 ref 对象一样,通过 x.value 修改或读取值
实现防抖效果:
import {customRef} from 'vue'
setup(){//自定义一个ref:myRef
function myRef(value,delay){
let timer
return customRef((track,trigger)=>{
return{
get(){
track() //通知vue追踪value的变化
return value
},
set(newValue){
value = newValue
clearTimeout(timer)
timer = setTimeout(()=>{
trigger() //通知vue去重新解析模板
},delay)
}
}
})
}
let keyWord = myRef('hello',500) //使用自定义customRef
return{keyWord}
}
作用:实观祖组件与后代组件的跨级通信
套路:父姐件有一个provide选项来提供数据,子组件有一个inject 选项来开始使用这些数据
//祖组件中
import {reactive,toRefs,provide} from 'vue'
setup(){
let car = reactive({
name:'奔驰',
price:'40W'
})
provide('car',car)
return{ ...toRefs(car)}
}
//后代组件中
import {inject,toRefs} from "vue";
setup(){
let car = inject('car')
console.log('car---',car)
return{ ...toRefs(car)}
}
注:父子组件间传值还用props
父传子:
//父组件引用子组件
<box-card :card="card" :fromRouteName="fromRouteName" ></box-card>
//子组件接收
interface Props {
card: CardInfo,
fromRouteName:String
}
const props = withDefaults(defineProps<Props>(), { //defineProps是编译宏,不用引入
card: () =>
reactive({
url: '',
description: '',
type:'',
}),
fromRouteName:{
type:String
}
})
//子组件接受的数据在templete中使用
<img :src="card.url" />--{{card.type}}--{{fromRouteName}}
//子组件在script的使用
props.fromRouteName
props.card.url
isRef:检查一个值是否为ref对象;
isReactive:检查一个对象是否是reactive创建的响应式代理;
isReadonly:检查一个对象是否是readonly 创建的只读代理;
isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理;
import {isRef,isReactive,isReadonly,isProxy} from "vue";
isRef(sum) //true或false
isReactive(person)
isReadonly(sum)
isProxy(sum)
1)Options APl 存在的问题(Vue2.x)
使用传统OptionsAPl (配置API)中,新增或者修改一个需求,就需要分别在data,methods,computed里修改。
2)Composition API 的优势
能更加优雅的组织代码,函数。让相关功能的代码更加有序的组织在一起。
在Vue2中:组件必须有一个根标签
在Vue3中:组件可以没有根标签,内部会将多个标签包含在内置的Fragment虚拟元素中
好处:减少标签层级,减小内存占用
Teleport(传送)是一种能够将我们的组件html结构移动到指定位置的技术。
<teleport to="body"> //也可传给选择器
<div v-if="isshow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<el-button @click="isshow = false">关闭弹窗</el-button>
</div>
</div>
</teleport>
等待异步组件时渲染一些额外内容,用户体验更好
// import childSuspence from "./pages/SuspenseDemo/childSuspence";//静态引入
import {defineAsyncComponent} from 'vue'
const childSuspence = defineAsyncComponent(()=>import('./pages/SuspenseDemo/childSuspence'));//动态引入
使用Suspense包裹组件,并配置好default与fallback,Suspense的底层是slot
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<child-suspence></child-suspence>
</template>
<template v-slot:fallback>
<h3>加载中....</h3>
</template>
</Suspense>
</div>
Vue 2.x 有许多全局 API 和配置:
//注册全局组件
Vue.component('myButton',{
data:()=>({count:0}),
template:''
})
//注册全局指令
Vue.directive('focus',{
inserted:el => el.focus()
})
Vue3.0中对这些 APl 做出了调整:将全局的 API,即:Vue.xxx 调整到应用实例(app)上,如下表:
2.x全局API(Vue) | 3.x实例API(app) |
---|---|
Vue.confg.xxxx | app.config.xxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.minix | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
a:data选项应始终被声明为一个函数
b:过渡类名的修改
//Vue2.x写法
.v-enter,
.v-leave-to{opacity:0}
.v-leave,
.v-enter-to{opacity:1}
//Vue3.x写法
.v-enter-from,
.v-leave-to {opacity:0}
.v-leave-from,
.v-enter-to{opacity:1}
c:移除keyCode作为v-on 的修饰符,同时不再支持config.keyCodes
d:移除v-on.native修饰符
父组件中绑定事件
<my-component v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent" />
子组件中声明自定义事件
<script>
export default {emits:['close']}
</script>
e:移除过滤器filter
getCurrentInstance 只暴露给高阶使用场景,典型的比如在库中。强烈反对在应用的代码中使用 getCurrentInstance。请不要把它当作在组合式 API 中获取 this 的替代方案来使用。
import { getCurrentInstance } from 'vue'
const MyComponent = {
setup() {
// 获取当前组件的上下文,下面两种方式都能获取到组件的上下文。
const { ctx } = getCurrentInstance(); // 方式一,这种方式只能在开发环境下使用,生产环境下的ctx将访问不到
const { proxy } = getCurrentInstance(); // 方式二,此方法在开发环境以及生产环境下都能放到组件上下文对象(推荐)
}
}
getCurrentInstance 只能在 setup 或生命周期钩子中调用
如需在 setup 或生命周期钩子外使用,请先在 setup 中调用 getCurrentInstance() 获取该实例然后再使用。
const MyComponent = {
setup() {
const internalInstance = getCurrentInstance() // 有效
const id = useComponentId() // 有效
const handleClick = () => {
getCurrentInstance() // 无效
useComponentId() // 无效
internalInstance // 有效
}
onMounted(() => {
getCurrentInstance() // 有效
})
return () =>
h(
'button',
{
onClick: handleClick
},
`uid: ${id}`
)
}
}
// 在组合式函数中调用也可以正常执行
function useComponentId() {
return getCurrentInstance().uid
}