ref reactive 家族
- ref是通过一个中间对象RefImpl持有数据,并通过重写它的set和get方法实现数据劫持的,本质上依旧是通过Object.defineProperty 对RefImpl的value属性进行劫持。
- reactive则是通过Proxy进行劫持的。Proxy无法对基本数据类型进行操作,进而导致reactive在面对基本数据类型时的束手无策。
ref 对基本数据类型,使用Object.defineProperty 进行数据劫持监听,对对象数据使用value包装proxy对象进行监听劫持数据
reactive 监听的对象数据需要使用Object.assign 的方式进行复制,或者对子属性直接赋值
reactive弊端:赋值一一对应赋值页面可以响应式,页面更新
而整体赋值页面会渲染不上,只有控制台会有变化,而页面中无法更新
如果直接重新赋值reactive变量的话,变量会失去响应式。
reactive整体赋值解决方法
方法一 创建这个arr[]对象可以是空对象!!
使用****Object.assign(原数据, 新数据)、
对象中添加多个新属性,则通过 Object.assign(原数据, 新数据) 创建新对象
Object.assign(m3, obj)
ref的value无论对象还是基本数据类型,重新赋值都能实现响应式,所以一般推荐ref 监听对象数据,可以直接赋值。
总结
ref和reactive是vue3组合式api中最重要的响应式api
ref是用来处理基本数据类型,reactive是用来处理复杂的数据类型
如果用ref处理复杂的数据类型,那么内部会自动将复杂的数据类型转为reactive代理对象的形式
ref内部:通过value属性添加getter,setter来实现数据的响应式
reactive内部:通过es6中的proxy来实现对对象内部所有的数据进行劫持
注意:ref定义的数据修改数据的时候需要加上.value,reactive不需要
isRef ---- isReactive
isRef 判断是不是一个ref对象
isReactive检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
let message = "hello world"
let message2 = ref("hello world")
console.log(isRef(message )); //false
console.log(isRef(message2 )); //true
shallowRef ----- shallowReactive
创建一个跟踪自身 .value 变化的 ref,是浅层响应式数据。 shallowReactive用于创建一个浅层响应式对象。与 reactive 类似,shallowReactive 只会追踪对象或数组的第一层属性变化,而不会追踪深层嵌套属性的变化。
性能优势
对于只需要监听顶层属性变化的场景,shallowRef 和 shallowReactive 可以减少 Vue 内部的追踪操作,从而提升性能,特别是当数据结构非常复杂或庞大时。
响应性丢失的风险
如果使用了 shallowRef 和 shallowReactive,嵌套对象的属性在直接修改时不会触发响应式更新,这可能导致一些视图更新的逻辑失效,使用时需要明确知道这一点。
import {shallowRef } from "vue"
let message = shallowRef({
res:"hello world",
})
const change= () => {
message.value.res = "change hello world" //不会进行监听修改
message.value= {
res:"change hello world",//会进行监听修改
}
}
triggerRef
强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。
意思是,在可以将浅层的ref,深层次的更新。
import {shallowRef, triggerRef } from "vue"
let message = shallowRef({
res:"hello world",
})
const change= () => {
message.value.res = "change hello world" //不会进行监听修改
triggerRef(message)//会被监听
}
toRef以及toRefs
作用:创建一个ref对象,其value值指向另一个对象中的某个属性。
语法:const name = toRef(obj,'name')
应用:要将响应式对象中的某个属性单独提供给外部使用时
拓展:toRefs与toRef功能一致,但可以批量创建多个ref对象,语法:toRefs(obj)
大致用处解构一个对象,解构出来依然是响应式的,不然 const name = obj.name 这样解构不是响应式的
toRaw
将响应式对象转换为普通对象:toRaw函数的主要作用是将响应式对象转换为其原始的非响应式对象。
避免不必要的页面更新:在某些情况下,开发者可能只需要临时访问响应式对象的值,而不希望触发页面的更新。这时,使用toRaw函数可以避免这种情况。
性能优化:在处理大量数据或复杂数据结构时,避免不必要的响应式转换可以提高应用的性能。
使用场景
临时读取响应式对象的值:当你需要读取响应式对象的值,但不希望触发页面更新时。
传递数据给第三方库:当需要将数据传递给不支持响应式系统的第三方库时,可以使用toRaw函数将响应式对象转换为普通对象。
性能敏感的操作:在处理大量数据或进行性能敏感的操作时,避免响应式系统的开销。
const person = reactive({
name: '张三',
age: 25,
});
function showRawPerson() {
const rawPerson = toRaw(person);
console.log(rawPerson); // 输出原始对象
rawPerson.age++; // 修改原始对象的年龄,页面不会更新
}
markRaw
markRaw的作用是阻止对象被转换为响应式
import { ref, markRaw } from 'vue';
const someObject = { name: '天天鸭' };
const rawObject = markRaw(someObject);
const stateRef = ref(rawObject);
// ref 本身是响应式的,但 rawObject 不会被转换为响应式
stateRef.value.name = '天天鹅'; // 这不会触发响应式更新
应用场景
使用大型第三方库对象
在使用大型第三方库(如图表库、地图库)时,可以使用markRaw()避免这些对象被Vue的响应式系统追踪,从而提高性能。
import { markRaw } from 'vue';
import * as echarts from 'echarts';
const chartInstance = markRaw(echarts.init(document.getElementById('chart')));
静态配置数据
import { markRaw } from 'vue';
const config = markRaw({
apiEndpoint: 'https://api.example.com',
timeout: 5000
});
readonly ## isReadonly ## shallowReadonly
readonly函数创建一个只读的响应式对象
const state = readonly(reactive({
count: 1,
name: 'Tom'
}))
console.log(state.count)// 1
console.log(state.name)// Tom
isProxy
检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。
Vue3中用于创建响应式对象的三个函数:
reactive、readonly和shallowReactive。
reactive函数用于创建深层响应式对象,
shallowReactive函数用于创建浅层响应式对象,
readonly函数用于创建深层只读响应式对象,
shallowReadonly函数用于创建浅层只读响应式对象。
这些函数可以帮助我们快速创建响应式数据,实现数据的自动更新。
isProxy,isReadonly,isReactive判断是否是相应是对象
isProxy 检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。
isReadonly 检查传入的值是否为 readonly() 和 shallowReadonly() 创建的只读对象。
isReactive 检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
customRef
了解即可: 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
// let keyword = ref('hello') //使用Vue准备好的内置ref
//自定义一个myRef
function myRef(value,delay){
let timer
//通过customRef去实现自定义
return customRef((track,trigger)=>{
return{
get(){
track() //告诉Vue这个value值是需要被“追踪”的
return value
},
set(newValue){
clearTimeout(timer)
timer = setTimeout(()=>{
value = newValue
trigger() //告诉Vue去更新界面
},delay)
}
}
})
}
provide 与 inject
provide 与 inject函数 只能用在由“祖组件” -> “子组件”传递参数,不能由子组件 -> 祖组件,且祖组件传递给子组件的参数只能使用,子组件不能修改传递过来的值,否则报错。
//父级
let car = reactive({name:'奔驰',price:'40万'})
provide('car',car)
//子级
const car = inject('car')
在 Vue 2 中,provide 和 inject 不是响应式的。这意味着,如果在父组件中通过 provide 提供的数据发生变化,注入到子组件中的数据不会自动更新。因此,开发者需要手动管理这些数据的变化,以确保子组件能够获取到最新的值。
1.方法一:传递的参数用一个方法返回
2.方法二:把需要传递的参数定义成一个对象
在 Vue 3 中,provide 和 inject 被重新设计,以利用新的响应式系统。现在,通过 provide 提供的数据是响应式的,这意味着当数据变化时,注入到子组件中的数据也会自动更新。这是通过 Vue 3 的 Composition API 实现的,其中 provide 和 inject 与 ref 或 reactive 结合使用,以确保数据的响应性。
computed
- 选项式写法 支持一个对象传入get函数和set函数自定义操作
const name = computed<string>({
get() {
return firstName.value + '-' + lastName.value
},
// 写入值
set(newVal) {
console.log(newVal); // 李-四
[firstName.value, lastName.value] = newVal.split('-') // 解构赋值
}
})
const changeName = () => {
name.value = '李-四'
}
2.函数式写法:只能支持一个getter函数,不允许修改值
const name = computed(() => {
return firstName.value + '-' + lastName.value
})
watch ---- watchEffect
watch(count,(newVal,oldVal) => {
console.log('值改变了',newVal,oldVal)
})
watch(() => obj.brand,() => {
console.log('监听的obj.brand.name改变了')
},{
deep:true,
immediate:true,
})
watchEffect 也是一个帧听器,是一个副作用函数。 它会监听引用数据类型的所有属性,不需要具体到某个属性,一旦运行就会立即监听,组件卸载的时候会停止监听。
watchEffect(() => {
console.log('name:',obj.name)
})
watch(
[r, s, t],
([newR, newS, newT], [oldR, oldS, oldT]) => {
console.log([newR, newS, newT], [oldR, oldS, oldT]);
}
);
//停止监听
const stop = watchEffect(() => {
console.log('name:',obj.name)
})
const stopWatchEffect = () => {
console.log('停止监听')
stop();
}
vue3内的宏
defineProps() 、defineEmits()、defineExpose()都是宏,它们使用形式上和函数调用差不多,区别是,第一,这些宏不需要通过模块导入;第二,这些宏会在编译期被编译成符合JavaScript原生语法的代码。
实现原理<script setup/>
<script setup/>本质是setup()函数的语法糖,vue-loader在编译期间会把<script setup/>内的代码编译成setup()函数,把defineExpose()内指定的变量编译为setup()函数的返回值。
虚拟DOM和diff算法
虚拟DOM其实就是相对于浏览器的真实DOM所渲染出来的一个用来描述真实DOM结构的JS对象
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文 档当中 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较(diff),记录两棵树差异 把第二棵树所记录的差异应用到第一棵树所构建的真正的DOM树上(patch),视图就更新了