艺灵设计

全部文章
×

Vue3学习之ref()用法

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

摘要:Vue3中定义一个响应式变量可以使用ref()或reactive(),ref()定义后的值在script中需要用.value才能对其进行重新赋值,在template中不需要.value。在不同的数据类型前,ref()定义的值并不全是深拷贝。

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

一、前言

话说Vue3.0问世已经快2年了,而我还一直使用着成熟的Vue2,难免有些落伍了。

再说了,现在前端这么卷,不学习只会被淘汰。所以我抽空了解了一下Vue3,学学新知识,终归是有好处的。

今天来聊聊Vue3中使用ref()定义一个响应式变量的用法,对ref()还不熟悉的看官可以猛戳→→→Vue官方文档

阅读官方文档,我们了解到以下信息:
1、返回一个响应式的、可更改的 ref 对象;
2、可以用.value赋予新的值

带着上面的已知线索,艺灵先抛出4个疑问,咱们带着问题阅读本文,岂不美哉![:滑稽]

二、4个疑问

  1. vue3中如何定义一个响应式变量?
  2. ref()定义后的值该如何更新?
  3. ref()是深拷贝还是浅拷贝?
  4. ref()会改变原数据吗?

灵魂4问问完了,看官都能解答不?

2.1、答:使用 ref()

虽然reactive()也可以定义一个响应式变量,但不属于今天讨论范围哈。今天主要讨论的是ref()

为了降低学习成本和更好的兼容Vue2,下面先使用setup(){}的写法来写demo。

<!-- 友情提示:因编码原因,文章中的双括号{}均做了转码处理,复制后需要替换成英文状态下的{ }才能运行 -->
<template>
  <div>
    <p>年龄:{{ age }​}</p>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    const age = ref(10) // 定义年龄默认值10,用ref让其变为响应式的

    return {
      age, // 暴露出去,供别的地方使用,例如:上方的template
    }
  },
}
</script>

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

运行上述代码后,此时页面中会显示“年龄:10”。也就是说,代码中的age被编译成了10,此时表示着使用ref()成功。

目前的代码还看不出age响应式的,所以我们需要做一些交互处理才能证明其是响应式的。

2.2、答:通过 .value 修改其值

在页面上新增一个按钮,并通过@click点击事件触发相关函数,然后在下方的setup(){}中进行定义函数并在return中暴露出来即可。

<!-- 友情提示:因编码原因,文章中的双括号{}均做了转码处理,复制后需要替换成英文状态下的{ }才能运行 -->
<template>
  <div>
    <p>年龄:{{ age }​}</p>
    <p><button @click="handleChangeAge">修改年龄</button></p>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    const age = ref(10) // 定义年龄默认值10,用ref让其变为响应式的

    // 点击按钮修改年龄
    function handleChangeAge() {
      age.value = parseInt(Math.random() * 100) + 1 // 通过随机数生成随机年龄
    }
    return {
      age, // 暴露出去,供别的地方使用,例如:上方的template
      handleChangeAge, // 注意这里
    }
  },
}
</script>

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

现在,我们点击页面上的按钮就能看到年龄在不停的发生变化。动态gif图如下:点击按钮修改年龄

通过上面的 2.1 和 2.2 这两个小例子,我们成功的验证了已经信息和解决了前2个问题。那是不是说,只要是用ref()定义的变量,在修改时就要用.value才能重新赋值呢?

显然,还有别的方法。

2.2.1、在template中不需要用.value赋值

<!-- 友情提示:因编码原因,文章中的双括号{}均做了转码处理,复制后需要替换成英文状态下的{ }才能运行 -->
<template>
  <div>
    <p>年龄:{{ age }​}</p>
    <p><button @click="age++">修改年龄</button></p>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    const age = ref(10) // 定义年龄默认值10,用ref让其变为响应式的

    return {
      age, // 暴露出去,供别的地方使用,例如:上方的template
    }
  },
}
</script>

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

现在点击上面的按钮,同样可以实现效果。

2.3、答:既是深拷贝又是浅拷贝

是不是觉得有点儿矛盾呀,其实不然。

既然是说结论,那就得讲一个前提。在不说清楚数据类型的前提下就贸然下结论是不负责任的表现哈。

2.3.1、当定义数据类型为数字、字符串、布尔时,是深拷贝

<!-- 友情提示:因编码原因,文章中的双括号{}均做了转码处理,复制后需要替换成英文状态下的{ }才能运行 -->
<template>
  <div>
    <p>年龄:{{ refAge }​}</p>
    <p>姓名:{{ refName }​}<input type="text" v-model="refName"></p>
    <p>性别:{{ refSex }​}
      <label><input type="radio" name="sex" value="true" v-model="refSex" />男</label>
      <label><input type="radio" name="sex" value="false" v-model="refSex" />女</label>
    </p>
    <p><button @click="handleChangeAge">修改年龄</button></p>
    <p><button @click="handleChangeOldData">修改原始数据,控制台中会打印相关数据</button></p>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    let age = 10 // 定义年龄,类型:Number
    let name = '张三' // 姓名,类型:String
    let sex = false // 性别,类型:Boolean
    const refAge = ref(age) // 用ref让其变为响应式的
    const refName = ref(name)
    const refSex = ref(sex)

    // 点击按钮修改年龄
    function handleChangeAge() {
      refAge.value = parseInt(Math.random() * 100) + 1 // 通过随机数生成随机年龄
      console.log('原始age=', age, ';原始name=', name, ';原始sex=', sex)
    }

    // 改变原始数据
    function handleChangeOldData() {
      age = 2 // 改变原始数据
      name='李四'
      sex=true
      console.log('响应式的值有变化吗?见下方↓↓↓',)
      console.log('age=', refAge.value, 'name=', refName.value, 'sex=', refSex.value)
    }
    return {
      refAge, // 暴露出去,供别的地方使用,例如:上方的template
      refName,
      refSex,
      handleChangeAge, // 注意这里
      handleChangeOldData,
    }
  },
}
</script>

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

阅读上方代码,可以看到在handleChangeOldData函数中分别修改了agenamesex,然后打印了响应式数据。现在我们只需要点击对应的按钮来执行此方法,观看控制台的日志即可知道响应式数据有没有被修改。

下方区域为对应的demo,看官可点击绿色的vConsole按钮或底部的“新窗口预览”,按下F12,一边点击按钮一边看控制台日志哈。

2.3.2、当定义数据为引用数据类型,如对象、数组时,是浅拷贝

<!-- 友情提示:因编码原因,文章中的双括号{}均做了转码处理,复制后需要替换成英文状态下的{ }才能运行 -->
<template>
  <div>
    <p>姓名:{{ refObj.name }​}</p>
    <p>年龄:{{ refObj.age }​}</p>
    <p><button @click="handleChangeAge">修改年龄</button></p>
    <p><button @click="handleChangeOldData">改变原始数据,控制台中会打印相关数据</button></p>
    <p>小提示:打开控制台后再点击上面的按钮,此时日志输出会更直观得出结论哦~</p>
  </div>
</template>

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

    const list = [] // 类型:Array
    const obj = { age, name, list } // 类型:Object
    const refObj = ref(obj) // 用ref让其变为响应式的

    // 点击按钮修改年龄
    function handleChangeAge() {
      refObj.value.age = parseInt(Math.random() * 100) + 1 // 通过随机数生成随机年龄
      console.log('原始数据有变化吗?见下方↓↓↓')
      console.log('obj=', obj, 'age=', age, ';name=', name)
    }

    // 改变原始数据
    function handleChangeOldData() {
      list.push(Math.random()) // 改变原始数据,
      console.log('响应式的值有变化吗?见下方↓↓↓')
      console.log('refObj=', refObj.value)
    }
    return {
      refObj, // 暴露出去,供别的地方使用,例如:上方的template
      handleChangeAge, // 注意这里
      handleChangeOldData,
    }
  },
}
</script>

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

阅读上方代码,我们在handleChangeOldData函数中往list中追加随机数,然后打印了响应式数据。现在我们只需要点击对应的按钮来执行此方法,观看控制台的日志即可知道refObj 有没有被修改。

下方区域为对应的demo,看官可点击“新窗口预览”,按下F12,一边点击按钮一边看控制台日志哈。

2.4、答:如果定义的是引用数据,则会改变原数据

在上面2.3.2的例子基础上补充2行代码即可测试这个问题。为了节约篇幅,此处新增代码添加到handleChangeAge方法中。

// 点击按钮修改年龄
function handleChangeAge() {
  refObj.value.age = parseInt(Math.random() * 100) + 1 // 通过随机数生成随机年龄
  console.log('原始数据有变化吗?见下方↓↓↓')
  console.log('obj=', obj, 'age=', age, ';name=', name)

  /* 此处为新增 ↓↓↓ */
  refObj.value.list.push(Math.random())
  console.log('list=', list)
  /* 此处为新增 ↑↑↑ */
}
</script>

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

具体demo就不放了,来一张动态gif图吧。ref定义为引用数据类型时,修改会影响原始数据

可以看到每次点击时,obj.list的长度发生了变化,并且打印的list=也在不停变化。

看官学会了吗?要不要再来一道题进行巩固下?[:嘿嘿]

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

看官可以先想想答案是什么。反正结果有些让我意外。

3.1、如何重置ref()定义的值?

本文的例子中一共出现了5种类型的数据,分别是:3个基本类型数值(Number)字符串(String)布尔(Boolean)和2个引用数据类型数组(Array)对象(Object)。不同的类型,在重置时需要注意类型,避免再次赋值时报错。

xxx.value = '' // 字符串类型
xxx.value = null // number类型如果不想设置值,可用null来清空
xxx.value = {} // 对象类型
xxx.value = [] // 数组类型

3.2、后续值有修改,不会影响原数据

根据前面2.3和2.4的结论,普通类型的数据是拷贝,引用类型的数据是拷贝。但是当数据被重置后,再次修改ref()的值,引用数据类型的数据不会被再次修改了

这。。。。。。好奇怪啊!!!

下面是一个可在线演示的demo,有兴趣的看官可以按以下步骤看控制台中的打印。
1、先随意输入姓名、年龄、添加爱好并填写值;
2、接着点击“获取数据”按钮;
3、看控制台数据,接着点“重置”按钮;
4、继续走第一步;
5、走第2步,获取数据,此时就会发现日志中的“爱好”和obj不会再发生变化。

相关源码见下方↓↓↓

<!-- 友情提示:因编码原因,文章中的双括号{}均做了转码处理,复制后需要替换成英文状态下的{ }才能运行 -->
<template>
  <div>
    <p>姓名:<input type="text" v-model="refName" /></p>
    <p>年龄:<input type="number" v-model="refAge" /></p>
    <p>
      爱好:<span class="hobby" v-for="(item, index) in refHobbyList" :key="item.id">
        <input type="text" v-model="item.name" class="hobby-input" />
        <span class="hobby-close" v-if="showClose" @click="handleRemove(index)">×</span>
      </span>
      <button @click="handleAddHobby" :disabled="disabled">新增({{ refHobbyList.length }​}/{{ max }​})</button>
    </p>
    <p><button @click="handleSubmit">获取数据</button> <button @click="handleReset">重置</button></p>
    <p>小提示:控制台中会打印相关数据哦~</p>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  computed: {
    disabled() {
      return this.refHobbyList.length === this.max
    },
    showClose() {
      return this.refHobbyList.length > 1
    },
  },
  setup() {
    let i = 0 // 计数器
    const age = null // 定义年龄,类型:null
    const name = '' // 姓名,类型:String
    const refAge = ref(age) // 用ref让其变为响应式的
    const refName = ref(name)
    const max = 3 // 最大爱好数
    const obj = { name: '', id: 0 } // 类型:Object
    const list = [] // 类型:Array
    const refHobbyList = ref(list) // 爱好列表
    const refObj = ref(obj)

    // 新增爱好
    function handleAddHobby() {
      refObj.value.id = parseInt(Math.random() * 10)
      refHobbyList.value.push({ name: '', id: i++ })
    }

    // 删除爱好
    function handleRemove(index) {
      refHobbyList.value.splice(index, 1)
    }

    // 重置数据
    function handleReset() {
      refName.value = '' // 字符串类型
      refAge.value = null // null
      refObj.value = {} // 对象类型
      refHobbyList.value = [] // 数组类型
      console.log('重置后,原始数据有变化吗?见下方↓↓↓')
      console.log('年龄=', age, ';姓名=', name, ';爱好=', list, ';obj=', obj)
    }

    //  获取数据
    function handleSubmit() {
      console.log('原始数据的值见下方↓↓↓')
      console.log('年龄=', age, ';姓名=', name, ';爱好=', list, ';obj=', obj)
    }
    return {
      refAge, // 暴露出去,供别的地方使用,例如:上方的template
      refName,
      max,
      refHobbyList,
      handleAddHobby,
      handleRemove,
      handleReset,
      handleSubmit,
    }
  },
}
</script>

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

想来想去我也没有想明白是为什么。

翻看vue3官方文档,发现有如下描述。

如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。
若要避免这种深层次的转换,请使用 shallowRef() 来替代。

然后我用shallowRef()试了下,无论是基础类型数据还是引用数据,全为深拷贝。难不成上述测试真的跟这个有关?如果有哪位大佬知道原因,还请不吝赐教,谢谢!

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

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

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

Tag: Vue3 Vue2 setup() ref() shallowRef() 响应式数据 引用数据类型 普通数据类型

上一篇: Vue2后台项目改造成Vite2后的优化后续   下一篇: Vue3学习之reactive()用法

评论区