概念了解:值类型与引用类型
在vue中乃至很多门语言中,值类型与引用类型的概念是通用的。基本类型也称为值类型,字符串(Sring)、布尔值(Boolean)和数字(Number)这些都是基本类型(primitive type)。
非基本类型也可以理解为引用类型,数组(Array)和对象(Object)都属于引用类型(reference type)。
区别大致如下:
基本类型:
基本类型/值类型(primitive type / value type)的变量直接保存的就是数据。
引用类型:
引用类型(reference type)存放的值是指向数据的引用(reference),而不是数据本身。
Vue单向数据流特性
刚开始看这个名词,可能会认为是:数据只能够从父组件传到子组件,而不能从子组件传到父组件。其实这是不对的!
从父组件传到子组件很简单,可以通过参数传递对吧,但子组件传到父组件呢?其实是可以通过事件进行传递,或者通过数据绑定的方式,将数据传到父组件上。
那什么是单向数据流呢?引用自网上:
Vue的单向数据流指的是,数据在父组件中被定义和更新,然后通过props的形式传递给子组件,子组件可以读取这些props,但不能直接修改它们。
概括:子组件只能读取父组件传来的数据,而不能修改,因为这些数据是read-only。父组件可以改,并且父组件改了之后所有子组件的这个值都会跟着改,单向传递。
为什么要有单向数据流机制
简单而言,就是为了保护数据。
如果父组件的数据在子组件中能够被更改,那子组件再传给子子组件,如果子子组件再做更改,那是不是每个拿到这个数据的组件都要去更新这个值呢…
这样下去会造成很多不必要的问题,如果出问题了调试也是很难调试出来的,为了便于结果预测以及提供更好的性能,Vue 就有了单向数据流机制。
实操比较
用上篇文章的代码来分析:
<script setup>
let {url} = defineProps({
url: {
required: true
},
});
url+=' changed'
console.log(url)
</script>
控制台输出如下:
好像跟上面说的不太一样,可以修改啊?
其实不然,这涉及到解构赋值。
let {url} = defineProps(...)
这行代码其实等价于以下代码:
const props = defineProps({
// ...
});
const url = props.url;
创建新变量: 解构赋值会创建一个新的变量 url
,并将 defineProps
返回的对象中的 url
属性的值赋给这个新变量。
原来是这样~
那,我这样拿的数据总不能修改了吧:
let props = defineProps({
url: {
required: true
},
});
props.url+=' changed'
console.log(props.url)
还真是,又学到一点东西了!
父组件数据更新
既然是单向传递,那父组件数据更新是不是就能直接影响子组件呢?试一下:
子组件新定义参数 a
,数字类型,希望在父组件中点击一次图片 a
就加一,用 <span> 将值显示数来查看。代码如下:
// 子组件
<script setup>
let props = defineProps({
url: {
required: true
},
a:{
type: Number
}
});
</script>
<template>
<img class="size" :src="url" :alt="url"/>
<span>{{props.a}}</span>
</template>
// 父组件
<script setup>
<script setup>
import swpier from './components/swiper.vue'
import url from '@/assets/logo.svg'
let num = 1
function onClick(){
num ++
console.log('父组件:',num)
}
</script>
<template>
<swpier :url="url" :a="num"></swpier>
<button @click="onClick">click</button>
</template>
在页面中,尝试点击,父组件监听到的 num 是更新了,但是子组件里 a
的值没变!咋回事?
这是因为 a
只是个变量,但不是响应式对象!
把在父组件中把 num 修改为响应式对象就行了!
父组件更新代码如下:
<script setup>
import swpier from './components/swiper.vue'
import url from '@/assets/logo.svg'
import {ref} from "vue"; // 在 vite 中使用这种方式引用静态资源
let num = ref(1)
function onClick(){
num.value ++
console.log('父组件:',num.value)
}
</script>
实操!
可以看到,子组件父组件的数据都实现了更新,完事!
引用数据类型修改
上面实验了基本数据类型,那引用数据类型呢?如果传递的是对象呢,子组件能否修改?
更新子组件代码,新增 obj
参数,接收对象,并尝试对 obj
对象进行修改:
<script setup>
let props = defineProps({
url: {
required: true
},
a:{
type: Number
},
obj:{
type: Object
}
});
props.obj = {name:'new name'}
console.log(props.obj)
</script>
父组件传递参数,传入 obj
对象,并带有一个属性 name
:
<swpier :url="url" :a="num" :obj="{name:'test'}"></swpier>
结果查看:
显然修改失败,这依旧符合单向数据传递原则。
那如果不是修改 obj
,而是修改 obj
的属性呢?
子组件修改对象的代码改成如下:
props.obj.name = 'new name'
console.log(props.obj.name)
再看结果:
可以了?为什么?
这是因为直接修改 obj
是修改了 obj
的指向,Vue 是监测到的,按照单向数据传递原则是不给的,保护数据!你修改了obj
下的属性,obj
还是同个对象,只不过里面的数据被你修改了,Vue 管不到,所以才能够修改。
但是,这并不意味着修改对象的属性这种行为是推荐的,还是按规范来,可以避免很多问题!
补充
响应式对象
是用来做双向数据绑定的。基本数据类型用 ref()
封装响应式对象,引用数据类型使用reactive()
封装响应式对象!这个是 vue 的基础知识,可以去官方文档查看。