在体验Vue3之前,我们先来了解一下Vu3到底有哪些亮点之处。
总共有6大特点:
const name = ref(initValue)
name.value
{{name}}
Object.defineProperty()
的get
与set
进行设置和取值操作reactive
函数(其实就是proxy代理)const 代理对象(proxy) = reactive(源对象)
接收一个对象或数组,返回一个代理对象(proxy的实例对象,简称proxy对象)经过reactive处理的对象是不能使用解构语法的,因为会失去响应式特性,可以使用toRefs将reactive每个属性都转成ref响应式,这样就可以进行响应式处理。
reactive就是用于将对象数据转为响应式,与vue2的observable
类似,而ref用于获得单独或为基础数据类型转为响应式。
为什么在vue3中会有两个将数据转为响应式数据的api,我们来进行详细说明:
property.value
简而言之,reactive可以将对象数据转为响应式,可以将对象中的每个元素都转为ref响应式数据;而ref不仅可以将对象数据转为响应式,还可以处理基础数据类型(string、boolean等)。
之所以会有此差异,那时因为vue3中的响应式是基于proxy实现的,而对于proxy的target必须是引用数据类型,即存放在堆内存中通过指针进行引用的对象。为什么是代理的引用数据类型,那是因为简单数据类型每次赋值都是全新的数据,根本无法进行代理,因此难以实现简单数据类型的响应式。
如果我们想要获取简单数据类型的响应式,应该如何做呢?
vue3中也考虑到这点,可以通过ref进行简单数据类型的响应式处理。ref通过创建内部状态,将值挂到value上,因此ref生成的对象要通过.value
获取使用。而重写get/set获得的监听,同时对对象的处理也依赖了reactive的实现。
正是如此,ref既可以处理简单数据类型,又可以将引用数据类型进行响应式处理。在实践中,我们应该避免将reactive当作vue2的data在顶部将所有变量进行声明,而是应该结合具体的应用和逻辑进行就近声明。
class RefImpl<T> {
private _value: T
public readonly __v_isRef = true
constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
...
const convert = <T extends unknown>(val: T): T =>
isObject(val) ? reactive(val) : val
...
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持) Object.defineProperty(data,"count",{
get(){},
set(){}
})
存在问题:
vue3的响应式原理
Object.defineProperty()
的set和get属性实现响应式(数据劫持)Proxy
来实现响应式(数据劫持),并通过Reflect操作愿对象内部的数据.value
进行操作,模板读取数据时不需要使用.value
.value
,即可进行设置和读取import {computed} from "vue";
setup(){
const sum = computed(()=>{
return num1 + num2;
})
}
import {computed,reactive} from "vue"
setup(){
const person = reactive({
firstName:"wen",
lastName:"bo"
})
//简写
let fullName = computed(()=>{
return person.firstName + "-" + person.lastName;
});
//完整
let fullName = computed(()=>{
get(){
return person.firstName + "-" + person.lastName;
},
set(value){
const newArr = value.split("-");
person.firstName = nameArr[0];
person.lastName = nameArr[1];
}
})
}
let person = reactive({
name:"wenbo",
age:18,
job:{
job1:{
salary:20
}
}
})
// 情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log("sum变了",newValue,oldValue);
},{immediate:true})
// 情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log("sum或msg变了",newValue,oldValue);
})
/*情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
此时的deep不生效
*/
watch(person,(newValue,oldValue)=>{
console.log("perosn变了",newValue,oldValue);
},{immediate:true,deep:false})
//情况四:监视reactive所定义的一个响应式数据中的某个属性
watch(()=>person.age,(newValue,oldValue)=>{
console.log("person的age变化了",newValue,oldValue)
})
//情况五:监视reactive所定义的一个响应式数据中的某些属性
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log("person的name或age变化了",newValue,oldValue)
})
//特殊情况-监听的对象的属性还是个对象,此时deep生效,可以进行深度监听
watch(()=>person.job,(newValue,oldValue)=>{
console.log("person的job变化了",newValue,oldValue)
},{deep:true})
注意:
watch监听reactive定义的对象有五种情况
watch监听ref定义的响应式数据有两种情况:
.value
进行监听。如const num = ref(0)
,此时对num.value
监听的话相当于直接对num的值(0)进行监听了,想想0有啥变化的。.value
进行监听,因为ref对象.value
是借助reactive处理的响应式数据proxy。let person = reactive({
name:"wenbo",
age:18,
job:{
job1:{
salary:20
}
}
})
// warchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调
watchEffect(()=>{
const x = person.name
const y = person.job.job1.salary
console.log("watchEffect触发了")
})
<div>pageX:{{point.pageX}},pageY:{{point.pageY}}<div>
hooks文件 usePoint.js
import {reactive} from "vue"
const handleClickPoint = ()=>{
//实现鼠标“打点”相关数据
let point = reactive({
pageX:0,
pagey:0
})
//实现鼠标“打点”相关方法
const handlePoint = (event)=>{
point.pageX = event.pageX;
point.pageY = event.pageY;
console.log(event.pageX,event.pageY)
}
//实现鼠标打点的相关周期函数
onMounted(()=>{
window.addEventListener("click",handlePoint)
})
onBeforeUnmounted(()=>{
window.removeEventListener("click",handlePoint)
})
}
const name = toRef(person,"name")
toRefs(person)
<template>
<input type="text" v-model="str"/>
<h1>当前的值:{{str}}h1>
template>
<script>
import {ref, customRef} from "vue";
export default {
setup(){
// 自定义一个ref:myRef
const myRef = (value)=>{
return customRef((track,trigger)=>{
return{
get(){
console.log(`有人从myRef中读取了此数据${value}`);
track();
return value;
},
set(newValue){
console.log(`有人把myRef的数据改为了新数据${newValue}`);
value = newValue;
trigger();//通知vue去重新解析模板
}
}
});
}
const str = myRef("hello");
return{
str
}
}
}
script>
- 作用:实现祖孙组件间的通信
- 应用场景:父组件有一个provide选项提供数据,子组件有一个inject来获取使用数据
- isRef:检查一个值是否为ref对象
- isReactive:检查一个值是否为reactive创建的响应式代理
- isReadonly:检查一个对象是否是由readonly创建的只读代理
- isProxy:检查一个对象是否是由reactive或readonly创建的代理
其实在使用中可以发现,其实hook本质上就是进行抽取的函数,灵活性比较高,但是在涉及到异步逻辑时考虑不周全就会导致很多问题。hook是可以覆盖异步情况的,但是必HTML须在setup当中执行时返回有效对象不能被阻塞。
通常异步具有两种风格:
外部没有其它依赖,只是交付渲染的响应变量。对于此种情况,可以通过声明、对外暴露响应变量,在hook中使用异步修改的方式。
外部具有依赖,需要在使用侧进行加工。对于此种情形,可以通过对外暴露Promise的方式,使得外部获取到同步操作的能力。
由于setup处于生命周期的beforeCreate和created阶段之间,因此不能获取到this。当然我们可以通过setup的第二个参数context获取到类似与this的部分能力,但是对于想操作路由、vuex等能力则受到了限制,为此最新的router@4、vuex@4都提供了组合式的api。
由于vue2底层限制使得无法使用这些hook,为此可以通过引用实例的方式获取一定的操作能力,也可以通过getCurrentInstance
获得组件实例上挂载的对象。
虽然组合式api的响应式原理是通过Object.defineProperty
改写属性的,与vue的响应式原理相同,但是在具体实现方式上存在差异,因此在setup中与vue原生的响应式并不互通。正因此,导致即使拿到相应的实例也没有办法监听它们的响应式,只能通过在选项配置进行使用。
在vue2中:组件必须有一个根标签
在vue3中:组件可以没有根标签,内部会将多个标签包裹在Fragment虚拟元素中
好处:减少标签层级,减少内存占用
Teleport时倚重能够将组件html结构移动到指定位置的技术
语法:
main.vue
<div name="modal">div>
<Modal :isOpen="isOpen" @closeModal="closeModal"/>
<button @click="openModal" >open modalbutton>
modal.vue
<teleport to='#modal'>
<div class="modal-box" v-if="isOpen">
<slot>this is a modalslot>
<button @click="handleCloseModal">closebutton>
div>
teleport>
等待异步组件时渲染一些额外内容,让应用有更好的用户体验。
使用步骤:
异步引入组件
使用suspense
包裹组件,并配置好default
与fallback
。
import {defineAsyncComponent} from "vue"
const child =defineAsyncComponent(()=>import("./components/child.vue"))
我是前端小菜鸡,感谢大家的阅读,我将继续和大家分享更多优秀的文章,此文参考了大量书籍和文章,如果有错误和纰漏,希望能给予指正。
更多最新文章敬请关注笔者掘金账号一川萤火和公众号前端万有引力。