大家好!我是方才,目前是8人后端研发团队的负责人,拥有6年后端经验&3年团队管理经验。
系统学习践行者!近期在系统化输出前端入门相关技术文章,期望能帮大家构建一个完整的知识体系。
如果对你有所帮助,记得一键三连!
我创建了一个编程学习交流群(扫码关注后即可加入),秉持“一群人可以走得更远”的理念,期待与你一起 From Zero To Hero!
茫茫人海,遇见即是缘分!方才兄送你ElasticSearch系列知识图谱、前端入门系列知识图谱、系统架构师备考资料!
Vue的响应式,让数据动起来
Holle!大家好,我是方才兄,接着刚刚新建的vue项目,我们一起开始学习vue3的响应式。整体内容如下:
重置下demo项目
为了方便后续的知识点学习,我们把一些不需要的内容先全部删除掉,包括style.css、HellowWord.vue等等文件。只保留必要的内容:
同时将App.vue的内容重置为一个空的单文件组件:
<!--负责 HTML 结构的部分,所有展示的内容都在这里定义。-->
<template>
</template>
<!--逻辑的“后台”,数据、方法和生命周期钩子全在这里,`JavaScript`的代码就编写在这个标签下。-->
<script setup>
</script>
<!--用``CSS``定义组件样式,加上``scoped``后样式只会在这个组件内生效,不会影响别的地方。-->
<style scoped>
</style>
index.html的内容也可以修正下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>方才coding</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/main.js"></script>
</body>
</html>
重新运行vue3项目:
插值表达式-取值
接下来,我们将直接编写App.vue的内容,来学习响应式数据。
要让数据和页面互动起来,Vue 给我们提供了插值表达式。简单来说,就是在 HTML 中插入{{ }}双大括号,把变量展示出来。
场景示例:比如,想在页面上展示网站名称,我们可以这样写:
<h1>欢迎来到:{{webName}}</h1>
只要给webName一个值,页面就会实时更新。如果网站名字从“方才”变成“方才coding“,这个变化会立刻反映到页面上。不用手动更新 DOM,Vue 会自动帮我们完成。那如何赋值呢?
基础响应式
接下来,是 Vue 响应式的两位主角——ref()和reactive()。它们能让你的数据在变动时自动更新视图,具体选哪个,还得看你的数据结构。
ref():让数据成为响应式
我们可以先通过ref()来动态传值:
<!--负责 HTML 结构的部分,所有展示的内容都在这里定义。-->
<template>
<h1>欢迎来到:{{ webName }}</h1>
</template>
<!--逻辑的“后台”,数据、方法和生命周期钩子全在这里,`JavaScript`的代码就编写在这个标签下。-->
<script setup>
import {ref} from "vue";
const webName = ref("方才coding")
</script>
<!--用``CSS``定义组件样式,加上``scoped``后样式只会在这个组件内生效,不会影响别的地方。-->
<style scoped>
h1 {
color: goldenrod;
}
</style>
结合之前学习的JavaScript,我们增加一个按钮,来改变变量webName的值,让数据动起来看看效果:
<!--负责 HTML 结构的部分,所有展示的内容都在这里定义。-->
<template>
<h1>欢迎来到:{{ webName }}</h1>
<button @click="randomName">加随机数</button>
</template>
<!--逻辑的“后台”,数据、方法和生命周期钩子全在这里,`JavaScript`的代码就编写在这个标签下。-->
<script setup>
import {ref} from "vue";
const webName = ref("方才coding")
const randomName = () => {
webName.value = "方才coding +" + Math.random();
}
</script>
<!--用``CSS``定义组件样式,加上``scoped``后样式只会在这个组件内生效,不会影响别的地方。-->
<style scoped>
h1 {
color: goldenrod;
}
</style>
注意点:
ref()提供的变量,在script标签的代码中的函数使用,需要通过xxx.value的形式进行访问;- 在
template标签中访问,使用插值表达式,直接使用变量即可,因为<script setup>中的setup会自动解包。
reactive():让对象成为响应式
另一种声明响应式状态的方式,即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:
const webInfo = reactive({
name: "方才coding2",
author:"方才"
})
使用reactive包装对象,然后直接修改对象的属性,页面就会自动更新。完整的代码示例:
<!--负责 HTML 结构的部分,所有展示的内容都在这里定义。-->
<template>
<h1>欢迎来到:{{ webName }}</h1>
<button @click="randomName">加随机数</button>
<h2>欢迎来到:{{ webInfo.name }}</h2>
<h3>作者:{{ webInfo.author }}</h3>
</template>
<!--逻辑的“后台”,数据、方法和生命周期钩子全在这里,`JavaScript`的代码就编写在这个标签下。-->
<script setup>
import {reactive, ref} from "vue";
const webName = ref("方才coding")
const webInfo = reactive({
name: "方才coding2",
author:"方才"
})
const randomName = () => {
webName.value = "方才coding +" + Math.random();
webInfo.name = "方才coding2 +" + Math.random();
}
</script>
<!--用``CSS``定义组件样式,加上``scoped``后样式只会在这个组件内生效,不会影响别的地方。-->
<style scoped>
h1 {
color: goldenrod;
}
</style>
两者的区别
通过上面的demo,我们发现ref()和reactive()都可以做到响应式更新数据的效果。那两者有什么区别呢?方才兄这里针对使用层面做一些简单的总结,供大家参考(官方更建议使用 ref() 作为声明响应式状态的主要 API):
| 对比项 | ref() | reactive() |
|---|---|---|
| 支持的数据类型 | 可以持有任何类型的值,包括原始类型、深层嵌套的对象、数组或者 JavaScript 内置的数据结构 | 只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 string、number 或 boolean 这样的原始类型。 |
| 值修改 | 当持有的是对象类型时,可以直接替换整个对象。 | 不能替换整个对象,当使用let修饰时,替换整个对象,会导致与第一个引用的响应性连接的丢失。 |
简单通过代码演示下区别点:
- 数据类型:
-
值修改:
const webInfoRef = ref({ name: "方才coding-ref", author:"方才" }) // 只能使用 let 修饰符,因为在下面的方法中会导致引用值的改变 let webInfo = reactive({ name: "方才coding-reactive", author:"方才" }) const randomName = () => { webInfoRef.value = {name:"randomName 替换了" + Math.random()} webInfo = {name:"randomName 替换了" + Math.random()}; webInfo.author = "randomName" + Math.random() }
计算属性:节省计算资源的小帮手
当一个数据依赖于另一个数据时,你可以考虑用computed()。它能把计算逻辑集中在一起,提高代码的可读性,并且只有当依赖的数据发生变化时才重新计算。
场景示例:const userCtTotal = computed(() => webInfo.userCt + webInfoRef.value.userCt),当依赖的数据发生变化时就会重新计算。
<!--负责 HTML 结构的部分,所有展示的内容都在这里定义。-->
<template>
<h1>ref()</h1>
<h2>欢迎来到:{{ webInfoRef.name }}</h2>
<h3>作者:{{ webInfoRef.author }}</h3>
<h3>userCt:{{ webInfoRef.userCt }}</h3>
<button @click="randomRef">testRef</button>
<h1>reactive</h1>
<h2>欢迎来到:{{ webInfo.name }}</h2>
<h3>作者:{{ webInfo.author }}</h3>
<button @click="testReactive">testReactive</button>
<p>用户总数{{ userCtTotal }}</p>
</template>
<!--逻辑的“后台”,数据、方法和生命周期钩子全在这里,`JavaScript`的代码就编写在这个标签下。-->
<script setup>
import {computed, reactive, ref} from "vue";
const webInfoRef = ref({
name: "方才coding-ref",
author: "方才",
userCt: 12
})
// 只能使用let修饰符,因为在下面的方法中会导致引用值的改变
let webInfo = reactive({
name: "方才coding-reactive",
author: "方才",
userCt: 13
})
const randomRef = () => {
webInfoRef.value = {name: "randomRef 替换了" + Math.random()}
webInfoRef.value.userCt = Math.ceil(Math.random() * 10);
}
const testReactive = () => {
webInfo.name = "testReactive 替换了" + Math.random()
webInfo.userCt = Math.ceil(Math.random() * 10);
}
const userCtTotal = computed(() => webInfo.userCt + webInfoRef.value.userCt)
</script>
<!--用``CSS``定义组件样式,加上``scoped``后样式只会在这个组件内生效,不会影响别的地方。-->
<style scoped>
h1 {
color: goldenrod;
}
</style>
侦听器:灵活应对数据变化
虽然 Vue 能自动追踪大部分数据变动,但当你需要在数据变化时执行一些特定逻辑,比如发起请求、写入日志,就可以用watch()来实现。
场景示例:比如想在用户总数发生变更时,做一个弹窗提醒:
watch(
// 第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组;
userCtTotal,
// 回调函数,默认提供变化前后的参数
(newValue,oldValue)=>{
let msg = `用户总数变化啦!变化前:【${oldValue}】,变化后:【${newValue}】`;
alert(msg)
}
)
每当userCtTotal的值变化时,watch都会触发,帮你执行相关的逻辑。
倾听器有3个参数:
watch(
// 第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组;
userCtTotal,
// 第一个参数是回调函数,默认提供变化前后的参数
(newValue, oldValue) => {
let msg = `用户总数变化啦!变化前:【${oldValue}】,变化后:【${newValue}】`;
alert(msg)
},
// 第三个参数是可选配置:三个可选配置
{immediate: true, deep: true, once: true}
)
可选配置说明:
immediate:watch默认是懒执行的:仅当数据源变化时,才会执行回调。希望在创建侦听器时,立即执行一遍回调,可以通过传入immediate: true选项来强制侦听器的回调立即执行,默认为false;deep参数:(默认值)表示仅监视对象的顶层属性的变化,deep: true则表示递归监视对象所有嵌套属性的变化;once:每当被侦听源发生变化时,侦听器的回调就会执行。如果希望回调只在源变化时触发一次,请使用once: true选项,默认为false。
仅了解:只读和浅响应式
虽然上面几种已经能满足很多场景需求,但 Vue 还为我们提供了更深入的优化手段。
readonly与shallowReadonly:只读保护数据
当你希望一个数据不被更改时,可以使用readonly()或者shallowReadonly()。前者是完全只读,后者只针对对象的外层属性。这样可以避免一些误操作导致数据污染。
import { readonly, shallowReadonly } from 'vue';
const readOnlyUser = readonly({ name: '方才兄' });
const shallowReadOnlyUser = shallowReadonly({ name: '方才兄' });
用这两个方法创建的数据,开发中就不用担心它会被误改,非常适合做常量配置等数据。
shallowRef与shallowReactive:浅层响应提升性能
Vue 默认是深层响应式,但有时只需要外层响应,这时候shallowRef和shallowReactive就是理想选择。它们只追踪对象的第一层属性,内部属性不会响应式更新,可以优化性能。
场景示例:比如管理一大批用户信息,但并不关心用户的每一项详细信息。
import { shallowReactive } from 'vue';
const users = shallowReactive([{ name: '方才兄', age: 28 }, { name: '小明', age: 24 }]);
users.push({ name: '小红', age: 21 }); // 新增的用户会触发响应,但不会追踪每个用户的详细变化
使用浅层响应性,节省了大量性能开销,适合大量数据和不频繁更新的场景。
结语
掌握了 Vue 的响应式,你在数据和视图之间就如鱼得水,开发起来畅通无阻。记得多动手实践,用ref()、reactive()实现基本功能,再通过computed和watch优化逻辑。更高级的功能如readonly和浅响应式,可以根据场景选择。关注方才兄,带你 Vue 路上从入门到进阶,代码效率飞起!---
近期更新计划
近期更新计划(有需要的小伙伴,记得点赞关注哟!)
- 输出
vue、router、elementplus等前端框架技术文章,期望能帮助大家快速建立相关的知识体系;- 基于vue3+springboot3的前后端分离的博客系统已经开源啦,欢迎大家star!gitee.com/fangcaicodi…
“学编程,一定要系统化”——若你也是系统学习的践行者,记得点赞关注,期待与你一起 From Zero To Hero!