vue3

87 阅读9分钟

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

  1. 选项式写法 支持一个对象传入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),视图就更新了