002-Ref与Reactive-目录
Ref
接受一个内部值并返回一个响应式且可变的 ref
对象。ref 对象仅有一个 .value
property,指向该内部值。
ref案例
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
let message: string = "我是message"
const changeMsg = () => {
message = "change msg"
}
</script>
以上案例是无法改变 message
的值,因为 message
不是响应式的无法被 vue 跟踪到。需要改成 ref 才能实现响应式变化。
<template>
<div>
<button @click="change">change</button>
<div>{{ name }}</div>
</div>
</template>
<script setup lang='ts'>
import { ref } from 'vue'
const name = ref('wangjw')
const change = () => {
name.value = "change msg";
console.log('name', name);
}
</script>
ref与Ref
ref
会根据初始化时的值推导其类型。
import { ref } from 'vue'
// 推导出的类型:Ref<number>
const year = ref(2020)
// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'
为 ref
内的值指定一个更复杂的类型,可以通过使用 Ref
这个类型。
import { ref } from 'vue'
import type { Ref } from 'vue'
// string类型 或者 number类型
const year: Ref<string | number> = ref('2020')
// 可以修改
year.value = 2020
在调用 ref()
时传入一个泛型参数
,可以覆盖默认的推导行为。
// 得到的类型:Ref<string | number>
const year = ref<string | number>('2020')
// 可以修改
year.value = 2020
指定了一个泛型参数
但没有给出初始值
,那么最后得到的就将是一个包含 undefined
的联合类型
。
// 推导得到的类型:Ref<number | undefined>
const n = ref<number>()
ifRef
判断是不是一个ref对象。
import { ref, Ref, isRef } from 'vue'
let message: Ref<string | number> = ref("我是message")
let notRef: number = 123
const changeMsg = () => {
message.value = "change msg"
console.log(isRef(message)); // true
console.log(isRef(notRef)); // false
}
shallowRef
创建一个跟踪自身 .value
变化的 ref
,但不会使其值也变成响应式的。
ref
深层次的响应;shallowRef
浅层次的响应。
注意:ref和shallowRef不能放在一块书写,会影响shallowRef,使他的值改变,造成视图的更新。
// 修改其属性是非响应式的,以下的书写方式是不会改变视图的
<template>
<div>
<button @click="changeMsg">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 changeMsg = () => {
message.value.name = '李四'
}
</script>
// 下面的修改value的方式是可以被监听到的,从而改变视图
<script setup lang="ts">
import { Ref, shallowRef } from 'vue'
type Obj = {
name: string
}
let message: Ref<Obj> = shallowRef({
name: "张三"
})
const changeMsg = () => {
message.value = { name: "李四" }
}
</script>
triggerRef
强制更新页面DOM。
customRef
自定义ref。
customRef
是个工厂函数
要求我们返回一个对象,并且实现 get
和 set
适合去做防抖之类的业务。
<template>
<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>
dom元素的ref
<template>
<div ref="dom">我是dom元素</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const dom = ref<HTMLDivElement>()
// 放到可读到元素的方法中即可打印。
console.log(dom.value?.innerText)
</script>
Reactive
与ref的共同点
ref和reactive都是将一个数据改变为响应式数据。
与ref的不同点
ref支持所有数据类型,reactive仅支持引用类型。比如 Array、Object、Map、Set等。
ref取值或赋值都需要使用.value
,但是reactive不需要。
数组的异步赋值问题
reactive的数组异步赋值是通过proxy去代理的,直接去赋值会将其覆盖,使其响应式失效,导致页面不去更新。
解决方案一:使用push
import { reactive } from 'vue'
let person = reactive<number[]>([])
setTimeout(() => {
const arr = [1, 2, 3]
person.push(...arr)
console.log(person);
},1000)
解决方案二:使用解构(包裹一层对象)
type Person = {
list?:Array<number>
}
let person = reactive<Person>({
list:[]
})
setTimeout(() => {
const arr = [1, 2, 3]
person.list = arr;
console.log(person);
},1000)
readonly
将一个reactive值变为只读的。
import { reactive,readonly } from 'vue'
const number = reactive({count:1})
const numberCopy = readonly(number)
// numberCopy是无法修改的
// numberCopy.count++
// numberCopy会受原始数据的影响,即number变化时,numberCopy也会变化。
number.count++
shallowReactive
只能对浅层的数据有影响。如果是深层的数据只会改变值,不会改变视图。
浅层:即只会影响到第一层,并发生视图改变。
备注:如果和reactive同时书写改变,会被影响导致深层的数据也改变视图。
toRef
toRef
只能修改对象的值,所以对于一个非响应式对象数据,是不会更新视图的,只会改变值。
如果改变的原始对象是响应式的,则会改变值并更新视图。
语法:toRef(对象,对象的一个属性)
obj = {
name:'张三'
}
const tempObj = toRef(obj,'name')
应用:需要一个变量等同于另一个响应式对象的某个属性,改变这个变量,响应式对象的该属性也同步变化。对于深层次的对象,防止每次使用都用 obj.abc.def.ghe
这种形式来使用。
本质:
// 如果是ref对象直接返回,否则调用 ObjectRefImpl 创建一个类ref对象。
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
defaultValue?: T[K]
): ToRef<T[K]> {
const val = object[key]
return isRef(val)
? val
: (new ObjectRefImpl(object, key, defaultValue) as any)
}
// 类ref对象只是做了值的改变,并未处理"收集依赖"和"触发依赖的过程"。
// 所以普通对象无法更新视图。
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly __v_isRef = true
constructor(
private readonly _object: T,
private readonly _key: K,
private readonly _defaultValue?: T[K]
) {}
get value() {
const val = this._object[this._key]
return val === undefined ? (this._defaultValue as T[K]) : val
}
set value(newVal) {
this._object[this._key] = newVal
}
}
toRefs
批量创建ref对象,一般用于结构reactive对象,方便使用。
import { reactive, toRefs } from 'vue'
const obj = reactive({
name: '张三',
year: 18
})
let { name, year } = toRefs(obj)
year.value++
console.log(name, year);
本质:
// 把reactive对象的每一个属性都变成了ref对象,循环调用了toRef。
export type ToRefs<T = any> = {
[K in keyof T]: ToRef<T[K]>
}
export function toRefs<T extends object>(object: T): ToRefs<T> {
if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`)
}
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}
toRaw
将响应式对象转化为普通对象。
本质:
// 通过 ReactiveFlags 枚举值,取出proxy对象的原始对象。
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw' // 原始对象
}
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}