Reactive 与 Ref

ref和reactive是Vue.js中用于创建响应式数据的两个重要概念,它们各自具有不同的应用场景。正常情况下ref和reactive都具有深层响应性。

Reactive

介绍

reactive() 将使对象本身具有响应性,其行为就和普通对象一样,不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。

reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的,且只有代理对象是响应式的,更改原对象不会触发更新。且在同一个对象调用 reactive() 会返回相同的代理,在一个代理上调用 reactive() 会返回它自己。

const raw = {}
const proxy = reactive(raw)
// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

Reactive简单复现

  • 接受一个参数,判断这个参数是否是对象
  • 创建拦截器对象handler,设置get/set/deleteProperty
  • 返回Proxy对象
发布订阅模式
let activeEffect = null
let targetMap = new WeakMap()
export function effect(callback) {
    activeEffect = callback
    console.log('start')
    callback()// 访问响应式对象属性,去收集依赖
    console.log('end')
    activeEffect = null
}
// 搜集依赖(订阅)
export function track(target, key) {
    console.log(key, '搜集依赖')
    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)
}
// 触发更新(发布)
export function trigger (target, key) {
    const depsMap = targetMap.get(target)
    if(!depsMap) return
    const dep = depsMap.get(key)
    if (dep) {
        // 找到集合后,执行它里面的每一个effect函数
        dep.forEach(effect => {
            effect()
        })
    }
}
Reactive
const isObject = obj => obj != null && typeof obj === 'object'
const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key)

export function reactive(target) {
    // 判断传递的参数是否是Obj类型 不是则直接返回 是的返回代理对象
    if(!isObject(target)) return
    const handler = {
        get(target, key, receiver) {
            // 收集依赖
            track(target, key)
            console.log("获取" + key + "值")
            let res = Reflect.get(target, key, receiver)
            // 递归调用 
            return isObject(res) ? reactive(res) : res 
        },
        set(target, key, value, receiver) {
            let res = true
            const odlValue = Reflect.get(target, key, receiver)
            // 新增 != 旧值触发更新
            if (odlValue !== value) {
                res = Reflect.set(target, key, value, receiver)
                console.log("设置" + key + value)
                // 触发更新
                trigger(target, key)
            }
            return res 
        },
        deleteProperty(target, key) {
            const hasKey = hasOwn(target, key)
            const result = Reflect.deleteProperty(target, key)
            // 判断是否有key 且删除成功
            if (hasKey && result) {
                console.log("删除" + key + "值")
                // 触发更新
                trigger(target, key)
            }
            return result
        }
    }
    return new Proxy(target, handler)
}
html
<html>
    <body>
        <script type="module">
            import { reactive, effect } from './js/reactive.js'
            let test = reactive({
                name: '张三', 
                request: { code: '200' }, 
                price: 500,
                count: 5
            })
            let total
           effect(() => {
                total = test.price * test.count
            })
            console.log('total'+total)

            test.price = 4000
            console.log('total'+total)

            test.count = 1
            console.log('total'+total)
        </script>
    </body>
</html>
上述代码的数据模型

在这里插入图片描述
在这里插入图片描述

Ref

介绍

ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回,但在模板中使用ref时,不需要附加.value,因为ref在模板中会自动解包

<script setup> 
//在组件模板中访问 ref,需要在组件的 `setup()` 函数中声明并返回它们
import { ref } from 'vue'
const count = ref(0)
function increment() {
  // 在 JavaScript 中需要 .value
  count.value++
}
</script>
<template>
	<!-- 模板中使用 -->
  <button @click="increment">
    {{ count }} 
  </button>
</template>

Vue3有了Reactive 创建响应式变量, 为什么还要有Ref?

在标准的JavaScript中,检测普通变量的访问或修改是行不通的。ES6新增的Proxy也无法绑定原始值,但Vue3利用gettersetter方法来拦截对象属性的getset操作。

另一方面,ref不同于普通变量,它可以做到跟reactive相同的将响应式数据传递给函数,并保留对最新值和响应式连接的访问。

Ref可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map

Ref简单复现

  • 判断接收的参数类型

    • Ref创建的对象: 直接返回
    • 普通对象: 内部调用reactive来创建响应式代理
    • 基本数据类型(如字符串、数字、布尔值等): 创建一个只有value属性的响应式对象
  • 创建ref对象并且只有value属性,且这个value属性具有getset

Ref
export function ref(raw) {
    // 判断 raw 是否是ref 创建的对象,如果是的话直接返回
    if (isObject(raw) && raw.__v_isRef) return 
    // 如果raw 是对象的话,调用 reactive转换为响应式对象 否则直接返回row本身
    let value = isObject(raw) ? reactive(raw) : raw
    const r = {
        __v_isRef: true,
        get value() {
            // 收集依赖
            console.log('get')
            track(r, 'value')
            return value
        },
        set value(newValue) {
            console.log('设置' + newValue)
            if (newValue !== value) {
                raw = newValue
                value = isObject(raw) ? reactive(raw) : raw
                trigger(r, 'value')
            }
        }
    }
    return r
}
html
<html>
    <body>
        <script type="module">
            import { effect, ref } from './js/reactive.js'
            const price = ref(500)
            const count = ref(1)
            let total = 0
            effect(() => {
                total = price.value * count.value
            })
            console.log('total'+total)
            price.value = 4000
            console.log('total'+total)
            count.value = 1
            console.log('total'+total)
        </script>
    </body>
</html>

在这里插入图片描述

Reactive 与 Ref 的不同

Reactive

  • 有限的值类型: 只能对象转换成响应式对象
  • 不能直接替换整个对象: 直接替换整个对象会使得前一个响应性连接丢失。
let state = reactive({ count: 0 })
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })
  • 对解构操作不友好:将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接。
const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收到的是一个普通的数字,并且无法追踪 state.count 的变化
// 因此我们必须传入整个对象以保持响应性
callSomeFunction(state.count)

Ref

  • 任何类型的值: 基本数据类型(如字符串、数字、布尔值等),还包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构。
  • 能直接替换整个对象:并不会丢失响应式,但在替换过程中需要确保新的对象原始对象具有相同的响应式属性。缺少某个属性或者属性类型不匹配,可能会导致响应式失效或数据更新不正确。
const objectRef = ref({ count: 0, name: 'Alice' }); 
objectRef.value = { count: 1, name: 'Bob' };
  • 解构操作友好

总而言之,如果一个对象中的成员非常多的时候,使用ref书写并不方便,因为总要带着value属性,如果只有一个响应式数据时候,使用ref会比较方便。但reactive存在上述限,因此 使用ref() 作为声明响应式状态的主要 API。

Vue3响应式基础官方文档:https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html#limitations-of-reactive
哔哩哔哩视频:https://b23.tv/8iUZJKr

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值