ref、unref、toRef、toRefs、isRef、customRef、shallowRef、triggerRef
refs api中的重点为:ref、toRefs、shallowRef、customRef,其次是 isRef 等。
作用:可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
语法 (源对象 , 源对象属性)````const name = toRef(person,‘name’)```
应用: 源响应式对象(toRef的第一个参数) 上的某个 property 新创建一个 ref
toRef示例
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2
// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3
请注意,这不同于:
const fooRef = ref(state.foo)
上面这个 ref 不会和 state.foo 保持同步,因为这个 ref() 接收到的是一个纯数值。
toRef() 这个函数在你想把一个 prop 的 ref 传递给一个组合式函数时会很有用:
<script setup>
import { toRef } from 'vue'
const props = defineProps(/* ... */)
// 将 `props.foo` 转换为 ref,然后传入
// 一个组合式函数
useSomeFeature(toRef(props, 'foo'))
</script>
当 toRef 与组件 props 结合使用时,关于禁止对 props 做出更改的限制依然有效。尝试将新的值传递给 ref 等效于尝试直接更改 props,这是不允许的。在这种场景下,你可能可以考虑使用带有 get 和 set 的 computed 替代。详情请见在组件上使用 v-model 指南。
即使源属性当前不存在,toRef() 也会返回一个可用的 ref。这让它在处理可选 props 的时候格外实用,相比之下 toRefs 就不会为可选 props 创建对应的 refs。
作用:
语法:toRefs(person)
原理:toRefs 会将 reactive 生成的对象的根级属性全都用 ref 转成 ref 对象,然后解构出来的都是 ref 对象,从而不丢失响应式
示例
<script lang='ts' setup>
import { reactive,toRef } from 'vue';
const state = reactive({
foo: 1,
bar: 2
})
let a = {...state}
const stateAsRefs = toRefs(state)
// stateAsRefs 是一个普通对象,stateAsRefs.foo则是响应式对象,因此{...}解构才不会丢失响应式
let {bar} = stateAsRefs
console.log(stateAsRefs)
console.log(stateAsRefs.foo.value) // 因为使用了 ref ,理所应当 .value。
console.log(a)
</script>
当从组合式函数中返回响应式对象时,toRefs 相当有用。使用它,消费者组件可以解构/展开返回的对象而不会失去响应性:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// ...基于状态的操作逻辑
// 在返回时都转为 ref
return toRefs(state)
}
// 可以解构而不会失去响应性
const { foo, bar } = useFeatureX()
toRefs 在调用时只会为源对象上可以枚举的属性创建 ref。如果要为可能还不存在的属性创建 ref,请改用 toRef 。
{{ name }}
关于toRef和toRefs
toRef就是把对象里面的那个属性新生成一个Ref类型,但是是引用了之前那个对象里面的属性
toRefs就是把对象第一层的属性生成一个Ref类型,然后全放到一个对象里面
let name = toRef(person, 'name');
let person1 = toRefs(person);
console.log(name);
console.log(person1);

注意直接从reactive中解构出来的就没有响应式了,只是一个单纯的数值。
所以有了toRefs,可以把第一层结构解构出来还是有响应式。
这两个函数主要是为了简便我们使用对象内部数据的步骤。
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
和 reactive() 不同,这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露,这也意味着值为 ref 的属性 不会被自动解包了。
谨慎使用
浅层数据结构应该只用于组件中的根级状态。请避免将其嵌套在深层次的响应式对象中,因为它创建的树具有不一致的响应行为,这可能很难理解和调试。
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
什么时候使用?
shallowReactive
setup() {
let person = shallowReactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20,
},
},
});
console.log('******', person);
console.log('******', person.job);
return {
person,
...toRefs(person)
};
},
name,age,job都是响应式数据,但是深层次的j1和salary则没有响应式。

shallowRef
setup() {
let refX = ref({
x:0
})
let shallowRefX = shallowRef({
x:0
})
console.log('ref',refX);
console.log('shallowRef',shallowRefX);
},

但是这个shallowRef定义的数据本身还是响应式,比如定义一个函数,内部写上
shallowRefX.value = {x:888},用一个事件触发后,页面也会更新的。
备注:
readonly: 让一个响应式数据变为只读的(深只读)。
只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。
要避免深层级的转换行为,请使用 shallowReadonly() 作替代。
shallowReadonly:让一个响应式数据变为只读的(浅只读)。
和 readonly() 不同,这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为 ref 的属性 不会被自动解包了。
谨慎使用
浅层数据结构应该只用于组件中的根级状态。请避免将其嵌套在深层次的响应式对象中,因为它创建的树具有不一致的响应行为,这可能很难理解和调试。
应用场景: 不希望数据被修改时。
readonly
当前求和为:{{sum}}
姓名:{{name}}
年龄:{{age}}
薪资:{{job.j1.salary}}K
使用readonly之后,无论是浅层次的,还是深层次的的数据都不能被修改。
如下图,点击了下面一排的三个按钮都被警告是不能被修改的

shallowReadonly
使用readonly之后,第一层次的数据不能被修改,但是深层次的的数据时可以被修改的。
如下图,点击了下面一排的三个按钮,前两个修改第一层数据被警告是不能被修改的,第三个修改工资是可以修改的。

toRaw:
作用:可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的响应式对象转为普通对象。
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
示例
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
markRaw:
作用:标记一个对象,使其永远不会再成为响应式对象。返回该对象本身。
应用场景:
示例
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 也适用于嵌套在其他响应性对象
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
toRaw
年龄:{{ age }}
座驾信息:{{ person.car }}
点击了输出最原始的person,但是页面的数据并没有改变,而且从输出结果来看这个并不是响应式的。

markRaw
年龄:{{ age }}
座驾信息:{{ person.car }}
点击给人添加一台车,将car对象标记,然后点击换车名和换价格,页面数据都没有改变。

谨慎使用
markRaw()和类似shallowReactive()这样的浅层式 API 使你可以有选择地避开默认的深度响应/只读转换,并在状态关系谱中嵌入原始的、非代理的对象。它们可能出于各种各样的原因被使用:
- 有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当呈现带有不可变数据源的大型列表时,跳过代理转换可以提高性能。
这应该是一种进阶需求,因为只在根层访问能到原始值,所以如果把一个嵌套的、没有标记的原始对象设置成一个响应式对象,然后再次访问它,你获取到的是代理的版本。这可能会导致对象身份风险,即执行一个依赖于对象身份的操作,但却同时使用了同一对象的原始版本和代理版本:
const foo = markRaw({ nested: {} }) const bar = reactive({ // 尽管 `foo` 被标记为了原始对象,但 foo.nested 却没有 nested: foo.nested }) console.log(foo.nested === bar.nested) // false
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
识别风险一般是很罕见的。然而,要正确使用这些 API,同时安全地避免这样的风险,需要你对响应性系统的工作方式有充分的了解。
自定义 ref 的get set,用于某些数据变更操作的额外功能封装,可以理解为computed 指令类似的功能之类的
val = isRef(val) ? val.value : val 计算的一个语法糖。<script lang='ts' setup>
import { ref } from 'vue';
let Redf = ref('南中乱党')
let un1 = unref({})
let un2 = unref(Redf)
let un3 = unref(ref({})) // un3 > proxy {}
</script>
<script lang='ts' setup>
import { shallowRef,watchEffect,triggerRef } from 'vue';
const shallow = shallowRef({
greet: 'Hello, world'
})
watchEffect(() => {
// 第一次运行时记录一次 "Hello, world"
console.log(shallow.value.greet)
})
shallow.value.greet = 'Hello, universe'
// 首先 watchEffect 追踪的是响应式数据,shallowRef 是浅层的,
// 所以当是{}的时候,无法触发 effect ,每次操作 shallow.value 的时候,则需要 triggerRef(shallow) 主动触发执行。
// 记住,是每次操作,都需要 triggerRef() 一下
triggerRef(shallow)
</script>
isRef: 检查一个值是否为一个 ref 对象
<script lang='ts' setup>
import { isRef } from 'vue';
// ()内例子是上面的。
console.log(isRef(Redf));
console.log(isRef(stateAsRefs.foo));
</script>
isReactive: 检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。
isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
isProxy: 检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。
只要对象是Proxy类型就会返回true