ref
创建一个响应式数据,一般来说用于创建简单类型的响应式对象,比如String
、Number
类型
const name = ref('tom')
可以看到,ref
方法将这个字符串进行了一层包裹,返回的是一个RefImpl
类型的对象,译为引用的实现(reference implement)
,在该对象上设置了一个不可枚举的属性value
,所以使用name.value
来读取值。
正如上面所说,ref
通常用于定义一个简单类型,那么是否可以定义一个对象或者数组?
const obj = ref({
age: 12,
sex: 'man',
})
控制台可以看到,对于复杂的对象,值是一个被proxy
拦截处理过的对象,但是里面的属性age
和sex
不是RefImpl
类型的对象,proxy
代理的对象同样被挂载到value
上,所以可以通过obj.value.age
来读取属性,这些属性同样也是响应式的,更改时可以触发视图的更新
reactive
通过上面ref的使用案例,起始不管是复杂引用类型,如array,object
等,亦或者是简单的值类型string,number
都可以使用ref
来进行定义,但是,定义对象的话,通常还是用reactive
来实现
const person = reactive({
height: 180,
})
可以看到返回的person是一个proxy
代理的对象,使用时person.height
即可
那么能否使用reactive
来定义普通类型?
const id = reactive('id是1')
可以看到**reactive
只能被用来定义对象**
ref与reactive的区别与联系
一般来说,ref
被用来定义简单的字符串或者数值,而reactive
被用来定义对象数组等
ref
定义对象时,value
返回的是proxy
,reactive
定义对象时返回的也是proxy
,而这确实存在一些联系
ref
来定义数据时,会对里面的数据类型进行一层判断,当遇到复杂的引用类型时,还是会使用reactive
来进行处理
class RefImpl {
constructor(value, _shallow) {
this._shallow = _shallow;
this.dep = undefined;
this.__v_isRef = true;
this._rawValue = _shallow ? value : toRaw(value);
this._value = _shallow ? value : toReactive(value);
}
////......
}
const toReactive = (value) => isObject(value) ? reactive(value) : value;// 判断参数是否为对象,是对象调用reactive创建,否则返回原始值
shallowRef与shallowReactive
ref
与reactive
都是对数据深度监听,不管是简单类型还是复杂的对象,只要发生改变时都出触发视图更新,对于深层次的对象来说,如果只是存在某些极少的属性容易发生更改,那么仍然监听这个庞大的对象整体属性无疑是对性能的浪费,这种情况可以使用 shallowRef
或者shallowReactive
来实现浅层次的监听
shallowRef
只监听.value
属性的值的变化,对象内部的某一个属性改变时并不会触发更新,只有当更改value
为对象重新赋值时才会触发更新
const foo = shallowRef({
c: 1,
})
const change = () => {
foo.value.c = 2 // 视图不更新
foo.value={a:1} // 视图更新
}
shallowReactive
只监听对象的第一层属性,对嵌套的对象不做响应式处理
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
const change = () => {
state.foo = 2 // 视图更新
state.nested={count:2}// 视图更新
state.nested.bar =3 // 视图不更新
}
triggerRef
用于手动强制更新shallowRef有关的副作用,更新视图,通过上面总结看到shallowRef
只监听value
属性,内部嵌套的对象改变时不触发更新。而triggerRef
的作用就是某些情况下能够强制触发刷新shallowRef
,比如某些情况下一个对象嵌套了几百层数据,但是需要对其中一层的数据做更改,其他的都不动,全变成响应式浪费性能,非响应式又无法更新视图,这时就需要更改这层数据之后,再使用triggerRef
来更新
const shallow = shallowRef({
greet: 'Hello, world'
})
shallow.value.greet = 'Hello, universe' // 视图不会更新
triggerRef(shallow) // 手动更新相关副作用,更新视图
readonly与shallowReadonly
将对象变成只读对象
readonly
接收一个普通对象或者经过reactive
、ref
处理过的响应式对象,使其变为只读对象,对其中的任何数据都不能进行更改
const original = reactive({ count: 0 })
const copy = readonly(original)
original.count++ // 仍然可以更改响应式对象
copy.count++ // 被readonly包裹后再更改会报警告
shallowReadonly
对象的第一层属性被设置成只读,嵌套的属仍然可以被更改
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2
}
})
state.foo++ //警告,不可被更改
state.nested.bar++ // 嵌套的属性仍然可以被更改
toRaw与markRaw
将响应式对象重新变成普通对象
toRaw
返回reactive
或者readonly
代理过的proxy
对象 ,或者ref
定义对象时的value
const count = ref({
a: 1,
})
const c = toRaw(count.value) // ref的value才是proxy对象
const count = reactive({
a: 1,
})
const c = toRaw(count) // reactive 对象不需要value
const roCount = readonly(count)
const c = toRaw(roCount) //readonly对象
markRaw
标记一个对象,使其永远不会变成响应式proxy,比如挂载一个第三方类库,那么这个类库不需要创建响应式,比如为一个响应式对象额外添加了一个属性用来展示列表,那么这个列表仅作展示使用,就不需要创建响应式,以避免性能的浪费
在vue2中。如果对一个响应式数对象foo
追加一个属性bar
,并不会触发视图中foo.bar
的更新,这种情况需要使用$set
来为foo
追加属性
this.foo.bar =1 // 不会更新
this.$set(this.foo,'bar',1) // 触发更新
但是在vue3中,使用的时proxy
来拦截数据,他的强大之处在于如果定义完一个响应式对象之后,再对这个对象的属性进行增删时,所追加的属性仍是响应式的,仍可以触发视图的更新
const foo =reactive({})
foo.bar =1 // 触发视图更新
但是vue3这样的做法又会存在一个问题--->某些情况下并不想让追加的数据变成响应式,这种情况需要使用markRaw
来把包裹对象
const foo =reactive({a:1})
foo.bar ={b:1} // bar属性仍是响应式
foo.bar =markRaw({c:1}) // 不是响应式
customRef
用于自定义ref
-
创建一个函数包裹
customRef
,这个函数用于传递初始值以及其他形参配置 -
customRef
为一个函数,接收两个参数track
跟踪器和trigger
触发器 -
customRef
返回一个带有get
函数和set
函数的对象,这两个函数中编写读取和写入值得操作逻辑
<input v-model="text" />
// 创建参数包裹customRef传递初始值
function useDebouncedRef(value, delay = 200) {
let timeout
// 返回customRef
return customRef((track, trigger) => {
// 返回一个带有get和set函数得对象
return {
// 读取值操作
get() {
// 设置跟踪器,跟踪这个值得改变
track()
return value // 取值
},
// 写入值操作
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue // 渎赋值
trigger() // 触发器,通知视图去获取数据
}, delay)
}
}
})
}
export default {
setup() {
return {
text: useDebouncedRef('hello') // 使用
}
}
}