ref和reactive创建响应式数据

前文我们讲在setup里创建数据直接let name = 'yujiabao',然后在return出去就可以使用了的时候,说了这样创建的数据不是响应式的,不会再视图上及时更新,现在我们就来讲讲VUE3如何创建响应式数据。

插一嘴VUE2创建响应式数据是直接放在data里就好了,底层会给数据做数据代理和数据劫持,所以直接就是响应式数据。

VUE3创建响应式数据的方式有两种,一种是通过ref去创建响应式数据,还有一种是通过reactive去创建响应式数据。

ref创建基本类型的响应式数据

首先我们要先引入这个ref,可以直接在setup语法糖里引入。

html 复制代码
<script setup lang="ts">
import { ref } from "vue";
</script>

引入ref以后,我们就可以把想变成响应式数据的变量用ref包裹起来,所以其实ref是一个函数,我们传一个参数过去,就可以返回一个响应式数据,我们可以打印看一下。

html 复制代码
<script setup lang="ts">
import { ref } from "vue";
let name = ref("yujiabao");
let age = ref("18");
let gender = "男";

console.log(name);
console.log(age);
console.log(gender);
</script>

可以看到ref函数会返回一个RefImpl对象,这个对象里有很多变量,其中带下划线_的我们不用管,里面有一个变量叫value,这个是我们要在意的,这个value就是我们要的响应式数据。

那这个时候是不是在使用的时候要在后面加上.value了呢?比如使用name变量,既然已经是一个对象了,我们在模板使用的时候是不是要name.value呢?其实不用,因为模板会自动给我们加上这个.value,我们就直接使用name就好了。

html 复制代码
<template>
  <div>{{ name }}</div>
  <div>{{ age }}</div>
  <div>{{ gender }}</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
let name = ref("yujiabao");
let age = ref("18");
let gender = "男";
</script>

但是我们在js或者ts里操作这个变量的时候必须要.value

html 复制代码
<template>
  <div>{{ name }}</div>
  <div>{{ age }}</div>
  <div>{{ gender }}</div>
  <button @click="changeName">修改姓名</button>
</template>

<script setup lang="ts">
import { ref } from "vue";
let name = ref("yujiabao");
let age = ref("18");
let gender = "男";

function changeName() {
  name.value = "于家宝";
}
</script>

如果不加.value会提示报错,因为此时name已经不是一个字符串而是一个对象了,在模板可以直接使用name是因为模板会自动帮你加上.value

reactive创建复杂类型的响应式数据

首先和ref一样,要先引入reactive

html 复制代码
<script setup lang="ts">
import { reactive } from "vue";
</script>

使用方式跟ref也差不多,用reactive包裹起来的变量就是响应式变量了。

html 复制代码
<script setup lang="ts">
import { reactive } from "vue";
let person = reactive({ name: "yueliang", age: "18" });
console.log(person);
</script>

我们可以看到reactive函数会返回一个Proxy对象,对象里面有两个变量,其中Target里就是我们需要的响应式数据。

同样在模板使用的时候,我们不需要在后面.Target,因为模板会替我们处理好这些。

html 复制代码
<template>
  <div>{{ person.name }}今年{{ person.age }}岁</div>
</template>

<script setup lang="ts">
import { reactive } from "vue";
let person = reactive({ name: "yueliang", age: "18" });
console.log(person);
</script>

但是有一点不一样,我们讲在js或者ts里操作ref返回的响应式数据的时候是需要加上.value的,那么我们在js或者ts里操作reactive返回的响应式数据的时候是否也需要加上.Target呢?其实是不需要的,直接操作就好了。

html 复制代码
<script setup lang="ts">
import { reactive } from "vue";
let person = reactive({ name: "yueliang", age: "18" });
function change() {
  person.name = "月亮";
}
</script>

ref创建复杂类型的响应式数据

前面我们讲了ref可以创建基本类型的响应式数据,其实ref也可以创建复杂类型的响应式数据,插句嘴,reactive不能创建基本类型的响应式数据。

html 复制代码
<script setup lang="ts">
import { ref } from "vue";
let person = ref({ name: "yueliang", age: "18" });
console.log(person);
</script>

可以看到被ref包裹起来的复杂数据同样也是一个RefImpl对象,对象里面也有一个value变量,但是这个value变量可不是想象中的那个{name: 'yueliang',age: '18'},而是一个Proxy对象,这就又回到reactive的知识点了,里面有一个Target变量,存放的就是我们需要的响应式数据。

这说明什么,说明ref在让复杂数据变成响应式的时候,会在底层再进行一次reactive的调用,所以RefImpl.value又被reactive包裹了一次,就是说这个时候person.value就是reactive({name: 'yueliang',age: '18'}),这就是ref创建复杂类型响应式数据的原理。

这个时候要注意一点,那此时在js或者ts里操作这个person的时候,还能跟reactive一样吗?是不能一样的。

html 复制代码
<script setup lang="ts">
import { ref } from "vue";
let person = ref({ name: "yueliang", age: "18" });
function change() {
  person.name = "月亮";
}
</script>

这时候是有报错的:

此时这个person就是一个RefImpl对象,没有name属性,根据前面讲的,在js或者ts里操作是需要加上.value的,此时peoson.value才是我们想要的{name: 'yueliang',age: '18'},所以操作的时候应该这么写。

html 复制代码
<script setup lang="ts">
import { ref } from "vue";
let person = ref({ name: "yueliang", age: "18" });
function change() {
  person.value.name = "月亮";
}
</script>

ref对比reactive

前面我们讲了refreactive都可以定义响应式数据,但是它们两者之间有什么区别呢,我们这里也要讲一下。

ref既能定义基本数据类型,也可以定义复杂数据类型,而reactive只能定义复杂数据类型。

这一点前面已经说了,在这里就不再赘述了。

ref定义的数据在js或者ts中使用的时候需要加.value,而reactive定义的不需要。

这一点前面也说了,不赘述了。

reactive定义的复杂类型响应式数据被重新定义以后会失去响应式

我们可以对reactive定义的复杂数据类型进行属性上的修改,比如修改person对象的name属性或者age属性,这是可以的,修改完person还是响应式数据,但是不能直接修改整个person,这相当于对person对象进行重新定义了,这样修改完以后person数据就不是响应式的了。

html 复制代码
<template>
  <div>{{ person.name }}今年{{ person.age }}岁</div>
  <button @click="changeName">changeName</button>
  <button @click="changeAge">changeAge</button>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
import { reactive } from "vue";
let person = reactive({ name: "yueliang", age: "18" });
function changeName() {
  person.name = "月亮";
  console.log(person, "修改name属性,还有响应式");
}
function changeAge() {
  person.age = "19";
  console.log(person, "修改age属性,还有响应式");
}
function change() {
  person = {
    name: "月亮",
    age: "19",
  };
  console.log(person, "修改整个person对象,相当于重新定义person对象,失去响应式");
}
</script>

比如上述代码,其中changeName方法和changeAge方法都是能够生效的,视图也会更新我们想要的结果,但是change方法点击以后视图就会没有反应,其实数据是已经改变了,但是因为修改了整个person对象,导致失去了响应式,所以视图没有更新,我们打印在后台就可以很清楚的看到。

其实这里就可以看到,person本身不是普通对象,而是一个Proxy对象,这个才是具备响应式的,而我们重新给它定义成一个普通对象,这才会让它失去响应式导致视图不会更新。

那有人就会想了,既然普通对象不行,那重新定义成一个reactive包裹的对象可以吗,我们可以试一试。

html 复制代码
<template>
  <div>{{ person.name }}今年{{ person.age }}岁</div>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
import { reactive } from "vue";
let person = reactive({ name: "yueliang", age: "18" });
function change() {
  person = reactive({
    name: "月亮",
    age: "19",
  });
  console.log(person, "用reactive重新定义person对象");
}
</script>

会发现其实还是不行,那是因为最开始的person是被reactive定义的响应式对象,这也是在页面上展示的,但是person再被reactive重新定义,那就是一个新的person对象了,就不是页面上的那个person对象了,所以页面上的那个不会改变,这听起来很绕口,但是这么理解就好了。

那既然不能重新定义这个person对象,如果遇到了很多属性都需要去修改,难不成不重新定义,去一个一个修改吗?其实也不用一个一个去修改属性,我们可以使用Object.assign方法去批量修改属性。

Object.assign(obj, obj1, obj2...)

  • 接收多个参数,会把obj1(包括)以后对象的属性都加到obj里面去,如果属性名相同就会直接覆盖。
  • 返回值:就是这个obj。
html 复制代码
<template>
  <div>{{ person.name }}今年{{ person.age }}岁</div>
  <button @click="changeName">changeName</button>
  <button @click="changeAge">changeAge</button>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
import { reactive } from "vue";
let person = reactive({ name: "yueliang", age: "18" });
function changeName() {
  person.name = "月亮";
  console.log(person, "修改name属性,还有响应式");
}
function changeAge() {
  person.age = "19";
  console.log(person, "修改age属性,还有响应式");
}
function change() {
  Object.assign(person, {
    name: "月亮",
    age: "18",
  });
  console.log(person, "使用Object.assign方法修改整个person对象");
}
</script>

这样就可以解决了。

ref定义的复杂类型响应式数据被重新定义以后不会失去响应式

前面说了reactive定义的复杂数据类型不能重新定义,重新定义会失去响应式,如果遇到需要修改多个属性的时候就使用Object.assign方法,但是ref定义的复杂数据类型重新定义是不会失去响应式的。不过这也要加上.value,其实严格来说这也不算是重新定义。

html 复制代码
<template>
  <div>{{ person.name }}今年{{ person.age }}岁</div>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
import { ref } from "vue";
let person = ref({ name: "yueliang", age: "18" });
function change() {
  person.value = {
    name: "月亮",
    age: "19",
  };
  console.log(person);
}
</script>

如果真的说是重新定义,就应该是下面这样,不加上这个.value

但是这样写都是有错的,因为此时person是一个RefImpl对象,不能这么赋值。

就算是用ref重新定义,也跟前面讲reactive重新定义的时候一样,是没有响应式的,因为页面上展示的是原来的person对象,现在person被重新定义了,就不是页面上展示的那个了,所以页面上的数据也是不会改变。

html 复制代码
<template>
  <div>{{ person.name }}今年{{ person.age }}岁</div>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
import { ref } from "vue";
let person = ref({
  name: "yueliang",
  age: "18",
});
function change() {
  person = ref({
    name: "月亮",
    age: "19",
  });
  console.log(person);
}
</script>

就算是ref定义的基本数据类型也不能这么修改,是没有效果的。

html 复制代码
<template>
  <div>{{ person }}</div>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
import { ref } from "vue";
let person = ref(0);
function change() {
  person = ref(9);
  console.log(person);
}
</script>

toRefs

我们现在已经对refreactive去定义响应式数据有一些了解了,那我们在对这个复杂类型的响应式数据进行解构赋值的时候,解构出来的数据还具备响应式吗?

html 复制代码
<template>
  <div>{{ name }}今年{{ age }}岁</div>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
import { reactive } from "vue";
let person = reactive({ name: "yueliang", age: 18 });
let { name, age } = person;
console.log(person);
console.log(age);

function change() {
  age++;
  console.log(age);
}
</script>

会发现展示是没有问题的,但是点击按钮的时候页面上的年龄没有预想中的递增+1

而且点击以后其实数据是已经改变的了,显而易见,这解构出来的数据没有响应式,而且前面打印也可以看到,打印出来的就只是一个普通的数据,不是RefImpl对象或者Proxy对象。

说白了其实let { name, age } = person就只是相当于下面这段代码:

js 复制代码
let name = person.name  'yueliang'
let age = person.age  18

只是简单的读取了person.name的值而已,看后台打印也可以看出来,就是一个普通的数据。

那怎么才能让解构出来的数据是响应式呢?这就要用到toRefs了。

html 复制代码
<template>
  <div>{{ name }}今年{{ age }}岁</div>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
// 先引入toRefs
import { reactive, toRefs } from "vue";
let person = reactive({ name: "yueliang", age: 18 });
let { name, age } = toRefs(person);
console.log(age);
function change() {
  age++;
  console.log(age);
}
</script>

这个toRefs从字面意思都能看出来,就是把某些东西给转换成ref包裹的,也就是响应式数据,这里就是把解构出来的nameage给转换成RefImpl对象,这样就是响应式的了,其实截图就可以看出来:

这里age报错了,就是因为此时age已经是RefImpl对象了,根据我们前面所讲的,js或者ts里操作时需要加上.value的,所以这里报错。

我们给它加上,然后去试一试看看页面能不能及时更新。

html 复制代码
<template>
  <div>{{ name }}今年{{ age }}岁</div>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
// 先引入toRefs
import { reactive, toRefs } from "vue";
let person = reactive({ name: "yueliang", age: 18 });
let { name, age } = toRefs(person);
console.log(age);
function change() {
  age.value++;
  console.log(age);
}
</script>

可以看到数据是可以更新的了,而且打印出来的是一个ObjectRefImpl对象,其实也就是一个RefImpl对象,是一个响应式数据。

其实我们也可以去打印一下这个toRefs(person),看看到底是个什么东西。

可以看到toRefs(person)就是一个普通的对象,但是对象里面的每一个属性都是ObjectRefImpl对象,这个对象里也有一个value属性,是不是证明了前面所说的ObjectRefImpl对象也就是RefImlpl对象。那这么一来,let { name, age } = person就可以看作下面这些代码:

js 复制代码
let name = name (ObjectRefImpl)
let age = age (ObjectRefImpl)

总的来说就是toRefs可以把所包裹的对象中的每一个属性都被ref包裹转换成RefImpl对象,所以这样解构出来的数据才具有响应式。

还有一个注意点:那此时的ageperson.age是否还有关联呢?

答案是有的,我们去试一下:

html 复制代码
<template>
  <div>{{ name }}今年{{ age }}岁</div>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
// 先引入toRefs
import { reactive, toRefs } from "vue";
let person = reactive({ name: "yueliang", age: 18 });
let { name, age } = toRefs(person);
function change() {
  age.value++;
  console.log(age.value, person.age);
}
</script>

可以看到ageperson.age是有关联的,就相当于它俩的指针指向的是同一个内存。

toRef

这个跟toRefs作用差不多,toRefs是把包裹对象的所有属性都用ref包裹一下,这个是指定某个属性去包裹。

html 复制代码
<template>
  <div>{{ name }}今年{{ nl }}岁</div>
  <button @click="change">change</button>
</template>

<script setup lang="ts">
// 先引入toRefs
import { reactive, toRef } from "vue";
let person = reactive({ name: "yueliang", age: 18 });
let name = toRef(person, "name");
let nl = toRef(person, "age");
function change() {
  nl.value++;
  console.log(nl.value, person.age);
}
</script>

效果都是一样的,只是toRefs是把包裹对象的全部属性都用ref包裹起来变成响应式的,toRef是把包裹对象的某个属性用ref包裹起来变成响应式的。

相关推荐
旷野本野11 分钟前
【HTML-CSS】
前端·css·html
Jolyne_17 分钟前
css实现圆柱体
前端·css·react.js
亦黑迷失24 分钟前
canvas + ts 实现将图片一分为二的功能,并打包发布至 npm
前端·typescript·canvas
....49229 分钟前
antvX6自定义 HTML 节点创建与更新教程
前端·html·antvx6
禹曦a31 分钟前
Web开发:常用 HTML 表单标签介绍
前端·html·web
姑苏洛言1 小时前
如何让用户回到上次阅读的位置?——前端视角下的用户体验优化实践
前端
kovlistudio1 小时前
红宝书第三十一讲:通俗易懂的包管理器指南:npm 与 Yarn
开发语言·前端·javascript·学习·npm·node.js
我爱吃干果1 小时前
ZoomCharts使用方法
前端·javascript·笔记·zoom
旧厂街小江1 小时前
LeetCode 第111题:二叉树的最小深度
前端·算法·程序员
&白帝&1 小时前
vue实现大转盘抽奖
前端·javascript·vue.js