Vue3-原始值的响应式方案ref

一、原始类型的值

原始类型的指的是: booleannumberstringsymbolundefindnull等类型的值.

一、初识ref

为什么vue3需要对原始值的响应式做单独处理?因为Javascript中的Proxy只能代理对象类型的数据, 如普通对象、数组、Set、Map等。 为了解决Proxy不能代理原始类型的问题,vue3使用对象类型包裹原始类型的方法,也就是提供一个ref函数,如下是ref函数的简单实现:

javascript 复制代码
function ref(val) {
  const wrapper = {
    value: val
  }
  return reactive(wrapper)
}

const count = ref(1)
count++

这就是使用ref创建的响应式数据时要加.vlaue访问的原因,因为在ref函数中使用了对象类型 包裹 原始类型,然后使用reactive将包裹对象变成响应式数据并返回。实际上vue3源码对ref的实现做了很多抽象和封装并对不同类型的数据做了严格的逻辑判断,想要更深一步的了解ref可自行阅读vue3 ref 相关源码,在此就不过多赘述了。

二、模板中自动脱ref

为了减轻开发者的心智负担,vue3在模板访问和设置ref时实现了自动脱ref,自动脱ref需要对包裹对象 添加唯一标识,这个唯一标识就是在包裹对象 上添加一个__v_isRef只读属性,它用来判断一个对象是否为ref。我们需要修改一下ref函数的实现:

javascript 复制代码
function ref(val) {
  const wrapper = {
    value: val
  }
  Object.definePrototype(wrapper, '__v_isRef',{
    value: ture
  })
  return reactive(wrapper)
}

Object.definePrototype为对象配置属性通常会用到三个描述符

  1. configurable 默认值为false 也就是不可配置
  2. writable 默认值为false 也就是不可写
  3. enumerable 默认值为false 也就是不可枚举 ,即for...in访问不到该属性

自动脱ref函数的简单实现

javascript 复制代码
function proxyRefs(objectWithRef) {
  return new Proxy(objectWithRef, {
  	get(target, key) {
      return unref(target)
    },
  	set(target, key, value, receiver) {
  	  const oldValue = target[key]
  	  if (isRef(oldValue) && !isRef(value)) {
	    oldValue.value = value
	    return true
	  } else {
	    return Reflect.set(target, key, value, receiver)
	  }
  	}
  })
}

function unref(ref) {
  return isRef(ref) ? ref.value : ref
}

function isRef(r) {
  return !!(r && r.__v_isRef === true)
}

三、解决响应式丢失问题

ref除用来为原始值提供响应式方案,还可以解决响应式丢失的问题,比如下面的一个例子:

javascript 复制代码
<template>
  <div>{{ count }}</div>
</template>

<script>
//
import { reactive } from 'vue'

export default {
  setup() {
    const proxy = reactive({ count: 1 })

    setTimeout(() => {
      proxy.count++
    }, 3000)

    return {
      ...proxy
    }
  }
}
</script>

运行上述代码,定时器3s后会修改响应式数据的值但模板中的数据并未更新,其根本原因出在...操作符上

javascript 复制代码
return {
  ...proxy
}

等价于:

javascript 复制代码
return {
  count: 1
}

返回的是一个普通对象,所以在模板中读取该对象的属性时不会与渲染函数建立联系,修改proxy的属性时自然也不会触发渲染函数的再次执行。故此toReftoRefs函数就应运而生,下面是toRef和toRefs函数的简单实现:

javascript 复制代码
function toRef(target, key) {
  const wrapper = {
    get value() {
	  return target[key]
	},
	set value(value) {
	  target[key] = value
	}
  }
  Object.definePrototype(wrapper, '__v_isRef',{
    value: ture
  })
  return wrapper
}

function toRefs(target) {
  const result = {}
  for (const key in target) {
    result[key] = toRef(target, key)	
  }
  return result
}

故此我们只要修改script中的一行代码就可以避免响应式的丢失了

javascript 复制代码
return {
  ...toRefs(proxy)
}
相关推荐
崔庆才丨静觅1 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax