艺灵设计

全部文章
×

Vue3学习之reactive()用法

作者:艺灵设计 - 来源:http://www.yilingsj.com - 发布时间:2022-08-24 13:18:49 - 阅: - 评:0 - 积分:0

摘要:在Vue2中,如果想定义一个响应式的变量,直接在data(){}中定义即可,不论类型。在Vue3中,定义对象或数组类型的响应式数据,可通过reactive()来实现。

友情提醒:接下来的Vue3系列笔记均为个人在学习中的理解,不保证完全准确性。如有不当之处,还请看官不吝赐教。

一、引子

上一篇我们了解了Vue3中ref()的用法,今天来说说另一个常用的reactive()的用法。对reactive()还不熟悉的看官可以猛戳→→→Vue官方文档

阅读官方文档,我们了解到以下信息:

1、返回一个对象的响应式代理;
2、响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。
3、值得注意的是,当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包。
......

不知道看官阅读完文档是什么感受,反正我是不太理解。

既然不太理解,那就动动手写几个demo加深下理解吧。跟上一节一样,艺灵仍然是有几个疑问,具体问题见下方。

二、4个疑问

  1. vue3中用reactive()定义基础类型的数据会怎样?
  2. reactive()定义后的值该如何更新?
  3. reactive()是深拷贝还是浅拷贝?
  4. reactive()会改变源数据吗?

好了,问题问完了,demo走起来。

2.1、答:用reactive()定义基础类型的数据,控制台会有警告

完整demo代码见下方。

<!-- 友情提示:因编码原因,文章中的双括号{}均做了转码处理,复制后需要替换成英文状态下的{ }才能运行 -->
<template>
  <div>
    <p>年龄:{{ reactiveAge }​}</p>
    <p>姓名:{{ reactiveName }​}<input type="text" v-model="reactiveName" /></p>
    <p>
      性别:{{ reactiveSex }​}
      <label><input type="radio" name="sex" value="true" v-model="reactiveSex" />男</label>
      <label><input type="radio" name="sex" value="false" v-model="reactiveSex" />女</label>
    </p>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  setup() {
    const age = 10 // 定义年龄,类型:Number
    const name = '张三' // 姓名,类型:String
    const sex = false // 性别,类型:Boolean
    const reactiveAge = reactive(age)
    const reactiveName = reactive(name)
    const reactiveSex = reactive(sex)

    return {
      reactiveAge,
      reactiveName,
      reactiveSex,
    }
  },
}
</script>

<style lang="scss" scoped></style>

打开控制台可看到有警告value cannot be made reactive: xxx。除此之外,修改姓名、性别时数据不会发生响应式变化。

如果定义的是一个数组或对象,则控制台中会显示一个Proxy的对象。

import { reactive } from 'vue'
export default {
  setup() {
    const age = 10 // 定义年龄,类型:Number
    const name = '张三' // 姓名,类型:String

    const reactiveObj = reactive({ age, name })
    console.log('reactiveObj=', reactiveObj) // 打印:reactiveObj= Proxy {age: 10, name: '张三'}

    return {
      reactiveObj,
    }
  },
}

2.2、答:具体问题具体分析

这里有多种情况,比如:只修改对象的某一个key的值、修改数组中的某个值、向数据中插入数据、清空数组、给数组重新赋值等。

下面用几个例子进行一一说明。

2.2.1、了解结构

在回答问题之前,我们需要了解今天的reactive()处理后的数据结构是什么样的。

沿用前面的例子,点击日志中的小三角将数据展开,结构如下图:用reactive()定义一个对象类型的数据

在控制台中通过reactiveObj.agereactiveObj.name来访问对应的值。

2.2.2、只修改对象中的某一个key的值

所以这里可通过xxx.[key]的形式来修改对象中指定key的值。比如:我要把“张三”改成“李四”。

<script>
import { reactive } from 'vue'
export default {
  setup() {
    const obj = { name: '张三', age: 10 }
    const reactiveObj = reactive(obj) // 用reactive让其变为响应式的

    /* 此处为修改的部分↓↓↓ */
    reactiveObj.name = '李四' // 修改姓名
    reactiveObj.age = 22 // 修改年龄
    /* 此处为修改的部分↑↑↑ */

    return {
      reactiveObj
    }
  },
}
</script>

<style lang="scss" scoped></style>

2.2.3、修改数组中的某个值

可通过索引来修改数组中指定的值。比如:数组的删除操作。

<script>
import { reactive } from 'vue'
export default {
  setup() {
    const list = [
      { name: '张三', age: 10 },
      { name: '李四', age: 20 },
    ]
    const reactiveList = reactive(list) // 用reactive让其变为响应式的

    /* 此处为修改的部分↓↓↓ */
    reactiveList[1].name = '王五' // 修改索引为1的name,即赋值为王五
    reactiveList.splice(0, 1) // 删除索引为0的数据,即删除name为张三的那条数据
    /* 此处为修改的部分↑↑↑ */

    return {
      reactiveList
    }
  },
}
</script>

2.2.4、向数组中插入数据

可通过数组的push()方法来向数组中追加数据。比如:向列表中新增一条名为王五的记录。

<script>
import { reactive } from 'vue'
export default {
  setup() {
    const list = [
      { name: '张三', age: 10 },
      { name: '李四', age: 20 },
    ]
    const reactiveList = reactive(list) // 用reactive让其变为响应式的

    /* 此处为修改的部分↓↓↓ */
    reactiveList.push({ name: '王五', age: 30 }) // 追加一条数据
    /* 此处为修改的部分↑↑↑ */

    return {
      reactiveList
    }
  },
}
</script>

2.2.5、清空数组

这里有多种方法,推荐下面的方法一。

2.2.5.1、方法一:用.length = 0可清空数组(推荐)
<script>
import { reactive } from 'vue'
export default {
  setup() {
    const list = [
      { name: '张三', age: 10 },
      { name: '李四', age: 20 },
    ]
    const reactiveList = reactive(list) // 用reactive让其变为响应式的

    /* 此处为修改的部分↓↓↓ */
    reactiveList.length = 0 // 清空了数组
    /* 此处为修改的部分↑↑↑ */

    return {
      reactiveList
    }
  },
}
</script>
2.2.5.2、方法二:遍历删除

这种方法比方法一麻烦些,不推荐。但从功能上来说,也可实现清空数组的需求哦!

<script>
import { reactive } from 'vue'
export default {
  setup() {
    const list = [
      { name: '张三', age: 10 },
      { name: '李四', age: 20 },
    ]
    const reactiveList = reactive(list) // 用reactive让其变为响应式的

    /* 此处为修改的部分↓↓↓ */
    // 循环删除以达到清空数组的目的
    for (var i = reactiveList.length; i > -1; i--) {
      reactiveList.splice(i, 1)
    }
    /* 此处为修改的部分↑↑↑ */

    return {
      reactiveList
    }
  },
}
</script>
2.2.5.3、方法三:=[]的错误用法

需要注意一点哈,平时我们常用的=[]在这里使用不当时,是无法达到预期效果的哈。即使你在定义时使用let而不是const

<!-- 友情提示:这是一个错误示例,写出来只是为了提醒大家不要掉坑中 -->
<script>
import { reactive, isReactive } from 'vue'
export default {
  setup() {
    const list = [
      { name: '张三', age: 10 },
      { name: '李四', age: 20 },
    ]
    let reactiveList = reactive(list) // 用reactive让其变为响应式的

    /* 此处为修改的部分↓↓↓ */
    console.log('reactiveList=', reactiveList, ';是否为响应式=', isReactive(reactiveList))
    reactiveList = [] // 此处对reactiveList进行了重新赋值,此时只是一个[],已不再具备响应式能力
    setTimeout(() => {
      reactiveList.push({ name: '王五', age: 30 })
      console.log('reactiveList=', reactiveList, ';是否为响应式=', isReactive(reactiveList))
    }, 1000)
    /* 此处为修改的部分↑↑↑ */

    return {
      reactiveList
    }
  },
}
</script>

上面代码大致的意思是:

  1. 先定义一个名为list变量名,值为Array类型且数组长度为2
  2. 接着使用reactive()使其变为响应式
  3. 打印数组的值并使用isReactive()方法检测其是否为响应式
  4. 接着使用 = []进行清空数据
  5. 延时1秒后向该数组中插入一条数据
  6. 再次打印数组的值并用isReactive()验证其是否为响应式。

打开控制台可看到如下日志↓↓↓

reactiveList= Proxy {0: {…}, 1: {…}} ;是否为响应式= true
reactiveList= [{…}] ;是否为响应式= false

查看2处日志,第一处的是否为响应式值为true,第二处的值为false。所以说,我们刚才的用法是有问题的。

=[]就完全不能用吗?

2.2.5.4、方法三:=[]的正确用法

这里需要满足一个条件,非顶级子级时才行。相关代码片段见下方↓↓↓

const obj = {
  id: 1,
  children: [
    {
      id: 2,
      children: [
        { id: 3, children: [] },
        { id: 4, children: [] },
      ],
    },
  ],
}
const reactiveObj = reactive(obj) // 用reactive让其变为响应式的
reactiveObj.children[0].children = []
reactiveObj.children = []
console.log('reactiveObj', reactiveObj, ';是否为响应式=', isReactive(reactiveObj.children))

2.2.6、给数组重新赋值

日常工作中,这个需求也很常见。比如说:订单列表页默认展示了10条数据,点击第2页的时候会请求第2页的数据,在拿到接口返回的数据后,会更新页面上的订单。

这个时候我们就需要对源数据进行重新赋值了,一般是this.tableData = res.data

由于用reactive()定义的变量无法直接使用=来进行再次赋值,所以可继续采用2.2.4的方法进行变通。

2.2.6.1、方法一:用.length + push()(推荐)
<script>
import { reactive } from 'vue'
export default {
  setup() {
    const list = [
      { name: '张三', age: 10 },
      { name: '李四', age: 20 },
    ]
    const reactiveList = reactive(list) // 用reactive让其变为响应式的

    /* 此处为修改的部分↓↓↓ */
    reactiveList.length = 0 // 清空了数组
    const twoList=[
      { name: '王五', age: 20 },
      { name: '赵六', age: 23 },
      { name: '张大三', age: 25 },
    ]
    reactiveList.push(...twoList) // 追加新数据
    /* 此处为修改的部分↑↑↑ */

    return {
      reactiveList
    }
  },
}
</script>
2.2.6.2、方法二:遍历删除 + push()

除此之外,还可以用遍历的方法来进行重新赋值。

<script>
import { reactive } from 'vue'
export default {
  setup() {
    const list = [
      { name: '张三', age: 10 },
      { name: '李四', age: 20 },
    ]
    const reactiveList = reactive(list) // 用reactive让其变为响应式的

    /* 此处为修改的部分↓↓↓ */
    // 循环删除以达到清空数组的目的
    for (var i = reactiveList.length; i > -1; i--) {
      reactiveList.splice(i, 1)
    }
    const twoList=[
      { name: '王五', age: 20 },
      { name: '赵六', age: 23 },
      { name: '张大三', age: 25 },
    ]
    reactiveList.push(...twoList) // 追加新数据
    /* 此处为修改的部分↑↑↑ */

    return {
      reactiveList
    }
  },
}
</script>
2.2.7、demo演示

来放一个完整的demo案例吧。看官可点击绿色的vConsole按钮或右上角的“新窗口预览”,按下F12,一边点击按钮一边看控制台日志哈。

实际上,在日常工作中我们常用的也就是上述提到的5种关于对象或数组的数据处理。还有一些其他的,比如:数组倒序(reverse)、数组去重(form)、数组过滤(filter)等方法,均可以结合2.2.6小结来实现业务需求,此处不再进行相关demo演示。

2.3、答:是浅拷贝

由于reactive()只能定义引用类型的数据,如:Array(数组)Object(对象),所以是不存在深拷贝的哈!

沿用上面2.2.7的demo,执行写好的handleChangeOldData()方法即可。相关源码片断见下方。

setup() {
  const age = 10 // 定义年龄,类型:Number
  const name = '张三' // 姓名,类型:String
  const list = [] // 类型:Array

  const obj = { age, name, list } // 类型:Object
  const reactiveObj = reactive(obj) // 用reactive让其变为响应式的

  // 改变原始数据
  function handleChangeOldData() {
    console.log('改变原始数据前的reactiveObj=', reactiveObj, ';obj=', obj)
    obj.name = '赵六' // 改变原始数据
    console.log('修改数据后的obj=', obj)
    console.log('响应式的值有变化吗?见下方↓↓↓')
    console.log('改变原始数据后的reactiveObj=', reactiveObj)
  }

  ...... // 此处省略一些其他代码

  return {
    ...... // 此处省略一些其他代码
    reactiveObj,
    handleChangeOldData
  }
}

上方代码大致意思是:

  1. setup(){}中申明agenameobj等变量
  2. 定义一个名为reactiveObj的变量,通过reactive()让其变成响应式代理
  3. 进入handleChangeOldData()函数,先是一个日志输出
  4. 接着修改obj.name的值为“赵六”
  5. 依次打印3处日志

点击“改变原始数据”按钮后的打印结果↓↓↓

改变原始数据前的reactiveObj= Proxy {age: 10, name: '张三', list: Array(0)} ;obj= {age: 10, name: '张三', list: Array(0)}
修改数据后的obj= {age: 10, name: '赵六', list: Array(0)}
响应式的值有变化吗?见下方↓↓↓
改变原始数据后的reactiveObj= Proxy {age: 10, name: '赵六', list: Array(0)} // 这里的name由原来的张三变成了赵六

通过上方日志可以直观的看到obj.namereactiveObj.name都发生了变化,只不过页面不会跟随变化哈。毕竟是直接修改了obj.name

2.4、答:会改变源数据

沿用上面2.2.7的demo,看官可通过以下步骤来验证结论。

  1. 点击“爱好”后面的“新增(0/3)”按钮,此时控制台会打印日志,输出:原始数据list= [{…}]”
  2. 再点击一次,此时控制台输出:原始数据list= (2) [{…}, {…}]
  3. 鼠标滑过一个爱好,点击右上角的小x进行删除,此时控制台输出:原始数据list= [{…}]

每次操作都打印了原始数据list的结果,而其也在不断的发生变化,所以也证明了其是浅拷贝。

三、新疑问:重置reactive()定义的值后,后续值有修改,会影响源数据吗?

上一节中,ref()的结果出乎意料,但今天的reactive()是正常的。如果您没有浏览过上一篇文章,可以戳我访问哦。

还是以2.2.7的demo为例,通过反复操作新增爱好 -> 删除所有爱好 -> 再新增爱好这几个步骤,观察控制台中的原始数据list=,会发现数组长度会随着对应的操作发生变化。所以说,这里仍会影响源数据

好了,以上就是今天的分享,咱们下节见。

转载声明:
  若亲想转载本文到其它平台,请务必保留本文出处!
本文链接:/xwzj/2022-08-24/vue3-reactive-responsive.html

若亲不想直保留地址,含蓄保留也行。艺灵不想再看到有人拿我的技术文章到他的地盘或者是其它平台做教(装)程(B)而不留下我的痕迹。文章你可以随便转载,随便修改,但请尊重艺灵的劳动成果!谢谢理解。

亲,扫个码支持一下艺灵呗~
如果您觉得本文的内容对您有所帮助,您可以用支付宝打赏下艺灵哦!

Tag: Vue3 Vue2 setup() reactive() Proxy ref() isReactive() 响应式数据

上一篇: Vue3学习之ref()用法   下一篇: 每月免费领6GB流量,拿好不谢

评论区