reactivity

vue watches the value, react re-runs the function. almost every difference between the two frameworks falls out of that one decision.

what a ref actually is

ref(0) returns an object with a value property behind a proxy. reading count.value registers whoever is reading it as a dependency. writing to it notifies exactly those readers, and nothing else runs.

const count = ref(0);

watchEffect(() => console.log(count.value)); // registers itself on read
count.value++;                               // only that effect re-runs

that is the whole model. the .value that everyone complains about is the price of it: a plain number cannot be proxied, so the value has to live inside something vue can intercept.

reactive({ count: 0 }) proxies the object itself, so there is no .value. it only works on objects, and it is the reason for the trap further down.

why there is no dependency array

in react, a change re-runs the component function and everything in it. the framework has no idea which values your effect touched, so you tell it:

useEffect(() => { console.log(count); }, [count]);

get that list wrong and the effect reads a stale value. see useState for why the value is stale rather than simply old.

vue collected the dependencies while the effect ran, so there is no list to write and no list to get wrong:

watchEffect(() => { console.log(count.value); });

the same reason explains the rest of the gap. no useCallback, because the component function does not re-run. no stale closures, because there is one value and not a snapshot per render. no memoization burden, because a write notifies its readers instead of re-rendering a subtree.

the trap: destructuring loses the proxy

reactivity lives in the proxy, so pulling a value out of it leaves the reactivity behind.

const state = reactive({ count: 0 });
const { count } = state;   // count is now a plain 0, forever

count is a copy taken at that moment. later writes to state.count update nothing, and nothing warns you.

const { count } = toRefs(state); // keeps the connection

the same thing happens with props, which is why toRefs(props) shows up in almost every composable that takes them.

when it bites

passing reactive state through a plain function, spreading it into a new object, or storing it in a normal array. all three copy the value out and the connection is gone. if a value stopped updating and nothing looks wrong, this is almost always what happened.