Vue3中ref为什么不能响应式更新?

嗨,你说的这个Vue3 中的ref和reactive的事情,我有点经验。
上周有个客人问我Vue3 的响应式系统怎么用,我就给他详细解释了一下。

首先,Vue3 的ref确实不是不能响应式更新,关键是要看你怎么用。
比如说,如果你用ref来包装一个对象或者数组,你要是想更新里面的属性,不能直接改,得替换整个对象或者数组。
我举个例子,比如你有个对象rowSelections,你用ref包起来,然后你想设置id属性,你这样写rowSelections.value.id=1 ;是不会触发更新的。
为什么?因为rowSelections.value的引用没变,你只是改了里面的id属性,ref是没察觉到的。

反过来,如果你用reactive来包装对象或数组,那更新属性就简单多了。
就像我刚才说的,你用reactive包一个对象,直接改属性,比如rowSelections.id=1 ;就能触发更新。
因为reactive会创建一个代理对象,它会跟踪对象内部所有属性的变化。

再说说ref的局限性。
如果你用ref包装对象,它只会跟踪整个对象的引用变化,如果你直接修改对象的内部属性,Vue是检测不到这个变化的。
这就跟reactive完全不一样了,reactive能深度追踪对象的属性变化。

对于解决方案,其实很简单。
如果你用ref,就老老实实通过.value赋值来更新基本数据类型。
比如count=ref(0),你想更新它,就写count.value=1 ;这样就能触发更新。

对于对象和数组,你最好用reactive。
这样,你直接操作属性,就能触发更新了。
比如state=reactive({list:[],user:{name:''}}),你想更新list或者user的name,直接写state.list.push(1 )或者state.user.name='John';都能触发更新。

当然,有时候你可能必须用ref来包装对象,那也没办法,你得替换整个对象。
就像refObj=ref({id:0}),你想更新id,你就得写refObj.value={id:1 };这样整个对象被替换了,Vue就能检测到变化了。

总结一下,ref适用于基本数据类型,reactive适用于对象和数组。
关键是要根据你的需求来选择。
搞错了,响应式更新就出问题。
我说的对吧?反正你看着办。
我还在想这个问题,也许以后还有更深的理解呢。

vue3框架如何给一个数组项赋值?

上周有个客户问我的确是这情况。
你搞懂了Vue3 里数组的操作就好办多了。

在setup函数里搞响应式数组,确实是这样搞的。
你先写: javascript const array = reactive([]);
这行代码关键,把空数组变成响应式的。
之后所有操作Vue都能监听到了。

你要加东西进去,就用push: javascript array.push({ name: '小明' });
你看,直接往里塞对象就行。
每次调用push,Vue就知道数组变了。

想清空?直接改length就行: javascript array.length = 0;
这招很狠,瞬间清空所有内容,性能也好。

不过要注意一点,如果你push的是非响应式对象,那里面属性变化Vue是监不到的。
比如你直接push一个普通对象{},那它自己内部改来改去,你组件里是看不出来变化的。
得用reactive创建对象再push: javascript array.push(reactive({ name: '小红' }));
这样内部修改也能触发更新了。

你用这个方法,基本能满足日常需求。
就是reactive用多了有点性能考虑,大数组频繁变动还是得看看。
我上次给某个大项目用这个,确实有点卡顿,后来改用ref+计算属性优化了下。

反正你看着办,这个方法是标准的。

vue中的$set的作用

说实话,$set这玩意儿在Vue2 里简直是救星,但用多了你就明白为啥Vue3 要搞个Proxy了。
我当年做项目时踩过坑,所以印象特别深。

比如有个场景,你拿用户信息,突然需要根据后端接口新增字段。
直接写user.info.newField = 'value'?在Vue2 里根本不管用,视图一点反应都没有。
我一开始还以为是代码写错了,最后发现得用this.$set(user.info, 'newField', 'value')才行。
这帮开发人员把user.info当普通对象处理,结果忘了Vue的响应式系统不是这么玩的。

有意思的是,数组操作也同理。
你有个购物车数组cart,后端返回新商品直接cart.push(item)?视图同样白屏。
我当时处理这个需求,把cart当普通数组用,结果用户加个商品页面就没反应了。
后来改用this.$set(cart, cart.length, item),或者更骚的操作cart.splice(-1 , 0, item)——虽然后者性能差点,但胜在直观。
还有更绝的,直接用this.$set(this.cart, 'length', this.cart.length + 1 ),把新元素塞到最后面,虽然现在看来有点中二。

但$set有个硬伤,就是不可配置属性会直接崩。
我有个组件需要处理受保护的属性,用Object.defineProperty把某些字段设为writable: false,结果用this.$set改值就抛出异常。
当时真是想破脑袋,最后只能改用user.info.newField.__proto__ = Object.assign({}, user.info.__proto__, {newField: 'newValue'})这种骚操作绕过。

不过话说回来,用$set最烦的是你得记住它只对已声明的响应式数据有效。
比如你直接操作根实例Vue.set(this, 'newProp', value)?完全白费功夫。
还有个坑是自动创建属性——如果你添加的属性原本不存在,$set会帮你新建,但如果你期望的是覆盖已有属性,它反而会创建新属性,当时我就因为这个bug改了三天代码。

到了Vue3 ,老实说我觉得$set快被淘汰了。
因为Proxy能自动拦截所有赋值操作,所以大部分场景直接写this.user.info.newField = 'value'就行。
但就我观察,用set方法的情况还是有的,比如处理嵌套响应式对象时,Vue3 的set方法能保持和Vue2 类似的错误处理机制。
我有个项目用了Vue3 后,重构代码时发现只有两个地方还用着$set,一个是处理不可配置属性,另一个是兼容某些第三方库。

所以说白了,$set是Vue2 的遗留产物,但理解它原理对现在用Vue3 的同志们也有帮助。
我最近面试的时候,面试官就问过我,虽然大部分场景用不着,但能说出原理的人还是能体现对Vue响应式系统的深刻理解。