为什么 Vue3 的 ref 让很多大佬操碎了心?


作者:瑞哥

来源:瑞哥聊前端

最近 Vue3 关于 ref-sugar 的提案引起了广泛的讨论:juejin.im/post/689417…[1]

<script setup>
import Foo from './Foo.vue'

// declaring a variable that compiles to a ref
ref: count = 1
const inc = () => {
  count++
}
// access the raw ref object by prefixing with $
console.log($count.value)
</script>

<template>
  <Foo :count="count" @click="inc" />
</template>

感兴趣的同学可以先阅读上面的讨论,本文不再重复讨论。提案目的是将 ref.value 的写法进一步简化,但因为修改了 js 语言本身的语义,引起了很多的争论。

本文希望用一种最保守的思路提供另一种心智负担可能更小的解决方案

1. 回顾 vue-composition-api-rfc

读写 ref 的操作比普通值的更冗余,因为需要访问 .value。尤大大对使用编译时的语法糖来解决这个问题非常谨慎,曾明确表示不默认提供此类支持。vue-composition-api-rfc.netlify.app/zh/#\%E5\%BC\%…[2]

对此我非常赞同。

但在 js 中必须要加 .value ,在模板中又不需要加 .value,这无疑造成了一定程度的混乱和割裂感。

2. 解决思路:转成对象 new String('foo') 再拦截

为什么会这样?究其根本,我们无法对基础数据类型值做拦截。行呀,那我们把基础数据类型转成对应的包装类实例,再进行拦截。例如:let str = 'foo' 改写成 let strObj = new String('foo'),此时 strObj 是对象,接下来我们尝试拦截,我写了一个库re-primitive,使用示例如下:

const { rePrimitive, watchEffect } = require('../dist/re-primitive.cjs')

// 用 rePrimitive 代替 ref; 并传入String的包装对象  new String('foo')
let proxy = rePrimitive(new String('foo'))
// let proxy = rePrimitive('foo')  // 内部做了装箱,可简写去掉 new String()
// rePrimitive 的作用是将对象设置成响应式,并增加 setValue() 修改数据的方法

watchEffect(() => {
  console.log('输出 proxy instanceof String:', proxy instanceof String) // true , 可以看出 proxy是String的实例,可以使用所有的String的原型方法
  console.log('输出 proxy.valueOf():' + proxy.valueOf())
  console.log('输出:proxy.substring(1): ' + proxy.substring(1))
})
console.log('==========')
proxy.setValue('bar') // 响应式修改数据,重新执行 effect 函数

// 输出结果

// 输出 proxy instanceof String:true
// 输出 proxy.valueOf():foo
// 输出:proxy.substring(1): oo
// ==========
// 输出 proxy instanceof String:true
// 输出 proxy.valueOf():bar
// 输出:proxy.substring(1): ar

  • rePrimitive: 相当于 reactive, ref。将基本数据类型设置成响应式,如果传递的是 new String('foo'), 则 proxy 仍然是 String 的实例;支持 String | Number | Boolean 三种类型,可简写去掉主动装箱。

let proxyStr = rePrimitive('foo') // 等价于 let proxyStr = rePrimitive(new String('foo'))
let proxyNum = rePrimitive(123) // 等价于 let proxyNum = rePrimitive(new Number(123))
let proxyBool = rePrimitive(false) // 等价于 let proxyBool = rePrimitive(new Boolean(false))

  • setValue: 响应式修改数据,重新执行 effect 函数

3. 总结

  • 取值用.valueOf() 。开发者需要时刻注意 proxy 是 String, Number, Boolean 对象,记得调用 .valueOf()方法才能取到实际值。配合 eslint 插件可帮助检查漏写.valueOf()方法

  • 修改用.setValue() 。这种解决方案完整保留 js 语言特性,仅仅只是增加了 .setValue() 方法,对开发者的心智负担可能略少于 ref

  • 实现源码仓库:https://github.com/ruige24601/re-primitive.git

参考资料

[1]

juejin.im/post/689417…: https://juejin.im/post/6894175515515551752

[2]

vue-composition-api-rfc.netlify.app/zh/#%E5%BC%…: https://vue-composition-api-rfc.netlify.app/zh/#%E5%BC%95%E5%85%A5-ref-%E7%9A%84%E5%BF%83%E6%99%BA%E8%B4%9F%E6%8B%85

最后

  1. 感谢阅读,欢迎分享给身边的朋友,

  2. 记得关注噢,黑叔带你飞!

亲,点这涨工资 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值