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)
}
相关推荐
Coder_Boy_几秒前
前端和后端软件系统联调经典问题汇总
java·前端·驱动开发·微服务·状态模式
PieroPC7 分钟前
NiceGUI .classes() 完整列表教程
前端
月巴月巴白勺合鸟月半7 分钟前
一个医学编码的服务
服务器·前端·javascript
ycgg12 分钟前
深入理解 DOM 的 dispatchEvent API
前端
方也_arkling13 分钟前
【JS】定时器的使用(点击开始计时,再次点击停止计时)
开发语言·前端·javascript
JS_GGbond14 分钟前
Vue原型链:让你的组件继承“超能力”
前端·vue.js
乆夨(jiuze)16 分钟前
不是所有的链式调用,都是Promise函数,Promise 规范及其衍生的 Promise/A+ 规范
前端·javascript·vue.js
锦瑟弦音23 分钟前
跑酷游戏开发笔记3 && 游戏开始场景 cocos 3.8.7
javascript·笔记·游戏
ttod_qzstudio31 分钟前
Vue 3 的魔法:用 v-bind() 让 CSS 爱上 TypeScript 常量
css·vue.js·typescript
MoonBit月兔31 分钟前
海外开发者实践分享:用 MoonBit 开发 SQLC 插件(其二)
开发语言·javascript·数据库·redis·mysql·moonbit