What is the difference between ref, toRef and toRefs What is the difference between ref, toRef and toRefs vue.js vue.js

What is the difference between ref, toRef and toRefs


Vue 3 ref

A ref is a mechanism for reactivity in Vue 3. The idea is to wrap a non-object in a reactive object:

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.

Hmm.. Why?

In JavaScript (and many OOP languages), there are 2 kinds of variable: value and reference.

Value variables:If a variable x contains a value like 10, it's a value variable. If you were to copy x to y, it simply copies the value. Any future changes to x will not change y.

Reference variables: But if x contains an object, then it's a reference variable. With these, y's properties do change when x's properties change, because they both refer to the same object. (Test this with vanilla JavaScript if that comes as a surprise.)

So this makes reference variables more dynamic in a sense, and more useful for reactivity, since any changes can be easily reflected anywhere in the app. Vue wants to harness this capability even for simple value variables, so ref wraps those in an object, which creates a reference variable.

reactive

For object variables, a ref wrapping is not needed because it's already a reference type. It only needs Vue's reactive functionality (which a ref also has):

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

But this object's properties might contain values rather than references. If you were to copy a value property directly to another location, the copy would lose the connection to the object, and its reactivity. This is where toRef is useful.

toRef

toRef converts a single reactive object property to a ref that maintains its connection with the parent object:

const state = reactive({  foo: 1,  bar: 2})const fooRef = toRef(state, 'foo')/*fooRef: Ref<number>,*/

toRefs

toRefs converts all of the properties, to a plain object with properties that are refs:

const state = reactive({  foo: 1,  bar: 2})const stateAsRefs = toRefs(state)/*{  foo: Ref<number>,  bar: Ref<number>}*/


reactive

reactive creates a deeply reactive proxy object based on the given object. The proxy object will look exactly the same as the given plain object, but any mutation, no matter how deep it is, will be reactive - this even includes added and removed properties. The important thing is that reactive can only work with objects, not primitives.

For example, const state = reactive({foo: {bar: 1}}) means:

  • state.foo is reactive (it can be used in template, computed and watch)
  • state.foo.bar is reactive
  • state.baz, state.foo.baz, state.foo.bar.baz are also reactive even though baz does not yet exist anywhere. This might look surprising (especially when you start to dig how reactivity in vue works). By state.baz being reactive, I mean within your template/computed properties/watches, you can write state.baz literally and expect your logic to be executed again when state.baz becomes available. In fact, if you write something like {{ state.baz ? state.baz.qux : "default value" }} in your template, it will also work. The final string displayed will reactively reflect state.baz.qux.

This can happen because reactive not only creates a single top level proxy object, it also recursively converts all the nested objects into reactive proxies, and this process continues to happen at runtime even for the sub objects created on the fly. Dependencies on properties of reactive objects are continuously discovered at runtime, whenever a property access attempt is made against a reactive object.

Consequently, you can also do const foo = state.foo in a separate variable and expect reactivity to work off foo. The power of reactivity comes from the Proxy object.

However, there are always edge cases to watch for:

  1. the recursive creation of nested proxies can only happen if there is a nested object. If a given property does not exist, or it exists but it is not an object, no proxy can be created at that property. E.g. reactivity does not work off the baz variable for const baz = state.baz, nor the bar variable for const bar = state.foo.bar. To make it clear, what it means is that you can use state.baz and state.foo.bar in your template/computed/watch, but not baz or bar created above.
  2. if you extract a nest proxy out to a variable, it is detached from its original parent. This can be made clearer with an example. The second assignment below (state.foo = {bar: 3}) does not destroy the reactivity of foo, but state.foo will be a new proxy object while the foo variable still points the to original proxy object.
const state = reactive({foo: {bar: 1}});const foo = state.foo;state.foo.bar = 2;foo.bar === 2; // true, because foo is reactivestate.foo = {bar: 3};foo.bar === 3; // false, foo.bar will still be 2;  

ref and toRef solve some of these edge cases.

ref

ref is pretty much the reactive that works also with primitives. We still cannot turn JS primitives into Proxy objects, so ref always wraps the provided argument X into an object of shape {value: X}. It does not matter if X is primitive or not, the "boxing" always happens. If an object is given to ref, ref internally calls reactive after the boxing so the result is also deeply reactive. The major difference in practice is that you need to keep in mind to call .value in your js code when working with ref. In your template you dont have to call .value because Vue automatically unwraps ref in template.

const count = ref(1);const objCount = ref({count: 1});count.value === 1; // trueobjCount.value.count === 1; // true

toRef

toRef is meant to convert a property of a reactive object into a ref. You might be wondering why this is necessary since reactive object is already deeply reactive. toRef is here to handle the two edge cases mentioned for reactive. In summary, toRef can convert any property of a reactive object into a ref that is linked to its original parent. The property can be one that does not exist initially, or whose value is primitive.

In the same example where state is defined as const state = reactive({foo: {bar: 1}}):

  • const foo = toRef(state, 'foo') will be very similar to const foo = state.foo but with two differences:
    1. foo is a ref so you need to do foo.value in js;
    2. foo is linked to its parent, so reassigning state.foo = {bar: 2} will get reflected in foo.value
  • const baz = toRef(state, 'baz') now works.

toRefs

toRefs is a utility method used for destructing a reactive object and convert all its properties to ref:

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