ref、toRef 和 toRefs 之间有什么区别

Zum*_*umm 9 vue.js vue-component vue-reactivity vuejs3 vue-composition-api

我刚刚开始使用 Vue 3 和 Composition API。

我想知道ref,toRef和之间有什么区别toRefs

Xin*_*hao 22

反应性的

reactive基于给定对象创建深度反应性代理 对象。代理对象看起来与给定的普通对象完全相同,但是任何突变,无论多深,都将是反应性的 - 这包括所有类型的突变,包括属性添加和删除。重要的是它只能处理对象,不能处理基元。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反应性,我的意思是在您的模板/计算属性/手表中,您可以按字面意思编写并期望您的逻辑在可用state.baz时再次执行。事实上,即使你在模板中编写了类似的内容,它也可以工作。最终显示的字符串将反应性地反映 state.baz.qux。state.baz {{ state.baz ? state.baz.qux : "default value" }}

之所以会发生这种情况,是因为reactive它不仅创建单个顶级代理对象,而且还会递归地将所有嵌套对象转换为反应式代理,并且这个过程在运行时继续发生,即使对于动态创建的子对象也是如此。每当对反应性对象进行属性访问尝试时,就会在运行时不断发现和跟踪反应性对象属性的依赖关系。考虑到这一点,您可以{{ state.baz ? state.baz.qux : "default value" }}逐步计算出这个表达式:

  1. 第一次求值时,表达式将读取baz off状态(换句话说,尝试对stateproperty进行属性访问baz)。作为一个代理对象,state会记住你的表达式依赖于它的 property baz,即使baz它还不存在。反应性关闭由拥有该属性的对象baz提供state
  2. 由于state.baz返回undefined,表达式的计算结果为“默认值”,而无需查看state.baz.qux。本轮中没有记录依赖关系state.baz.qux,但这很好。因为如果不先变异就无法变异quxbaz
  3. 在代码中的某处,您将值分配给state.baz: state.baz = { qux: "hello" }。此突变符合 属性的突变bazstate因此您的表达式计划重新评估。同时,分配给的state.baz是动态创建的子代理{ qux: "hello" }
  4. 再次计算您的表达式,这次state.baz不是undefined,因此表达式进展为state.baz.qux。返回“hello”,并qux记录代理对象对属性的依赖关系state.baz这就是我所说的依赖关系在运行时发生时被发现并记录的意思
  5. 一段时间后你就改变了state.baz.qux = "hi"。这是属性的突变qux,因此您的表达式将再次被评估。

考虑到上述内容,您应该也能够理解这一点:您可以存储state.foo在单独的变量中:const foo = state.foofoo反应性可以很好地处理你的变量。foo指向相同的东西state.foo- 反应式代理对象。反应性的力量来自代理对象。顺便说一下,const baz = state.baz效果不一样,稍后会详细介绍。

然而,总是有一些边缘情况需要注意:

  1. 仅当存在嵌套对象时,才会递归创建嵌套代理。如果给定属性不存在,或者存在但不是对象,则不能在该属性上创建代理。例如,这就是为什么反应性不能消除baz所创建的变量const baz = state.baz,也不能bar消除 的变量const bar = state.foo.bar。明确地说,这意味着您可以在模板/计算/监视中使用state.bazand ,但不能在上面使用or创建。state.foo.barbazbar
  2. 如果将嵌套代理提取到变量中,它将与其原始父级分离。通过一个例子可以更清楚地说明这一点。下面的第二个赋值 ( 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  
Run Code Online (Sandbox Code Playgroud)

reftoRef解决其中一些边缘情况。

参考

ref几乎reactive也适用于基元。我们仍然无法将 JS 原语转换为 Proxy 对象,因此ref始终将提供的参数包装X为 shape 的对象{value: X}。无论 X 是否原始,“拳击”总是会发生。如果将一个对象赋予ref,则在装箱后ref内部调用,因此结果也是深度反应的。实践中的主要区别是,在使用 ref 时,reactive您需要记住调用js 代码。.value在模板中,您不必调用,.value因为 Vue 会自动解开模板中的 ref。

const count = ref(1);
const objCount = ref({count: 1});

count.value === 1; // true
objCount.value.count === 1; // true
Run Code Online (Sandbox Code Playgroud)

参考文献

toRef旨在将反应对象的属性转换为ref. 您可能想知道为什么这是必要的,因为反应性对象已经是深度反应性的。toRef在这里处理提到的两种边缘情况reactive。总之,toRef可以将响应式对象的任何属性转换为链接到其原始父级的引用。该属性可以是最初不存在的属性,也可以是原始值。

在同一示例中,状态定义为const state = reactive({foo: {bar: 1}})

  • const foo = toRef(state, 'foo')与以下内容非常相似,const foo = state.foo但有两点不同:
    1. foo是一个ref所以你需要在js中做foo.value
    2. foo链接到其父级,因此重新分配state.foo = {bar: 2}将反映在foo.value
  • const baz = toRef(state, 'baz')现在可以了。

参考文献

toRefs是一个实用方法,用于破坏反应对象并将其所有属性转换为 ref:

const state = reactive({...});
return {...state}; // will not work, destruction removes reactivity 
return toRefs(state); // works
Run Code Online (Sandbox Code Playgroud)


Dan*_*Dan 13

视图 3 ref

REF是用于Vue公司3的想法是包裹非对象在一个反应性的机构reactive对象:

接受一个内部值并返回一个反应性和可变的 ref 对象。ref 对象有一个.value指向内部值的属性。

嗯.. 为什么?

了解在 JavaScript(和许多 OOP 语言)中,有两种类型的变量:值和引用。

如果一个变量x包含一个 value 5,那么如果你复制xy,你就复制了一个值。将来对 的任何更改x都不会更改y。但是,如果x包含一个对象(引用类型),那么y 变更如果x的性质发生变化,因为它们都指代相同的对象。

这使得引用类型更加动态;您可以复制它们并通过更改原件来更改副本,甚至可以通过更改副本来更改原件。Vue 想要利用这种能力,即使对于数字这样的值类型,它也允许你将它们“包装”在一个对象中以创建一个引用变量。

reactive

对于对象变量,不需要 ref 包装,因为它已经是引用类型。它只需要 Vue 的响应式功能(ref 也有):

const state = reactive({
  foo: 1,
  bar: 2
})
Run Code Online (Sandbox Code Playgroud)

但是如果这个对象的属性是值变量,它们自然就不是 refs。这意味着如果您要直接复制一个属性,您将失去与其父对象的连接。这toRef是有用的地方。

toRef

toRef将单个reactive对象属性转换为保持与父对象连接的 ref:

const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')
/*
fooRef: Ref<number>,
*/
Run Code Online (Sandbox Code Playgroud)

toRefs

toRefs所有属性转换为具有 refs 属性的普通对象:

const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
{
  foo: Ref<number>,
  bar: Ref<number>
}
*/
Run Code Online (Sandbox Code Playgroud)

  • 对于 toRef,“指向与父对象保持连接的引用”这一行对我来说是关键。我想知道为什么 toRef 存在,因为你可以只执行 ```const fooRef = ref(state.foo)``` ,但这会创建一个断开连接的引用;对它的任何更改只会更新 fooRef 的依赖项。但使用 toRef 会保留原始连接。 (6认同)