Ref 和 Reactive 学习笔记
Ref 和 Reactive 学习笔记
先不说什么是ref和reactive是啥,先说个情景:
我们定义了一个变量叫message,然后我在模板中展示出来。同时我们设置了一个按钮,来改变这个变量的值:
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
let message: string = "我是message"
const changeMsg = () => {
message = "我变了"
console.log(message)
}
</script>
但是上面的例子点击按钮之后页面上的值并不会被改变,只有控制台中输出的值被改变了,这很对劲,因为并没有重新给模板重新赋值,如果想让模板也变,就得手动给模板重新赋值一遍,这个很烦~。所以vue搞了个骚的,用一种方式让我们可以不手动给模板重新赋值,模板上就能展示最新的内容。因此,ref() 和 reactive() 闪亮登场~
一.ref全家桶
1.1 ref()
推荐使用 ref() 函数来声明响应式状态,ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回,对于上面的例子来说,我们可以做这样的修改:
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let message = ref("我是message")
const changeMsg = () => {
message.value = "change msg"
console.log(message)
}
</script>
目前,你去点击按钮的时候,你就会发现模板中的代码也跟着修改了。
Tips:有个很容易弄混和忽略的东西:
.value;在函数中使用ref的时候一定要记得.value,但是在模板中使用ref时,我们不需要附加.value。为了方便起见,当在模板中使用时,ref会自动解包 (有一些注意事项)。
1.2 shallowRef()
ref()的浅层作用形式。和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。
<template>
<div>
<button @click="changeMsg1">change</button>
<button @click="changeMsg2">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
import { Ref, shallowRef } from 'vue'
type Obj = {
name: string
}
let message: Ref<Obj> = shallowRef({
name: "我是初始值"
})
const changeMsg1 = () => {
message.value.name = '我不会被改变'
}
const changeMsg2 = () => {
message.value = { name: "这样才会改变" }
}
</script>
Tips:说人话就是:
message.value.name = '我不会被改变'这么改不行!message.value = { name: "这样才会改变" }这么改才行.同时还要注意,
ref()和shallowRef()是不可以在一起用的,否则DOM会被强制更新(具体原因往下开)。
1.3 customRef()
创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式
customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。
一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。
<template>
<div ref="div">小满Ref</div>
<hr>
<div>
{{ name }}
</div>
<hr>
<button @click="change">修改 customRef</button>
</template>
<script setup lang='ts'>
import { ref, reactive, onMounted, shallowRef, customRef } from 'vue'
function myRef<T = any>(value: T) {
let timer:any;
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newVal) {
clearTimeout(timer)
timer = setTimeout(() => {
console.log('触发了set')
value = newVal
trigger()
},500)
}
}
})
}
const name = myRef<string>('小满')
const change = () => {
name.value = '大满'
}
</script>
<style scoped>
</style>
Tips:说实话,才疏学浅没看懂....上一段满神的代码自己悟吧。我只能理解到我自己写了一个方法实现响应式,然后在创建响应式的时候可以高一些骚操作
1.4 triggerRef()
强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。强制更新页面DOM
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
import { Ref, shallowRef,triggerRef } from 'vue'
type Obj = {
name: string
}
let message: Ref<Obj> = shallowRef({
name: "小满"
})
const changeMsg = () => {
message.value.name = '大满'
triggerRef(message)
}
</script>
Tips:这个就是为什么上面 ref 和 shallowRef 为什么不可以一起用的原因,因为在ref中他会默认调用triggerRef,所以数据就强制更新了。
1.5 isRef()
这东西没啥好说的,检查某个值是否为 ref。是就返回True,不是就返回False
let foo: unknown
if (isRef(foo)) {
// foo 的类型被收窄为了 Ref<unknown>
foo.value
}
二.reactive
2.1 reactive()
还有另一种声明响应式状态的方式,即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:
import { reactive } from 'vue'
let person = reactive({
name:"aaa"
})
person.name = "aaa"
Tips:首先说明,
reactive()不能处理基本数据类型,写就报错,其次:如果用ref去绑定对象 或者 数组 等复杂的数据类型 我们看源码里面其实也是 去调用reactive,使用reactive 去修改值无须.value
但是对于一个数组来说,这样赋值页面是不会变化的因为会脱离响应式:
let person = reactive<number[]>([])
setTimeout(() => {
person = [1, 2, 3]
console.log(person);
},1000)
有两种方式可以解决这个问题,1:使用list.push(),别问我为啥不也不知道...
第二种我知道:就直接把他当做对象的一个属性搞,就别把他当数组,对就当对象,万事大吉~
type Person = {
list?:Array<number>
}
let person = reactive<Person>({
list:[]
})
setTimeout(() => {
const arr = [1, 2, 3]
person.list = arr;
console.log(person);
},1000)
2.2 shallowReactive()
效果同shallowRef()只能对浅层的数据 如果是深层的数据只会改变值 不会改变视图:
这俩按钮,第二个就会不好用
<template>
<div>
<div>{{ state }}</div>
<button @click="change1">test1</button>
<button @click="change2">test2</button>
</div>
</template>
<script setup lang="ts">
import { shallowReactive } from 'vue'
const obj = {
a: 1,
first: {
b: 2,
second: {
c: 3
}
}
}
const state = shallowReactive(obj)
function change1() {
state.a = 7
}
function change2() {
state.first.b = 8
state.first.second.c = 9
console.log(state);
}
</script>
三.To...
3.1 toRef
可以将值、refs 或 getters 规范化为 refs (3.3+)。
也可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。
toRef()接受两个参数,一个是你要结构的对象,一个是对象中某个属性的Key.
<template>
<div>
<button @click="change">按钮</button>
{{state}}
</div>
</template>
<script setup lang="ts">
import { reactive, toRef } from 'vue'
const obj = {
foo: 1,
bar: 1
}
const state = toRef(obj, 'bar')
// bar 转化为响应式对象
const change = () => {
state.value++
console.log(obj, state);
}
</script>
Tips:这个东西换成人话就是:把响应式的对象中的某个属性结构出来,并且结构出来的还是响应式的。
注意:你结构的对象一定得是响应式的才行,用toRef结构一个普通对象是没用的
3.2 toRefs
toRef是解构出一个,toRefs就解构出一堆:
import { reactive, toRefs } from 'vue'
const obj = reactive({
foo: 1,
bar: 1
})
let { foo, bar } = toRefs(obj)
foo.value++
console.log(foo, bar);
3.3 toRaw
他更简单,是就是把响应式的搞成普通的
import { reactive, toRaw } from 'vue'
const obj = reactive({
foo: 1,
bar: 1
})
const state = toRaw(obj)
// 响应式对象转化为普通对象
const change = () => {
console.log(obj, state);
}

浙公网安备 33010602011771号