3.1 ref-基本类型响应式
Vue2是把数据放在data里,在data里的数据,全都是响应式数据。而在Vue3是通过ref和reactive来实现响应式,这个ref跟标签的ref是不相同的,在后面会详细讲解标签ref。Vue3的理念是,它并不会把所有数据都设置成响应式,而是你需要哪个数据是响应式,就在哪个数据包裹一下ref(),让它变成响应式。
1、引入ref
import {ref} from 'vue'
2、把需要响应式的变量包裹ref()
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>地址:{{address}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="showTel">查看联系方式</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref} from 'vue'
let name = ref('张三')
let age = ref(18)
let tel = '13888888888'
let address = '北京昌平区宏福苑·宏福科技园'
console.log(name)
console.log(age)
// 方法
function changeName() {
name = 'zhang-san'
}
function changeAge() {
age += 1
}
function showTel() {
alert(tel)
}
</script>
在上面的示例中,只有name和age需要响应式,因此只给它们俩包裹ref()。包裹了ref()之后,它会变成了一个RefImpl实例对象,它里面有几个属性,但只有value才是我们能够使用的,带下划线的都是它内部属性。
既然它已经变成了RefImpl实例对象,而且值是在它的value属性里,那么是否应该写成name.value读取呢?注意:在模板里不需要加.value,但在ts里则需要加上.value才能获取到值
// 方法
function changeName() {
name.value = 'zhang-san'
}
function changeAge() {
age.value += 1
}
3、代码示例
3.2 reactive-对象类型响应式
ref主要是针对基本类型的响应式,如果是对象类型我们可以使用reactive,用法跟ref一样,也是使用reactive把对象进行包裹。
3.2.1 对象响应式
1、引入reactive
import {reactive} from 'vue'
2、把对象包裹reactive()
<template>
<div class="person">
<h2>汽车信息:一辆{{car.brand}}车,价值{{car.price}}万</h2>
<button @click="changePrice">修改汽车的价格</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive} from 'vue'
// 数据
let car = reactive({brand:'奔驰',price:100})
console.log(car)
// 方法
function changePrice(){
car.price += 10
}
</script>
在上面的示例中,包裹了reactive()之后,对象就变成了响应式,而且它是一个Proxy实例对象,它里面的target属性就是存放着对象的属性值。
注意:reactive定义的响应式是深层次的,就是哪怕你的对象很复杂,例如obj.a.b.c,它一样可以监听得到。
3.2.2 数组响应式
数组响应式跟对象一样,因为数组也是属于对象范畴,使用的时候也直接包裹Reactive()即可。
<template>
<div class="person">
<h2>游戏列表:</h2>
<ul>
<li v-for="g in games" :key="g.id">{{g.name}}</li>
</ul>
<button @click="changeFirstGame">修改第一个游戏的名字</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive} from 'vue'
// 数据
let games = reactive([
{id:'aysdytfsatr01',name:'王者荣耀'},
{id:'aysdytfsatr02',name:'原神'},
{id:'aysdytfsatr03',name:'三国志'}
])
// 方法
function changeFirstGame(){
games[0].name = '流星蝴蝶剑'
}
</script>
3.2.3 示例代码
3.3 ref-对象类型响应式
reactive是只能定义对象类型,它不能定义基本类型。而ref不单是可以定义基本类型的响应式,它也可以定义对象类型。
1、使用ref定义对象类型
用ref定义对象类型跟基本类型是一样的,在修改值的时候也同样需要.value获取对象值再进行修改。
<template>
<div class="person">
<h2>汽车信息:一辆{{car.brand}}车,价值{{car.price}}万</h2>
<button @click="changePrice">修改汽车的价格</button>
<br>
<h2>游戏列表:</h2>
<ul>
<li v-for="g in games" :key="g.id">{{g.name}}</li>
</ul>
<button @click="changeFirstGame">修改第一个游戏的名字</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref} from 'vue'
// 数据
let car = ref({brand:'奔驰',price:100})
let games = ref([
{id:'aysdytfsatr01',name:'王者荣耀'},
{id:'aysdytfsatr02',name:'原神'},
{id:'aysdytfsatr03',name:'三国志'}
])
console.log(games)
// 方法
function changePrice(){
car.value.price += 10
console.log(car.value.price)
}
function changeFirstGame(){
games.value[0].name = '流星蝴蝶剑'
}
</script>
2、观察ref实例对象
凡是使用ref包裹之后,它都会变成RefImpl实例对象,但它的value值却是一个Proxy对象。也就是说,ref面对对象类型的时候,它的底层实际上还是由Reactive实现的。
3、示例代码
3.4 ref对比reactive
1、宏观角度
(1)ref用来定义:基本类型数据、对象类型数据
(2)reactive用来定义:对象类型数据
2、区别(在下方有详述)
(1)ref创建的变量必须使用.value
(可以使用volar
插件自动添加.value
)。
(2)reactive重新分配一个新对象,会失去响应式
(可以使用Object.assign
去整体替换)。
3、使用原则
(1)若需要一个基本类型的响应式数据,必须使用ref
。
(2)若需要一个响应式对象,层级不深,ref
、reactive
都可以。
(3)若需要一个响应式对象,且层级较深,推荐使用reactive
。
3.4.1 使用volar插件
在使用ref变量的时候,每次都得手写一个.value,在vscode里提供了一个插件叫volar,其实这个插件在vite初始化工程的时候就会提示你安装的了。
如果已经安装过,在vscode左侧的扩展按钮进入,就会看到Volar插件。
启用这个Volar插件,在左侧工具栏选择"Manage"—>选择"setting"—>选择"Volar"—>勾选"Auto Insert: Dot value"选项。
此时它就会自动识别,如果是ref变量,它会自动帮我们填入.value。
3.4.2 reactive的局限性
使用reactive定义了响应式对象后,如果重新给它赋值一个新的对象,哪怕是reactive响应式对象,它也会失去响应式的功能。请观察以下changeCar方法,页面是不会发生任何变化。
<template>
<div class="person">
<h2>汽车信息:一辆{{car.brand}}车,价值{{car.price}}万</h2>
<button @click="changeBrand">修改汽车的品牌</button>
<button @click="changePrice">修改汽车的价格</button>
<button @click="changeCar">修改汽车</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,reactive} from 'vue'
// 数据
let car = reactive({brand:'奔驰',price:100})
// 方法
function changeBrand(){
car.brand = '宝马'
}
function changePrice(){
car.price += 10
}
function changeCar(){
car = reactive({brand:'奥拓',price:1})
}
</script>
这里可以通过使用Object.assign()
合并对象来解决,这个方法只是将后面的对象属性合并到第一个对象里,但并没有改变reactive关联的对象链。
<script lang="ts" setup name="Person">
......
function changeCar(){
// car = reactive({brand:'奥拓',price:1})
Object.assign(car,{brand:'奥拓',price:1})
}
</script>
3.4.3 ref赋值新对象
对比reactive赋值新对象需要借助Object.assign(),ref并不需要,它直接对value属性赋值一个新的对象即可,响应式也毫无影响。因为ref的value不管是什么值,只要它发生改变,它就一定是响应式。
<script lang="ts" setup name="Person">
import {ref,reactive} from 'vue'
// 数据
let car = ref({brand:'奔驰',price:100})
// 方法
......
function changeCar(){
car.value = {brand:'奥拓',price:1}
}
</script>
注意:关于ref重新赋值的问题,一定是要改变它的value值,如果赋一个新的ref值,例如let a = ref(0); a = ref(9)
这样它的响应式是失效的。
3.4.4 示例代码
3.5 toRefs与toRef
3.5.1 toRefs
我们用reactive包裹了对象之后,它就变成了响应式了,假设此时我们使用解构赋值,要注意的一点是,解构赋值的变量,并不是响应式的,它只是一个普通的变量而已。
<script lang="ts" setup name="Person">
let person = reactive({
name:'张三',
age:18
})
let {name,age} = person
</script>
此时toRefs就可以派上用场了,它的作用就是可以让对象的每一个属性解构出来都变成响应式:
<script lang="ts" setup name="Person">
let person = reactive({
name:'张三',
age:18
})
let {name,age} = toRefs(person)
</script>
这样一来,name和age属性都变成了响应式,而且如果改变它们,会联动改变对象的属性值。下面我们输出name和person.name,发现它们的值是一样的,会联动修改。
<template>
<div class="person">
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}},{{nl}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive,toRefs} from 'vue'
// 数据
let person = reactive({
name:'张三',
age:18
})
let {name,age} = toRefs(person)
// 方法
function changeName(){
name.value += '~'
console.log(name.value,person.name)
}
function changeAge(){
age.value += 1
}
</script>
包裹了toRefs()之后,整个对象里的各个属性值都变成了ref对象,我们可以通过输出toRefs(person)进行观察:
3.5.2 toRef
toRef作用其实跟toRefs是相似的,toRefs是把整个对象的所有属性都变成了响应式,而toRef是指定对象的某一个属性变成响应式。它传两个参数,第一个是对象,第二个是属性名。
例如下面例子的nl变量,它是使用toRef解构age属性出来的,结果也同样是响应式:
<template>
<div class="person">
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}},{{nl}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive,toRefs,toRef} from 'vue'
// 数据
let person = reactive({
name:'张三',
age:18
})
// 使用toRefs从person这个响应式对象中,解构出name、age,且name和age依然是响应式的
// name和age的值是ref类型,其value值指向的是person.name和person.age
let {name,age} = toRefs(person)
let nl = toRef(person,'age')
// 方法
function changeName(){
name.value += '~'
console.log(name.value,person.name)
}
function changeAge(){
age.value += 1
}
</script>
3.5.3 示例代码
3.6 标签ref
了解完Vue3的响应式的ref之后,再来看看我们比较熟悉的标签的ref。其实Vue2和Vue3的标签ref使用方式是一样的,不过在获取ref元素的时候稍微有点区别。
3.6.1 使用标签ref
1、Vue2
在Vue2使用ref直接在html标签上使用,然后通过this.$refs来获取即可。
<template>
<div class="person">
<h1>中国</h1>
<h2 ref="title2">北京</h2>
<h3>尚硅谷</h3>
</div>
</template>
<script>
export default {
mounted() {
console.log(this.$refs.title2)
}
}
</script>
2、Vue3
vue3在模板上的使用跟vue2是一样的,但获取ref元素的时候,它需要用ref类型先把ref标签保存起来,然后再使用。
<template>
<div class="person">
<h1>中国</h1>
<h2 ref="title2">北京</h2>
<h3>尚硅谷</h3>
<button @click="showLog">点我输出h2这个元素</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref} from 'vue'
// 创建一个title2,用于存储ref标记的内容
let title2 = ref()
function showLog(){
console.log(title2.value)
}
</script>
3.6.2 defineExpose的使用
ref标签不只是用在普通的html元素,它还可以应用在组件标签上。假设这里存在一对父子组件,在父组件里对子组件标签使用ref的话,它获取的是子组件的示例。
父组件:
<template>
<button @click="showLog">测试</button>
<Person ref="child"/>
</template>
<script lang="ts" setup name="App">
import Person from './components/Person.vue'
import {ref} from 'vue'
let child = ref()
function showLog(){
console.log(child.value)
}
</script>
子组件:
<template>
<div class="person">
......
</div>
</template>
<script lang="ts" setup name="Person">
let a = ref(0)
let b = ref(1)
let c = ref(2)
</script>
这里子组件有a、b、c3个属性,在父组件获取子组件的时候,按理说它是可以拿到子组件的所有属性的,因为它已经把子组件的示例获取到了。
实际上父组件只拿到子组件的示例,而属性一个都拿不到。这是Vue3对子组件的一个保护机制,相当于一个人的隐私,如果想查看就得先得到对方的同意。这里可以使用defineExpose进行对属性的对外暴露,这样外部才可以访问内部属性。
子组件:
<script lang="ts" setup name="Person">
import {ref} from 'vue'
// 创建一个title2,用于存储ref标记的内容
let title2 = ref()
let a = ref(0)
let b = ref(1)
let c = ref(2)
function showLog(){
console.log(title2.value)
}
defineExpose({a,b,c})
</script>