- 基本数据类型(单类型):除Object。 String、Number、boolean、null、undefined。
- 引用类型:object。里面包含的 function、Array、Date。
响应式系统 API
ref reactive
vue3
通过ref reactive
来定义响应式数据ref
我们用来将基本数据类型定义为响应式数据,其本质是基于Object.defineProperty()
- 基本数据类型(单类型):除
Object。String、Number、boolean、null、undefined
。
- 基本数据类型(单类型):除
- 重新定义属性的方式来实现(ref更适合定义基本数据类型)
reactive
用来将引用类型定义为响应式数据,其本质是基于 **Proxy
**实现对象代理- 引用类型:
object
。里面包含的function、Array、Date
。
- 引用类型:
// 基本类型
const nub = ref(0)
const str = ref('inline')
const boo = ref(false)
// 引用类型
const obj = reactive({
name:'inline',
age:'18'
})
const arr = reactive(['0','1','2'])
#reactive
接收一个普通对象然后返回该普通对象的响应式代理。等同于 2.x 的 Vue.observable()
const obj = reactive({ count: 0 })
响应式转换是“深层的”:会影响对象内部所有嵌套的属性。基于 ES2015 的 Proxy 实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象。
类型定义
function reactive<T extends object>(raw: T): T
ref
接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value
。
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
如果传入 ref 的是一个对象,将调用 reactive
方法进行深层响应转换。
模板中访问
当 ref 作为渲染上下文的属性返回(即在
setup()
返回的对象中)并在模板中使用时,它会自动解套,无需在模板内额外书写.value
:<template> <div>{{ count }}</div> </template> <script> export default { setup() { return { count: ref(0), } }, } </script>
作为响应式对象的属性访问
当 ref 作为 reactive 对象的 property 被访问或修改时,也将自动解套 value 值,其行为类似普通属性:
const count = ref(0) const state = reactive({ count, }) console.log(state.count) // 0 state.count = 1 console.log(count.value) // 1
- 注意如果将一个新的 ref 分配给现有的 ref, 将替换旧的 ref:
const otherCount = ref(2) state.count = otherCount console.log(state.count) // 2 console.log(count.value) // 1
注意当嵌套在 reactive
Object
中时,ref 才会解套。从Array
或者Map
等原生集合类中访问 ref 时,不会自动解套:const arr = reactive([ref(0)]) // 这里需要 .value console.log(arr[0].value) const map = reactive(new Map([['foo', ref(0)]])) // 这里需要 .value console.log(map.get('foo').value)
类型定义
interface Ref<T> { value: T } function ref<T>(value: T): Ref<T>
有时我们可能需要为 ref 做一个较为复杂的类型标注。我们可以通过在调用
ref
时传递泛型参数来覆盖默认推导:const foo = ref<string | number>('foo') // foo 的类型: Ref<string | number> foo.value = 123 // 能够通过!
#readonly
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依赖追踪
console.log(copy.count)
})
// original 上的修改会触发 copy 上的侦听
original.count++
// 无法修改 copy 并会被警告
copy.count++ // warning!
toRefs
toRefs会将我们一个响应式的对象转变为一个普通对象,然后将这个普通对象里的每一个属性变为一个响应式的数据
当我们用
...扩展运算符
时我们得到的只是一个普通类型的数值,并不是一个响应式数据
<div>
<p>姓名:{{ name }}</p>
<p>性别:{{ gender }}</p>
<p>年龄:{{ age }}</p>
</div>
setup(){
const obj = reactive({
name:'inline',
gender:'男',
age:'18',
})
return{
...toRefs(obj),
}
}
- 注意 在
<script setup>
中 不可以使用 解构赋值
const user = reactive({
name:'aa',
age:11,
height:180
})
return {
...toRefs(user)
}
toRefs()函数可以将响应式对象转换为普通对象,但该普通对象中的每个属性都是一个单独的ref对象。
注意这个词 引用类型 ,这个时候我们进行解构(浅拷贝)之后,指向的还是同一个地址,因此响应式也不会丢失了。 toRef / toRefs
toRef 和 toRefs 可以用来复制 reactive 里面的属性然后转成 ref
- 而且它既保留了响应式,也保留了引用,也就是你从 reactive 复制过来的属性进行修改后,除了视图会更新,原有 ractive 里面对应的值也会跟着更新
不加 s 和 加 s 的区别就是这样:
- toRef: 复制 reactive 里的单个属性并转成 ref
- toRefs: 复制 reactive 里的所有属性并转成 ref
toRef
作用:创建一个 ref 对象,其 value 值指向另一个对象中的某个属性。 语法:const name = toRef(person, ' name ')
应用:要将响应式对象中的某个属性单独提供给外部使用时 扩展:toRefs
与 toRef 功能一致,但可以批量创建多个 ref 对象 语法:toRefs(person)
proxy
<script>
//源数据
let person = {
name:"张三",
age:18
}
const p = new Proxy(person,{
//有人读取p的某个属性时调用
get(target, p, receiver) {
console.log(`有人读取了p身上的${p}属性`);
//return target[p]
return Reflect.get(target,p)
},
//有人修改、增加p的某个属性时调用
set(target, p, value, receiver) {
console.log(`有人修改了p身上的${p},我要去更新界面了`);
//target[p] = value
Reflect.set(target,p,value)
},
//有人删除p的某个属性时调用
deleteProperty(target, p) {
console.log(`有人删除了p身上的${p},我要去更新界面了`);
//return delete target[p]
return Reflect.deleteProperty(target,p)
}
})
</script>
- 返回 person,然后取值时使用
person.name、person.age
等,如果我们想返回它的某些字段,就需要使用toRef
- 例如返回
name
属性name:toRef(person, "name")
这样才能让数据依然是响应式的
#computed
接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
或者,接受一个具有 get
和 set
函数的对象,用来创建可写的 ref 对象。
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
类型声明:
// 只读的
function computed<T>(
getter: () => T,
debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>
// 可写的
function computed<T>(
options: {
get: () => T
set: (value: T) => void
},
debuggerOptions?: DebuggerOptions
): Ref<T>
interface DebuggerOptions {
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
}
interface DebuggerEvent {
effect: ReactiveEffect
target: any
type: OperationTypes
key: string | symbol | undefined
}
vue3中reactive赋值,不能响应式变化
- 在使用vue3中,使用reactive创建的对象或者数组进行赋值时,可以正常赋值,但是不会触发响应式变化。
let obj = {
name:张三,
age:23
}
let reactive = reaciive({})
reactive = obj // 可以正常赋值,但是不能响应式变化
原因
- vue3官方文档说明:
reactive()
返回一个对象的响应式代理- 为什么不生效: 每次直接把一个对象或者数组赋值给reactive创建的对象或数组时,导致reactive创建的响应式对象被新赋值的直接代理,再vue3中操作的都是proxy代理对象,所以失去了响应式
- 在vue3中不管是对象还是数组都不能直接将整个数据进行赋值,这样会造成reactive定义的响应式失效
- 通俗说: 就像对象的地址被替换,就不是原来的那个对象了
- 解决方法
- 不是直接赋值,包裹一层
可以直接对它对对象内进行赋值,不是重新赋值,不会失去响应式
let obj = reactive{
arr = [1,2,3]
})
obj.arr = [4,5,6] // 还是响应式
- 直接使用ref ref赋值时,包裹了一层对象
let arr = reactive([1,2,3])
let arr1 = ref([1,2,3]) // ref重新赋值时,通过.value赋值
为什么会这样 vue3的响应式原理是通过es6的proxy实现的
proxy 的使用本身就是对于 对象的拦截, 通过new Proxy 的返回值,触发get和set方法,es6的Reflect(反射),动态对被代理对象的相应属性进行特定的操作
proxy是基于对象的拦截,如果本身是个原始值时,就拦截不到,失去了,所以ref本身实现响应式会使用
.value
let obj = {
name: "许七安",
age: 23,
wife: {
name: "洛玉衡",
},
hobby: ["勾栏听曲", "插花弄玉"],
};
let proxy = new Proxy(obj, {
get: function (target, key) {
console.log("触发get");
return Reflect.get(target, key);
},
set: function (target, key, value) {
console.log("触发set");
return Reflect.set(target, key, value);
},
});
console.log((proxy.name = "许银锣"));
console.log((proxy.wife.name = "慕楠栀")); // 当修改更深的一层对象时,不会触发set事件,但是会修改
console.log(proxy);
发现当嵌套一层时,不会触发响应式事件,无法拦截下来,所以这个时候就需要嵌套一层
let obj = {
name: "许七安",
age: 23,
wife: {
name: "洛玉衡",
},
hobby: ["勾栏听曲", "插花弄玉"],
};
function reactive(obj) {
return new Proxy(obj, {
get: function (target, key) {
console.log("触发get");
// 判断如果是个对象在包装一次,实现深层嵌套的响应式
if (typeof target[key] === "object") {
return reactive(target[key]);
};
return Reflect.get(target, key);
},
set: function (target, key, value) {
console.log("触发set");
return Reflect.set(target, key, value);
},
});
}
const proxy = reactive(obj)
console.log((proxy.name = "许银锣"));
console.log((proxy.wife.name = "慕楠栀")); // 这样就可以监听到set
console.log(proxy);
这样就是.value
嵌套一层实现响应式监听
assign
const dialogRuleForm = reactive({
title: "",
status: "",
author: "",
});
fetchApi({id: row.id})
.then((res) => {
// 如果 res.data 是一个对象
Object.assign(dialogRuleForm, res.data);
// 如果 res.data 是一个数组
Object.assign(dialogRuleForm, {...res.data});
})
setup() {
// 需要一个带默认值的数组list;
let list = reactive([{id: 1, name: 'Andy'}])
// 每次触发事件重置list,把新值放入,此种方式不会触发视图更新
const checkBtn = () => {
// 此时重置操作 地址指向变成一个新的地址,不会触发视图更新
list = [{id: 1, name: 'Andy'}]
list.push({id: 2, name: 'Lucy'})
}
// --------------------------------------------------
// 正确的重置处理方法如下:使用reactive将数组包裹到一个对象中
let list = reactive({
data: [{id: 1, name: 'Andy'}]
});
const checkBtn = () => {
list.data = [{id: 1, name: 'Andy'}]
list.data.push({id: 2, name: 'Lucy'})
}
// 或者使用ref
let list = ref([{id: 1, name: 'Andy'}]);
const checkBtn = () => {
list.value = [{id: 1, name: 'Andy'}]
list.value.push({id: 2, name: 'Lucy'})
}