ref、toRef 和 toRefs 之间有什么区别
What is the difference between ref, toRef and toRefs
我刚刚开始使用 Vue 3 和 Composition API。
我想知道 ref
、toRef
和 toRefs
之间有什么区别?
Vue 3 ref
ref 是 Vue 3 中的一种反应机制。其思想是将非对象包装在 reactive
对象中:
Takes an inner value and returns a reactive and mutable ref object. The ref object has a single property .value
that points to the inner value.
嗯..为什么?
在JavaScript(以及许多 OOP 语言)中,有两种变量:value 和 reference。
值变量:
如果一个变量 x
包含一个像 10
这样的值,它就是一个 value 变量。如果您要将 x
复制到 y
,它只是复制值。 x
的任何未来更改都不会更改 y
。
引用变量:但是如果x
是一个对象(或数组),那么它就是一个引用变量。有了这些,y
的属性 do 会在 x
的属性发生变化时发生变化,因为它们都 refer同一个对象。 (因为复制的是引用,而不是对象本身。如果意外出现,请使用 vanilla JavaScript 进行测试,您会看到 x === y
)
由于 Vue 3 反应性依赖于 JavaScript proxies 来检测变量变化——并且由于代理需要引用变量——Vue 提供了 ref
来将您的值变量转换为引用变量。
(并且 Vue 会自动在模板中解包你的 refs
,这是 ref
的一个额外好处,如果你将你的值变量包装在一个手动对象。)
reactive
如果您的原始变量是对象(或数组),则不需要 ref
包装,因为它已经是 reference 类型。它只需要 Vue 的 reactive 功能(ref
也有):
const state = reactive({
foo: 1,
bar: 2
})
但是此对象的 属性 可能包含值,例如数字 10
。如果你把一个value属性复制到别处,又会出现上面的问题。 Vue 无法跟踪副本,因为它不是引用变量。这就是 toRef
有用的地方。
toRef
toRef
将单个 reactive
对象 属性 转换为 ref
保持与父对象 的连接:
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
/*
fooRef: Ref<number>,
*/
toRefs
toRefs
将 all 属性转换为具有 refs:
属性的普通对象
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
{
foo: Ref<number>,
bar: Ref<number>
}
*/
无反应
reactive
基于给定的 object 创建一个深度反应 proxy object ].代理对象看起来与给定的普通对象完全相同,但任何变化,无论它有多深,都将是反应性的 - 这包括所有类型的变化,包括 属性 添加和删除。重要的是 reactive
只能处理对象,不能处理基元。
例如,const state = reactive({foo: {bar: 1}})
表示:
state.foo
是反应式的(可用于模板、计算和观察)
state.foo.bar
是被动的
state.baz
、state.foo.baz
、state.foo.bar.baz
也是反应性的,即使 baz
尚不存在。这可能看起来令人惊讶(尤其是当您开始挖掘 vue 中的反应性如何工作时)。 state.baz
是反应式的,我的意思是在你的 template/computed properties/watches 中,你可以按字面意思写 state.baz
并期望你的逻辑被执行当 state.baz
可用 时再次出现。事实上,即使你在你的模板中写了类似 {{ state.baz ? state.baz.qux : "default value" }}
的东西,它也会起作用。显示的最终字符串将反应性地反映 state.baz.qux.
之所以会发生这种情况,是因为 reactive
不仅创建了一个顶级代理对象,它还递归地将所有嵌套对象转换为反应代理,并且这个过程在运行时继续发生,即使对于创建的子对象也是如此在飞行中。每当对反应对象 进行 属性 访问尝试时,都会 在运行时持续发现和跟踪对反应对象属性的依赖性。考虑到这一点,您可以逐步计算出这个表达式 {{ state.baz ? state.baz.qux : "default value" }}
:
- 第一次计算时,表达式将读取 baz off state(换句话说,属性
state
属性 baz
上尝试访问)。作为一个代理对象,state 会记住你的表达式依赖于它的 属性 baz
,即使 baz
还不存在。 关闭反应性 baz
由拥有 属性. 的 state
对象提供
- 因为
state.baz
returns undefined
,表达式的计算结果为“默认值”而无需费心查看 state.baz.qux
。本轮没有记录到state.baz.qux
上的依赖,但是这样就好了。 因为如果不先变异 baz
就无法变异 qux
。
- 在您的代码中某处您为
state.baz
分配了一个值:state.baz = { qux: "hello" }
。此突变符合 state
的 baz
属性 的突变,因此您的表达式将被安排重新评估。同时,分配给 state.baz
的是为 { qux: "hello" }
动态创建的 子代理
- 你的表达式被再次计算,这次
state.baz
不是 undefined
所以表达式前进到 state.baz.qux
。返回“hello”,并从代理对象 state.baz
中记录对 qux
属性 的依赖。 这就是我所说的在运行时发现并记录依赖项的意思。
- 一段时间后你改变
state.baz.qux = "hi"
。这是 qux
属性 的突变,因此您的表达式将被再次计算。
考虑到以上内容,您应该也能理解这一点:您可以将 state.foo
存储在一个单独的变量中:const foo = state.foo
。反应性可以很好地解决您的变量 foo
。 foo
指向与 state.foo
指向的同一事物 - 一个反应式代理对象。反应的力量来自代理对象。顺便说一句,const baz = state.baz
的工作方式不同,稍后会详细介绍。
但是,总有一些边缘情况需要注意:
- 嵌套代理的递归创建只有在存在嵌套对象时才会发生。如果给定的 属性 不存在,或者存在但不是对象,则无法在 属性 处创建代理。例如。这就是为什么反应性不会影响
const baz = state.baz
创建的 baz
变量,也不会影响 const bar = state.foo.bar
的 bar
变量。说清楚一点,意思是你可以在你的template/computed/watch中使用state.baz
和state.foo.bar
,但不能使用上面创建的baz
或bar
。
- 如果您将嵌套代理提取到一个变量,它会从其原始父级分离。举个例子可以更清楚地说明这一点。下面的第二个赋值 (
state.foo = {bar: 3}
) 不会破坏 foo
的反应性,但 state.foo
将是一个新的代理对象,而 foo
变量仍然指向原始代理对象.
const state = reactive({foo: {bar: 1}});
const foo = state.foo;
state.foo.bar = 2;
foo.bar === 2; // true, because foo and state.foo are the same
state.foo = {bar: 3};
foo.bar === 3; // false, foo.bar will still be 2
ref
和 toRef
解决了其中一些边缘情况。
ref
ref
几乎是 reactive
也适用于原语。我们仍然无法将 JS 原语转换为 Proxy 对象,因此 ref
总是将提供的参数 X
包装成形状为 {value: X}
的对象。 X 是否原始并不重要,“装箱”总是会发生。如果将一个对象提供给 ref
,ref
在装箱后会在内部调用 reactive
,因此结果也是深度响应式的。实践中的主要区别在于,在使用 ref 时,您需要记住在 js 代码中调用 .value
。在你的模板中你不必调用 .value
因为 Vue 会自动解包模板中的引用。
const count = ref(1);
const objCount = ref({count: 1});
count.value === 1; // true
objCount.value.count === 1; // true
toRef
toRef
用于将反应对象的 属性 转换为 ref
。你可能想知道为什么这是必要的,因为反应对象已经是深度反应的了。 toRef
在这里处理 reactive
中提到的两个极端情况。总之,toRef
可以将反应对象的任何 属性 转换为链接到其原始父级的 ref。 属性可以是初始不存在的,也可以是原始值
在同一示例中,状态定义为 const state = reactive({foo: {bar: 1}})
:
const foo = toRef(state, 'foo')
与 const foo = state.foo
非常相似,但有两个区别:
foo
是一个 ref
所以你需要在 js 中做 foo.value
;
foo
链接到其父级,因此重新分配 state.foo = {bar: 2}
将反映在 foo.value
中
const baz = toRef(state, 'baz')
现在可以使用了。
toRefs
toRefs
是一种实用方法,用于破坏反应对象并将其所有属性转换为 ref:
const state = reactive({...});
return {...state}; // will not work, destruction removes reactivity
return toRefs(state); // works
我刚刚开始使用 Vue 3 和 Composition API。
我想知道 ref
、toRef
和 toRefs
之间有什么区别?
Vue 3 ref
ref 是 Vue 3 中的一种反应机制。其思想是将非对象包装在 reactive
对象中:
Takes an inner value and returns a reactive and mutable ref object. The ref object has a single property
.value
that points to the inner value.
嗯..为什么?
在JavaScript(以及许多 OOP 语言)中,有两种变量:value 和 reference。
值变量:
如果一个变量 x
包含一个像 10
这样的值,它就是一个 value 变量。如果您要将 x
复制到 y
,它只是复制值。 x
的任何未来更改都不会更改 y
。
引用变量:但是如果x
是一个对象(或数组),那么它就是一个引用变量。有了这些,y
的属性 do 会在 x
的属性发生变化时发生变化,因为它们都 refer同一个对象。 (因为复制的是引用,而不是对象本身。如果意外出现,请使用 vanilla JavaScript 进行测试,您会看到 x === y
)
由于 Vue 3 反应性依赖于 JavaScript proxies 来检测变量变化——并且由于代理需要引用变量——Vue 提供了 ref
来将您的值变量转换为引用变量。
(并且 Vue 会自动在模板中解包你的 refs
,这是 ref
的一个额外好处,如果你将你的值变量包装在一个手动对象。)
reactive
如果您的原始变量是对象(或数组),则不需要 ref
包装,因为它已经是 reference 类型。它只需要 Vue 的 reactive 功能(ref
也有):
const state = reactive({
foo: 1,
bar: 2
})
但是此对象的 属性 可能包含值,例如数字 10
。如果你把一个value属性复制到别处,又会出现上面的问题。 Vue 无法跟踪副本,因为它不是引用变量。这就是 toRef
有用的地方。
toRef
toRef
将单个 reactive
对象 属性 转换为 ref
保持与父对象 的连接:
const state = reactive({ foo: 1, bar: 2 }) const fooRef = toRef(state, 'foo') /* fooRef: Ref<number>, */
toRefs
toRefs
将 all 属性转换为具有 refs:
const state = reactive({ foo: 1, bar: 2 }) const stateAsRefs = toRefs(state) /* { foo: Ref<number>, bar: Ref<number> } */
无反应
reactive
基于给定的 object 创建一个深度反应 proxy object ].代理对象看起来与给定的普通对象完全相同,但任何变化,无论它有多深,都将是反应性的 - 这包括所有类型的变化,包括 属性 添加和删除。重要的是 reactive
只能处理对象,不能处理基元。
例如,const state = reactive({foo: {bar: 1}})
表示:
state.foo
是反应式的(可用于模板、计算和观察)state.foo.bar
是被动的state.baz
、state.foo.baz
、state.foo.bar.baz
也是反应性的,即使baz
尚不存在。这可能看起来令人惊讶(尤其是当您开始挖掘 vue 中的反应性如何工作时)。state.baz
是反应式的,我的意思是在你的 template/computed properties/watches 中,你可以按字面意思写state.baz
并期望你的逻辑被执行当state.baz
可用 时再次出现。事实上,即使你在你的模板中写了类似{{ state.baz ? state.baz.qux : "default value" }}
的东西,它也会起作用。显示的最终字符串将反应性地反映 state.baz.qux.
之所以会发生这种情况,是因为 reactive
不仅创建了一个顶级代理对象,它还递归地将所有嵌套对象转换为反应代理,并且这个过程在运行时继续发生,即使对于创建的子对象也是如此在飞行中。每当对反应对象 进行 属性 访问尝试时,都会 在运行时持续发现和跟踪对反应对象属性的依赖性。考虑到这一点,您可以逐步计算出这个表达式 {{ state.baz ? state.baz.qux : "default value" }}
:
- 第一次计算时,表达式将读取 baz off state(换句话说,属性
state
属性baz
上尝试访问)。作为一个代理对象,state 会记住你的表达式依赖于它的 属性baz
,即使baz
还不存在。 关闭反应性baz
由拥有 属性. 的 - 因为
state.baz
returnsundefined
,表达式的计算结果为“默认值”而无需费心查看state.baz.qux
。本轮没有记录到state.baz.qux
上的依赖,但是这样就好了。 因为如果不先变异baz
就无法变异qux
。 - 在您的代码中某处您为
state.baz
分配了一个值:state.baz = { qux: "hello" }
。此突变符合state
的baz
属性 的突变,因此您的表达式将被安排重新评估。同时,分配给state.baz
的是为{ qux: "hello" }
动态创建的 子代理
- 你的表达式被再次计算,这次
state.baz
不是undefined
所以表达式前进到state.baz.qux
。返回“hello”,并从代理对象state.baz
中记录对qux
属性 的依赖。 这就是我所说的在运行时发现并记录依赖项的意思。 - 一段时间后你改变
state.baz.qux = "hi"
。这是qux
属性 的突变,因此您的表达式将被再次计算。
state
对象提供
考虑到以上内容,您应该也能理解这一点:您可以将 state.foo
存储在一个单独的变量中:const foo = state.foo
。反应性可以很好地解决您的变量 foo
。 foo
指向与 state.foo
指向的同一事物 - 一个反应式代理对象。反应的力量来自代理对象。顺便说一句,const baz = state.baz
的工作方式不同,稍后会详细介绍。
但是,总有一些边缘情况需要注意:
- 嵌套代理的递归创建只有在存在嵌套对象时才会发生。如果给定的 属性 不存在,或者存在但不是对象,则无法在 属性 处创建代理。例如。这就是为什么反应性不会影响
const baz = state.baz
创建的baz
变量,也不会影响const bar = state.foo.bar
的bar
变量。说清楚一点,意思是你可以在你的template/computed/watch中使用state.baz
和state.foo.bar
,但不能使用上面创建的baz
或bar
。 - 如果您将嵌套代理提取到一个变量,它会从其原始父级分离。举个例子可以更清楚地说明这一点。下面的第二个赋值 (
state.foo = {bar: 3}
) 不会破坏foo
的反应性,但state.foo
将是一个新的代理对象,而foo
变量仍然指向原始代理对象.
const state = reactive({foo: {bar: 1}});
const foo = state.foo;
state.foo.bar = 2;
foo.bar === 2; // true, because foo and state.foo are the same
state.foo = {bar: 3};
foo.bar === 3; // false, foo.bar will still be 2
ref
和 toRef
解决了其中一些边缘情况。
ref
ref
几乎是 reactive
也适用于原语。我们仍然无法将 JS 原语转换为 Proxy 对象,因此 ref
总是将提供的参数 X
包装成形状为 {value: X}
的对象。 X 是否原始并不重要,“装箱”总是会发生。如果将一个对象提供给 ref
,ref
在装箱后会在内部调用 reactive
,因此结果也是深度响应式的。实践中的主要区别在于,在使用 ref 时,您需要记住在 js 代码中调用 .value
。在你的模板中你不必调用 .value
因为 Vue 会自动解包模板中的引用。
const count = ref(1);
const objCount = ref({count: 1});
count.value === 1; // true
objCount.value.count === 1; // true
toRef
toRef
用于将反应对象的 属性 转换为 ref
。你可能想知道为什么这是必要的,因为反应对象已经是深度反应的了。 toRef
在这里处理 reactive
中提到的两个极端情况。总之,toRef
可以将反应对象的任何 属性 转换为链接到其原始父级的 ref。 属性可以是初始不存在的,也可以是原始值
在同一示例中,状态定义为 const state = reactive({foo: {bar: 1}})
:
const foo = toRef(state, 'foo')
与const foo = state.foo
非常相似,但有两个区别:foo
是一个ref
所以你需要在 js 中做foo.value
;foo
链接到其父级,因此重新分配state.foo = {bar: 2}
将反映在foo.value
中
const baz = toRef(state, 'baz')
现在可以使用了。
toRefs
toRefs
是一种实用方法,用于破坏反应对象并将其所有属性转换为 ref:
const state = reactive({...});
return {...state}; // will not work, destruction removes reactivity
return toRefs(state); // works