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.