Home > database >  Vue 3 reactivity of a primitive ref passed as a prop
Vue 3 reactivity of a primitive ref passed as a prop

Time:02-03

While playing with Vue 3 reactivity I incountred a behaviour that I couldn't explain.

I created a ref of a primitive. Checking isRef on it returns obviously true. But when passed to a child component as a prop, isRef() and also isReactive() return false. Why ?

Also, even if they both return false, the watcher of this prop I added in the child component is triggered if the value changes. How could it trigger if the watched value is not a ref nor reactive ?

Here is a code snippet for both the parent and the child :

Parent.vue

let foo = ref(0);
function changeFoo() {
  foo.value  ;
}
  
console.log("parent check is reactive?", isReactive(foo)); // retruns false
console.log("parent check is ref?", isRef(foo)); // retruns true

watch(foo, (value, oldValue) => {
  console.log("foo changing from", oldValue, "to ", value);
});

Child.vue

const props = defineProps<{
  foo: number;
}>();

console.log("child check is reactive?",isReactive(props), isReactive(props.foo)); // returns true false
console.log("child check is ref ?",isRef(props), isRef(props.foo)); // returns false false // Question 1: Why props.foo is not a Ref ?
  
watch(props, (value, oldValue) => {
  console.log("props changing from", oldValue, " to ", value);
});
 
watch(()=>props.foo, (value, oldValue) => {
  // Question 2: Why this Watcher detects changes of "foo" without it being a ref nor reactive ?
  console.log("props.foo changing from ", oldValue, " to ", value);
});

And a link to Vue SFC Playground here

Bonus question : When foo is passed to a composable used in the child component, the watcher inside the composable is not triggered unless we pass foo via a toRef but we don't need this additional step if foo is a ref/reactive object. Why primitives needs this addition step ?

CodePudding user response:

But when passed to a child component as a prop, isRef() and also isReactive() return false.

You're only telling half the story here, as your own comment actually indicates:

console.log("child check is reactive?",isReactive(props), isReactive(props.foo)); // returns true false

isReactive(props) does return true, because the entire props object (which wraps foo) is reactive. Any update the parent makes to foo, gets passed down to the Child as an updated props object. It's true props.foo is not a ref/reactive, because it doesn't need to be. As long as props is reactive, props.foo will update

The watcher is able to activate on changes to props.foo because you're actually using special syntax where you pass in a "getter" to the watch specifically meant for watching the property of a reactive object (in your case: props). There's an example in the Vue docs that says the same thing.

If you ever needed to assign props.foo to it's own reactive variable, say to pass to a composable, that's where toRef can be used.

// reactive but not synced with props.foo
const newFoo = ref(props.foo)

// reactive AND synced with props.foo
const newFoo = toRef(props, 'foo')

As I indicated with the above comments, if you make a new variable with just ref, it'll be reactive, but it won't be synced with it's source property, i.e. the first newFoo won't reactively update when props.foo updates, but the second newFoo will. This is also explained well in the Vue docs

  • Related