​Vue2,3响应式原理,ref和reactive,toRef和toRefs,shallowRef和shallowRefs

本文详细比较了Vue3中的ref和reactive与Vue2的区别,重点讲解了响应式原理、依赖收集、类型推导、ref的使用以及如何通过Proxy、track和effect实现自动更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

与Vue2区别

使用

Vue2 :data(){ return { data} }

Vue3 : ref (initVal)和 reactive(initObj)

Vue2: Observer标记响应式+监听,Dep管理Watcher 依赖

Vue3:track 收集依赖,trigger 触发更新【WeakMap,Map,Set 来实现】 

类型推导:TypeScript

vue2:文档注释来指定类型type:

vue3:script setup lang脚本语言="ts"

响应式ref

ref原理:reactive({value:initVal})实现ref

ref+effec实现computed

ref(推荐)和reactive:深层次

reactive重新分配/传参时会丢失响应性:标记的是地址,而非值

解决:Object.assign(target, ...sources)返回修改后同地址的obj

单个响应引用:toRef(原始响应数据,属性)修改引用会影响原始数据

多个响应引用:toRefs解构赋值 的toRef(解构并保持响应式)

shallowRef()和shallowReactive():仅在根层次,不深层次

手动收集依赖+通知更新

effect():更改数据后执行,更新依赖该数据的数据(依赖)

track()收集依赖的effect()放进dep(set去重)

更新时触发trigger函数通知dep里所有effect()执行

当依赖的为object类型:WeakMap存obj,Map存obj.props

proxy:自动收集依赖+通知更新

new Proxy(target, handler)

Reflect

Reflect.get(target, propertyKey[, receiver])函数调用方式从对象中读值

reactive(被依赖的数据){return proxy}

vue3新增全局变量activeEffect存储当前执行的effect

track不必再判断obj和obj.key,直接dep存储当前activeEffect


Vue2区别

使用

Vue2 :data(){ return { data} }

Vue3 : ref (initVal)reactive(initObj)

<script setup>
  import { ref, reactive, toRefs } from "vue"
  const name = ref('参宿')
  const state = reactive({
    name: 'Jerry',
    sex: '男'
  })
</script>

Vue2: Observer标记响应式+监听Dep管理Watcher 依赖

Vue3:track 收集依赖trigger 触发更新【WeakMap,Map,Set 来实现 

类型推导:TypeScript

由于Proxy是基于原生的Proxy对象实现的,所以可以更好地支持TypeScript等静态类型检查工具,提供更准确的类型推导和代码提示。

vue2:文档注释来指定类型type:

<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  props: {
    message: {
      type: String, // 指定属性的类型
      required: true, // 属性是否必须
      default: 'Hello, Vue 2', // 默认值
    },
  },
};
</script>

vue3:script setup lang脚本语言="ts"

<script setup lang="ts">

</script>

响应式ref

ref原理:reactive({value:initVal})实现ref

function ref (initValue) {
    return reactive({
        value: initValue
    })
}

let num = ref(5)//num就会成为一个响应式的数据
console.log(num.value) // 5

ref+effec实现computed

effect():更改数据后执行,更新依赖该数据的数据(依赖),详情可见下文

function computed(fn) {
    const result = ref()
    effect(() => result.value = fn()) // 执行computed传入函数
    return result
}


let num1 = ref(5)
let num2 = ref(8)
let sum1 = computed(() => num1.value * num2.value)
let sum2 = computed(() => sum1.value * 10)
//初始值
console.log(sum1.value) // 40
console.log(sum2.value) // 400
//响应式顺序:
//num1->改变了effect中依赖num1->执行effect传入的函数
//->改变了响应式的result->sum1在初始化时已经被赋予了响应式的result,所以sum1改变
num1.value = 10

console.log(sum1.value) // 80
console.log(sum2.value) // 800

num2.value = 16

console.log(sum1.value) // 160
console.log(sum2.value) // 1600

ref(推荐)和reactive:深层次

reactiveref
❌只支持引用数据类型✅支持基本数据类型+引用数据类型
✅在 <script> 和 <template> 中无差别使用❌在 <script> 和 <template> 使用方式不同(script中要.value)
❌重新分配/传参时会丢失响应性:改变地址✅重新分配/传参时不会失去响应:改变对象属性value
解构时会丢失响应性,需使用toRefs

reactive重新分配/传参时会丢失响应性:标记的是地址,而非值

会认为在重新赋值一个新的普通对象

解决:Object.assign(target, ...sources)返回修改后同地址的obj

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// Expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget === target);
// Expected output: true

let state = reactive({ count: 0 })
// state =  {count:1}   state失去响应
state = Object.assign(state , {count:1})

单个响应引用:toRef(原始响应数据,属性)修改引用会影响原始数据

const state = reactive({
  foo: 1,
  bar: 2
})

// a two-way ref that syncs with the original property
const fooRef = toRef(state, 'foo')

// mutating the ref updates the original
fooRef.value++
console.log(state.foo) // 2

多个响应引用:toRefs解构赋值 的toRef(解构并保持响应式)

 // 使用toRefs解构
  const {name, sex} = toRefs(state)
  // template可直接使用{{name}}、{{sex}}

shallowRef()和shallowReactive():仅在根层次,不深层次

手动收集依赖+通知更新

effect():更改数据后执行,更新依赖该数据的数据(依赖)

let name = '林三心', age = 22, money = 20
let myself = '', ohtherMyself = ''
const effect1 = () => myself = `${name}今年${age}岁,存款${money}元`
const effect2 = () => ohtherMyself = `${age}岁的${name}居然有${money}元`

effect1() // 先执行一次
effect2() // 先执行一次
console.log(myself) // 林三心今年22岁,存款20元
console.log(ohtherMyself) // 22岁的林三心居然有20元
money = 300

effect1() // 再执行一次
effect2() // 再执行一次

console.log(myself) // 林三心今年22岁,存款300元
console.log(ohtherMyself) // 22岁的林三心居然有300元

track()收集依赖的effect()放进dep(set去重)

更新时触发trigger函数通知dep里所有effect()执行

let name = '林三心', age = 22, money = 20
let myself = '', ohtherMyself = ''
const effect1 = () => myself = `${name}今年${age}岁,存款${money}元`
const effect2 = () => ohtherMyself = `${age}岁的${name}居然有${money}元`

const dep = new Set()
function track () {
    dep.add(effect1)
    dep.add(effect2)
}
function trigger() {
    dep.forEach(effect => effect())
}
track() //收集依赖
effect1() // 先执行一次
effect2() // 先执行一次
console.log(myself) // 林三心今年22岁,存款20元
console.log(ohtherMyself) // 22岁的林三心居然有20元
money = 300

trigger() // 通知变量myself和otherMyself进行更新

console.log(myself) // 林三心今年22岁,存款300元
console.log(ohtherMyself) // 22岁的林三心居然有300元

当依赖的为object类型:WeakMap存obj,Map存obj.props

const targetMap = new WeakMap()
function track(target, key) {
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        targetMap.set(target, depsMap = new Map())
    }

    let dep = depsMap.get(key)
    if (!dep) {
        depsMap.set(key, dep = new Set())
    }

...
}
function trigger(target, key) {
    let depsMap = targetMap.get(target)
    if (depsMap) {
        const dep = depsMap.get(key)
        if (dep) {
            dep.forEach(effect => effect())
        }
    }
}

proxy:自动收集依赖+通知更新

new Proxy(target, handler)

target

包装target (对象/数组/函数甚/proxy对象)

handler

被代理对象上的自定义行为(定义一组处理函数(例如get、set)的对象)

target:被代理者
prop:被代理者的属性
receiver:代理者 ,Proxy 或者继承 Proxy 的对象

const person = { name: '林三心', age: 22 }

const proxyPerson = new Proxy(person, {
    get(target, key, receiver) {
        return target[key]
    },
    set(target, key, value, receiver) {
        target[key] = value
    }
})

console.log(proxyPerson.name) // 林三心

proxyPerson.name = 'sunshine_lin'

console.log(proxyPerson.name) // sunshine_lin

Reflect

Proxy和Reflect的方法都是一一对应的,在Proxy里使用Reflect会提高语义化

  • Proxy的get对应Reflect.get
  • Proxy的set对应Reflect.set

属性访问方法

A.属性访问器(访问):obj.key,obj[key]

B.函数调用:mp.get(key)

Reflect.get(target, propertyKey[, receiver])函数调用方式从对象中读值

receiver

如果target对象中指定了getterreceiver则为getter调用时的this

该方法会拦截目标对象的以下操作:

  • 访问属性:proxy[foo] 和 proxy.bar
  • 访问原型链上的属性:Object.create(proxy)[foo]
  • Reflect.get()
const person = { name: '林三心', age: 22 }

const proxyPerson = new Proxy(person, {
    get(target, key, receiver) {
        return Reflect.get(receiver, key) // 相当于 receiver[key]
    },
    set(target, key, value, receiver) {
        Reflect.set(receiver, key, value) // 相当于 receiver[key] = value
    }
})

console.log(proxyPerson.name)

proxyPerson.name = 'sunshine_lin' 
// 会直接报错,栈内存溢出 Maximum call stack size exceeded

reactive(被依赖的数据){return proxy}

function reactive(target) {
    const handler = {
        get(target, key, receiver) {
            track(receiver, key) // 访问时收集依赖
            return Reflect.get(target, key, receiver)
        },
        set(target, key, value, receiver) {
            Reflect.set(target, key, value, receiver)
            trigger(receiver, key) // 设值时自动通知更新
        }
    }

    return new Proxy(target, handler)
}

const person = reactive({ name: '林三心', age: 22 }) // 传入reactive
const animal = reactive({ type: 'dog', height: 50 }) // 传入reactive

...

vue3新增全局变量activeEffect存储当前执行的effect

track不必再判断obj和obj.key,直接dep存储当前activeEffect

let activeEffect = null
function effect(fn) {
    activeEffect = fn
    activeEffect()
    activeEffect = null // 执行后立马变成null
}
function track(target, key) {
    // 如果此时activeEffect为null则不执行下面
    // 这里判断是为了避免例如console.log(person.name)而触发track
    if (!activeEffect) return
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        targetMap.set(target, depsMap = new Map())
    }

    let dep = depsMap.get(key)
    if (!dep) {
        depsMap.set(key, dep = new Set())
    }
    dep.add(activeEffect) // 把此时的activeEffect添加进去
}

// 每个effect函数改成这么执行
effect(effectNameStr1)
effect(effectNameStr2)
effect(effectAgeStr1)
effect(effectAgeStr2)
effect(effectTypeStr1)
effect(effectTypeStr2)
effect(effectHeightStr1)
effect(effectHeightStr2)

林三心画了8张图,最通俗易懂的Vue3响应式核心原理解析 - 掘金

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值