CompositionAPI中的ref,reactive响应式引用的用法和原理
响应式引用
原理
通过proxy对数据进行封装,当数据变化时,触发模板等内容的更新
ref 处理基础类型的数据
1.普通的变量
【例子】
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
let name = 'LeBrown'
setTimeout(() => {
name = 'James'
}, 2000);
return {
name
}
}
})
const vm = app.mount('#root')
我们发现2秒后name的值并没有从LeBrown变成James,也就是说name并不是响应式的数据,它只是普通的变量
2.将普通的变量变成响应式的引用
在CompositionAPI中通过2个语法可以将这种普通的变量变成响应式的引用,响应式的引用的特点是当值发生变化时,页面也会跟着发生变化,是一种双向绑定的数据
【例子】
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
let { ref } = Vue;
// 'LeBrown' 变成 proxy({value: 'LeBrown'})这样的一个响应式引用
let name = ref('LeBrown')
setTimeout(() => {
name.value = 'James'
}, 2000);
return {
name
}
}
})
const vm = app.mount('#root')
【运行结果】

【备注】
- 基础类型的数据调用
ref后就会通过proxy将LeBrown变成proxy({val: 'LeBrown '})这样的响应式引用 - 使用ref将基础类型的数据
LeBrown转成一个对象{value:'LeBrown'},因此在设置值的时候就要使用name.value='xxx' - vue在做模板处理的时候会自动的做个转换,也就是说如果知道
name是被ref返回的响应式引用,会自动的调用name.value,因此再模板中只需要写{{name}}而不需要写name.value proxy没法对基础类型的数据做代理,ref底层做的事情是将基础类型的数据LeBrown转成对象{value:'LeBrown'}的形式,然后再通过proxy做代理,因此ref只适用于处理基础类型的数据,而不能处理引用数据类型的数据
reactive处理非基础类型的数据
1. 让对象上的属性值变成响应式
使用reactive实现上面的例子
【例子】
const app = Vue.createApp({
template: `
<div>{{nameObj.name}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
// { name: 'LeBrown' } 变成 proxy({ name: 'LeBrown' })这样的一个响应式引用
let nameObj = reactive({ name: 'LeBrown' })
setTimeout(() => {
nameObj.name = 'James'
}, 2000);
return {
nameObj
}
}
})
const vm = app.mount('#root')
2. 让数组中的值变成响应式
【例子】
const app = Vue.createApp({
template: `
<div>{{nameObj[0]}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
let nameObj = reactive([123])
setTimeout(() => {
nameObj[0] = 456
}, 2000);
return {
nameObj
}
}
})
const vm = app.mount('#root')
【运行结果】

【备注】
- 同
ref一样,reactive会将非基础类型的数据{name: 'LeBrown'}变成proxy({name:'LeBrown'})这样的响应式引用,使用方法也和ref类似 - 用了
Composition新的语法ref和reactive之后就可以替代之前将数据写在data中的写法(也是响应式数据),在vue3中就可以不用写data,可以写成用ref或reactive的形式定义数据
其他Composition API
1. readonly不希望是响应式而是只读的
也就是说现在不希望对nameObj中属性的值进行变更,而是希望它变成只读的
【例子】
const app = Vue.createApp({
template: `
<div>
{{nameObj[0]}}
</div>
`,
setup(props, context) {
const { reactive,readonly } = Vue;
let nameObj = reactive([123])
const copyNameObj = readonly(nameObj)
setTimeout(() => {
nameObj[0] = 456
copyNameObj[0] = 456
}, 2000);
return {
nameObj,
copyNameObj
}
}
})
const vm = app.mount('#root')
【运行结果】

也就是说,在响应式引用前加上readonly,那么这个这个响应式引用就变成只读的了
2. toRefs将解构时的基础类型值转成响应式
如果在模板中我不想写{{nameObj.name}},而是希望能把name从nameObj中解构出来,同时name也要是响应式引用
【例子】
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
let nameObj = reactive({ name: 'LeBrown' })
setTimeout(() => {
nameObj.name = 'James'
}, 2000);
const {name} = nameObj
return {
name
}
}
})
const vm = app.mount('#root')
但是这样写2秒之后,页面上的内容并没有发生变化,为什么呢?
因为nameObj它是经过reactive处理过的,它是响应式的引用
但是经过解构赋值之后,它就只是一个基础类型的数据,因此它不具备响应式的特点
Vue中提供了新的响应式的语法toRefs
【例子】
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive,toRefs } = Vue;
let nameObj = reactive({ name: 'LeBrown' })
setTimeout(() => {
nameObj.name = 'James'
}, 2000);
const {name} = toRefs(nameObj)
return {
name
}
}
})
const vm = app.mount('#root')
【备注】
toRefs原理: proxy({name: 'LeBrown'}) ===> 转成 {name: proxy({value: 'LeBrown'})}
这样调用 const {name} = toRefs(nameObj)实际上调用的是proxy({value: 'LeBrown'}
而模板中中调用{{name}},实际上是调用了{{name.value}}
3. toRef
【例子】
1. 引入的需求
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive,toRefs } = Vue;
const data = reactive({ name: 'LeBrown' })
// 解构出来 toRefs将proxy({value: 'LeBrown'}) 变成 {name: proxy({{value: 'LeBrown'}})}
const {name} = toRefs(data)
setTimeout(() => {
name.value = 'James'
}, 2000);
return {name}
}
})
const vm = app.mount('#root')
如果将解构的时候解构的不是name而是age会发生什么?
const app = Vue.createApp({
template: `
<div>{{age}}</div>
`,
setup(props, context) {
const { reactive,toRefs } = Vue;
const data = reactive({ name: 'LeBrown' })
// 解构出来 toRefs将proxy({value: 'LeBrown'}) 变成 {name: proxy({{value: 'LeBrown'}})}
const {age} = toRefs(data)
setTimeout(() => {
age.value = 'James'
}, 2000);
return {age}
}
})
const vm = app.mount('#root')
data中并没有age,把age结构出来,会生效吗,如果生效页面会不会先先是空白,再变成James
实际结果却报错了

【备注】
为什么要有toRef?
toRefs从响应式对象data中找数据,如果找不到它不会给默认的响应式的引用,而是给age一个undefined,这样由于{age}不存在于响应式对象data中,那么它就不具备响应式.而toRef就是为了解决这样的问题而产生的
2. 使用toRef改写
const app = Vue.createApp({
template: `
<div>{{age}}</div>
`,
setup(props, context) {
const { reactive,toRef } = Vue;
const data = reactive({ name: 'LeBrown' })
const age = toRef(data,"age")
setTimeout(() => {
age.value = 'James'
}, 2000);
return {age}
}
})
const vm = app.mount('#root')
【运行结果】

【备注】
当尝试从响应式对象data中取age,如果能取到就取,如果取不到就给个默认的空的响应式数据,它依然具备响应式的特性。
它主要为了应对从响应式对象中取值的时候会出现这个对象中没有这个属性,而又不希望报错,一直具备响应式特征的时候就使用toRef而不是使用toRefs
转载自:https://juejin.cn/post/7245171528123564090