Code前端首页关于Code前端联系我们

Vue3响应式到底和Vue2比有啥不一样?新手能快速上手吗?怎么避开常见坑?

terry 2小时前 阅读数 29 #Vue

很多刚转Vue3或者还在观望的前端小伙伴,估计刷教程刷到一半就被“Proxy”“Reflect”这些词绕晕,心里满是问号:老版本的Object.defineProperty明明用着也还行,怎么非要换个技术栈升级响应式的底层逻辑?是不是新手更难搞了?写代码的时候遇到ref和reactive傻傻分不清楚,会不会经常踩雷?别慌,今天咱们就用大白话唠明白这些问题,从基础认知到避坑指南,全都是日常开发摸爬滚打出来的干货。

为啥Vue3要换掉Vue2的响应式核心?

先别管Proxy和Object.defineProperty的具体代码差别,说个大家开发中能直接摸到的好处——你有没有在Vue2里遇到过「明明改了数组长度或者深层对象属性,但页面就是不刷新的情况?

// Vue2里常踩的坑
this.list[0] = '新内容';
this.list.length = 0;
this.userInfo.address.street = '更新后的街道';

第一个数组改索引,第二个数组重置长度,Vue2都得用特定的$set、$delete或者数组变异方法才能触发响应;第三个深层对象嵌套太深,要是初始化时没定义address或者street的getter/setter,Vue2也没办法直接监听新增的属性,而Vue3用了Proxy之后,这些场景直接“一竿子解决”——不管你是改索引、重置长度、新增/删除深层对象属性,都能自动捕获到变化更新页面,根本不用记那些额外的API。

底层原理上,Object.defineProperty和Proxy到底差在哪?

简单说,Object.defineProperty就像给对象的“每个口袋拉链”——你得提前把每个要监听的口袋(属性)都装个拉链(getter/setter),要是哪天偷偷塞个新口袋或者剪个新口袋拉链,拉链是不会自动装上的,得手动操作,而Proxy呢?它是给整个对象(或者数组)套了个“智能监控套”,不管你摸口袋、塞口袋、剪口袋,甚至把口袋套套再套口袋,监控套都能第一时间知道你做了啥,然后通知页面更新。

哦对了,Object.defineProperty只能给单个属性监听,要是你的对象有1000个属性,就得循环1000次给每个装拉链,性能自然会下降;Proxy直接监听整个对象,初始化速度会快很多,尤其是大对象多的项目,这个优势就更明显了。

新手入门Vue3响应式,先搞懂ref和reactive这对“黄金搭档”就行?

没错,新手不用一开始就死磕Proxy和Reflect的源码细节,先把日常开发用得最多的ref和reactive搞明白、用顺手,响应式这关就算过了80%。

ref是什么?什么时候用ref?

你可以把ref理解成“一个带响应式开关的盒子”,不管盒子里装的是基本类型(数字、字符串、布尔值、undefined、null、Symbol),还是引用类型(对象、数组、函数),它都能把这个东西包起来,让它变成响应式的。

那什么时候用ref?

  1. 当你要定义基本类型的响应式数据时,必须用ref——因为Proxy只能套引用类型,基本类型套不上盒子,只能用ref先装个壳。
  2. 当你要定义引用类型但之后可能会完全替换掉它时,建议用ref——比如你从接口拿回来的数据先赋值给一个空数组,之后又重新赋值成一个新的具体数组,用reactive的话直接替换会丢失响应式,但ref替换整个盒子里的东西就行,因为监控套会自动套在盒子本身?不对不对,是ref会在盒子内部把引用类型自动转换成proxy,然后监控盒子的value属性,所以整个替换value是没问题的。
  3. 当你要把一个响应式数据传给子组件作为props的一部分,或者要用v-model绑定单个值时,建议用ref——v-model其实就是v-model:value的简写,ref直接.value就对应了value属性,更顺手。

给大家举个ref的小例子:

import { ref } from 'vue';
setup() {
  // 基本类型,必须用ref
  const count = ref(0);
  // 引用类型,也可以用ref
  const user = ref({ name: '张三', age: 18 });
  // 注意哦,在setup或者script setup语法糖里(推荐用script setup),修改ref数据要加.value
  const addCount = () => {
    count.value++;
  };
  const updateUserAge = () => {
    // 引用类型的ref,内部属性不用加.value,直接改就行
    user.value.age++;
  };
  const replaceUser = () => {
    // 完全替换整个引用类型的ref也没问题
    user.value = { name: '李四', age: 25 };
  };
  return { count, user, addCount, updateUserAge, replaceUser };
}

哦对了,如果你用的是Vue3的<script setup>语法糖(现在基本都是用这个了),修改ref数据要加.value,但在模板里不用,Vue3会自动帮你解包,直接写{{ count }}或者@click="addCount"就行,非常方便。

reactive是什么?什么时候用reactive?

你可以把reactive理解成“一个直接套了智能监控套的引用类型”,它只能套对象、数组、Map、Set这些引用类型,不能套基本类型,套基本类型会报错。

那什么时候用reactive?

  1. 当你要定义一组相关的引用类型响应式数据时,建议用reactive——比如用户的所有信息(姓名、年龄、手机号、地址)放在一个user对象里,表单的所有字段放在一个form对象里,这样写代码会更清晰,不用一个个去定义ref。
  2. 当你不需要完全替换掉整个引用类型数据时,建议用reactive——因为完全替换的话会丢失响应式,比如你定义了一个reactive的form对象,之后直接把form = { name: '' }赋值,这个form就不再是响应式的了,这点一定要注意。
  3. 当你要解构一个响应式对象但又不想失去响应式时,可以用reactive配合toRefs——比如你从接口拿回来的数据放在一个reactive的result对象里,里面有data、code、msg三个属性,你可以用toRefs把这三个属性都变成ref,这样解构之后每个属性还是响应式的。

给大家举个reactive的小例子:

import { reactive, toRefs } from 'vue';
setup() {
  // 一组相关的引用类型数据,用reactive
  const userForm = reactive({
    name: '',
    age: 18,
    gender: '男',
    address: reactive({
      // 嵌套对象也可以用reactive再套一层,不过其实不用,外层的已经能监控到深层了
      province: '',
      city: '',
      street: ''
    })
  });
  // 完全替换整个userForm会丢失响应式!!!
  // userForm = { name: '王五' }; 这种写法是错的
  // 正确的更新方式
  const updateForm = () => {
    userForm.name = '王五';
    userForm.address.street = '更新后的街道';
  };
  // 解构配合toRefs
  const { name, age } = toRefs(userForm);
  // 这样修改name.value,userForm.name也会跟着变,保持响应式
  const changeName = () => {
    name.value = '赵六';
  };
  return { userForm, updateForm, name, age, changeName };
}

Vue3响应式新手必踩的5个坑,提前避坑少走弯路!

刚学Vue3响应式的小伙伴,很容易踩这几个坑,今天给大家列出来,提前记牢:

第一个坑:给reactive对象直接赋值,丢失响应式

刚才在讲reactive的时候已经提到过了,比如你定义了一个reactive的list = reactive([1,2,3]),之后直接list = [4,5,6],这个list就不再是响应式的了,正确的做法有两个:

  1. 用splice替换整个数组的内容:list.splice(0, list.length, 4,5,6);
  2. 直接改list的每个索引:list[0] = 4; list[1] =5; list[2] =6;
  3. 最推荐的做法是用ref替换reactive,这样直接替换list.value = [4,5,6]就行,完全没问题。

第二个坑:在setup或者script setup里忘记给ref数据加.value

这个是新手最容易犯的错误,尤其是刚从Vue2转过来的小伙伴,Vue2里修改data里的数据直接this.xxx就行,不用加任何东西,但在Vue3的setup或者script setup里,ref数据本质上是一个对象,里面有一个value属性才是真正的内容,所以修改的时候必须加.value,模板里不用,Vue3会自动解包。

第三个坑:解构reactive对象没有用toRefs,失去响应式

比如你定义了一个reactive的user = reactive({ name: '张三', age: 18 }),之后直接const { name, age } = user,这样name和age只是普通的字符串和数字,修改它们不会更新页面,正确的做法是用toRefs把它们都变成ref:const { name, age } = toRefs(user),这样修改name.value,user.name也会跟着变,保持响应式。

第四个坑:ref包裹引用类型时,内部属性不用加.value

刚才在讲ref的时候也提到过,比如你定义了一个ref的user = ref({ name: '张三', age: 18 }),之后修改user.name是错的,要修改user.value.name;但修改user.value.name不用再加.value,直接改就行,因为内部的对象已经被ref自动转换成了proxy。

第五个坑:用watch监听ref的基本类型和引用类型的写法不一样

比如你定义了一个ref的count = ref(0),监听它的变化直接watch(count, (newVal, oldVal) => { console.log(newVal, oldVal); })就行;但如果你定义了一个ref的user = ref({ name: '张三', age: 18 }),直接watch(user)只能监听整个user.value被替换的情况,不能监听内部属性的变化,要监听内部属性的变化,需要传第三个参数{ deep: true },或者直接watch(() => user.value.name, ...)监听单个属性,如果是reactive的对象,直接watch(user)默认就是深度监听的,不用加deep。

给大家举个watch的小例子:

import { ref, reactive, watch } from 'vue';
setup() {
  const count = ref(0);
  const userRef = ref({ name: '张三', age: 18 });
  const userReactive = reactive({ name: '李四', age: 20 });
  // 监听ref的基本类型
  watch(count, (newVal, oldVal) => {
    console.log('count变化了:', newVal, oldVal);
  });
  // 监听ref的引用类型的内部属性变化,必须加deep
  watch(userRef, (newVal, oldVal) => {
    console.log('userRef内部变化了:', newVal, oldVal);
  }, { deep: true });
  // 监听ref的引用类型的单个属性变化,不用加deep
  watch(() => userRef.value.age, (newVal, oldVal) => {
    console.log('userRef的age变化了:', newVal, oldVal);
  });
  // 监听reactive的对象,默认深度监听
  watch(userReactive, (newVal, oldVal) => {
    console.log('userReactive内部变化了:', newVal, oldVal);
  });
  const addCount = () => count.value++;
  const updateUserRefAge = () => userRef.value.age++;
  const updateUserReactiveAge = () => userReactive.age++;
  return { count, userRef, userReactive, addCount, updateUserRefAge, updateUserReactiveAge };
}

Vue3响应式新手怎么快速上手?

新手不用一开始就死磕Proxy和Reflect的源码,先做到这几点就行:

  1. 知道Vue3响应式和Vue2的区别,核心是用了Proxy,解决了Vue2的数组改索引、重置长度、深层对象新增/删除属性不响应的问题,性能也更好;
  2. 搞懂ref和reactive的用法和适用场景,简单来说就是“基本类型用ref,引用类型尽量用reactive(不需要完全替换的情况下),需要完全替换的引用类型也用ref”;
  3. 记住新手必踩的5个坑,提前避坑少走弯路;
  4. 多写多练,写几个小项目自然就熟练了。

哦对了,Vue3还有一些其他的响应式API,比如computed、watchEffect、toRef、shallowRef、shallowReactive等等,这些可以等你把ref和reactive搞明白之后再慢慢学,不用着急一口吃成个胖子。

好了,今天关于Vue3响应式的内容就唠到这里,希望能对大家有所帮助,如果还有什么不懂的问题,欢迎在评论区留言讨论哦!

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门