[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-vue-weekly-reactivity-ref-vs-reactive":44,"search-suggestions":522,"related-vue-weekly-reactivity-ref-vs-reactive":562,"code:vue:true:148h64z":578,"code:js:true:v8zupz":579,"code:js:true:8kpq28":580,"code:js:true:1x4310q":581,"code:js:true:14vvu9e":582,"code:js:true:1so14f8":583,"code:js:true:1qmw064":584,"code:js:true:ar6qoc":585,"code:js:true:o0mlxz":586,"code:js:true:1dfpyop":587,"code:js:true:13c7fy9":588,"code:js:true:1xvuxg7":589,"comments-01a03410-2db8-735b-88b2-5285459dc915":590},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":74,"playground":76,"body":78,"bodyMd":506,"seo":507,"translationGroupId":509,"series":48,"verticalId":5,"thread":510,"assessments":512,"translations":518,"quiz":520},"01a03410-2db8-735b-88b2-5285459dc915","vue-weekly-reactivity-ref-vs-reactive","Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)",null,"A complete guide to Vue reactivity: how ref and reactive really work, why destructuring breaks updates, when to use each, and a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-reactivity-ref-vs-reactive.png",14,"2026-08-24T13:57:38.080Z",90,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"vue","Vue",[64,65,68,71],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},"javascript","Javascript",{"slug":69,"name":70,"color":48},"tutorial","Tutorial",{"slug":72,"name":73,"color":48},"webdev","Webdev",{"assessments":75},1,{"slug":46,"title":77},"Vue reactivity: ref vs reactive — interactive playground",{"blocks":79,"version":75},[80,84,87,90,95,98,108,111,114,117,120,134,137,140,145,148,151,154,157,160,163,167,170,177,180,183,187,190,195,198,201,204,207,211,215,218,222,225,231,234,237,240,243,247,250,254,257,261,264,268,272,275,279,282,287,291,294,298,301,304,308,311,320,323,332,335,338,341,344,347,350,353,356,359,362,365,369,372,375,378,382,385,389,392,396,399,403,406,409,461,465,468,476,479,482,485,488,491,494,497,500],{"id":81,"html":82,"type":83},"b1","\u003Cp>Your counter is wired up. The click handler runs — you added a \u003Ccode>console.log\u003C\u002Fcode>, it prints \u003Ccode>3\u003C\u002Fcode>. The number on screen still says \u003Ccode>0\u003C\u002Fcode>.\u003C\u002Fp>","paragraph",{"id":85,"html":86,"type":83},"b2","\u003Cp>Nothing is broken. Vue never subscribed to the thing you changed.\u003C\u002Fp>",{"id":88,"html":89,"type":83},"b3","\u003Cp>That gap — between \u003Cem>the value changed\u003C\u002Fem> and \u003Cem>Vue knows the value changed\u003C\u002Fem> — is where almost every baffling Vue bug lives. It is also why \u003Ccode>ref\u003C\u002Fcode> and \u003Ccode>reactive\u003C\u002Fcode> feel like two competing ways to do the same job. They are not. Once you can see the subscription, the whole reactivity API stops being a list of functions to memorise and becomes one rule with a few consequences.\u003C\u002Fp>",{"id":91,"html":92,"text":93,"type":94,"level":31},"b4","What you&#39;ll learn","What you'll learn","heading",{"id":96,"html":97,"type":83},"b5","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":99,"type":100,"items":101,"ordered":18},"b6","list",[102,103,104,105,106,107],"Explain what Vue actually tracks, and at what moment it starts tracking","Choose between \u003Ccode>ref()\u003C\u002Fcode> and \u003Ccode>reactive()\u003C\u002Fcode> deliberately, not by habit","Spot the four ways code silently loses reactivity — destructuring being the famous one","Use \u003Ccode>computed\u003C\u002Fcode>, \u003Ccode>watch\u003C\u002Fcode> and \u003Ccode>watchEffect\u003C\u002Fcode> for what each is actually for","Reach for \u003Ccode>shallowRef\u003C\u002Fcode> and \u003Ccode>toRaw\u003C\u002Fcode> when deep reactivity is the wrong default","Keep the cheat sheet at the end open while you work",{"id":109,"html":110,"text":110,"type":94,"level":31},"b7","Who this is for",{"id":112,"html":113,"type":83},"b8","\u003Cp>You&#39;ve built at least one Vue 3 component with \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode>, and you&#39;ve used \u003Ccode>ref()\u003C\u002Fcode> because a tutorial told you to. You don&#39;t need to know how a Proxy works — we&#39;ll build that up.\u003C\u002Fp>",{"id":115,"html":116,"type":83},"b9","\u003Cp>This article is written against \u003Cstrong>Vue 3.5.41\u003C\u002Fstrong>, the current stable release as of August 2026. (Vue 3.6 is in release candidate as this goes out; the reactivity semantics below are the stable, documented ones.)\u003C\u002Fp>",{"id":118,"html":119,"text":119,"type":94,"level":31},"b10","Table of contents",{"id":121,"type":100,"items":122,"ordered":18},"b11",[123,124,125,126,127,128,129,130,131,132,133],"\u003Ca href=\"#the-problem-four-counters-two-silently-broken\">The problem: four counters, two silently broken\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-reactivity-is-a-read-time-subscription\">The mental model: reactivity is a read-time subscription\u003C\u002Fa>","\u003Ca href=\"#ref-a-box-you-can-pass-around\">\u003Ccode>ref()\u003C\u002Fcode>: a box you can pass around\u003C\u002Fa>","\u003Ca href=\"#reactive-a-proxy-you-have-to-hold-on-to\">\u003Ccode>reactive()\u003C\u002Fcode>: a proxy you have to hold on to\u003C\u002Fa>","\u003Ca href=\"#the-four-ways-reactivity-gets-lost\">The four ways reactivity gets lost\u003C\u002Fa>","\u003Ca href=\"#computed-cached-lazy-and-worth-understanding\">\u003Ccode>computed\u003C\u002Fcode>: cached, lazy, and worth understanding\u003C\u002Fa>","\u003Ca href=\"#watch-vs-watcheffect\">\u003Ccode>watch\u003C\u002Fcode> vs \u003Ccode>watchEffect\u003C\u002Fcode>\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices-which-one-when\">Best practices: which one, when\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>",{"id":135,"html":136,"text":136,"type":94,"level":31},"b12","The problem: four counters, two silently broken",{"id":138,"html":139,"type":83},"b13","\u003Cp>Here are four counters. Two work. Two update their data and never update the screen. Before reading on, decide which two.\u003C\u002Fp>",{"id":141,"code":142,"type":143,"language":61,"highlight":144},"b14","\u003Cscript setup>\nimport { ref, reactive } from 'vue'\n\n\u002F\u002F A\nconst a = ref(0)\nconst incA = () => a.value++\n\n\u002F\u002F B\nconst b = reactive({ count: 0 })\nconst incB = () => b.count++\n\n\u002F\u002F C\nlet { count } = reactive({ count: 0 })\nconst incC = () => count++\n\n\u002F\u002F D\nlet d = reactive({ count: 0 })\nconst incD = () => { d = { count: d.count + 1 } }\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cbutton @click=\"incA\">A: {{ a }}\u003C\u002Fbutton>\n  \u003Cbutton @click=\"incB\">B: {{ b.count }}\u003C\u002Fbutton>\n  \u003Cbutton @click=\"incC\">C: {{ count }}\u003C\u002Fbutton>\n  \u003Cbutton @click=\"incD\">D: {{ d.count }}\u003C\u002Fbutton>\n\u003C\u002Ftemplate>","code",[],{"id":146,"html":147,"type":83},"b15","\u003Cp>\u003Cstrong>A and B update. C and D don&#39;t.\u003C\u002Fstrong> And notice what C and D have in common: the numbers \u003Cem>do\u003C\u002Fem> change. \u003Ccode>count\u003C\u002Fcode> in C really does become 1, 2, 3. The bug is not in the arithmetic. It&#39;s that nobody told Vue to care.\u003C\u002Fp>",{"id":149,"html":150,"type":83},"b16","\u003Cp>The usual explanation is &quot;you can&#39;t destructure reactive objects.&quot; That&#39;s true, but it&#39;s a rule to memorise, and rules to memorise are what you fall back on when you don&#39;t have the model. Let&#39;s get the model instead.\u003C\u002Fp>",{"id":152,"html":153,"text":153,"type":94,"level":31},"b17","The mental model: reactivity is a read-time subscription",{"id":155,"html":156,"type":83},"b18","\u003Cp>Vue&#39;s reactivity has two halves, and almost everyone learns only the first.\u003C\u002Fp>",{"id":158,"html":159,"type":83},"b19","\u003Cp>The half people learn: \u003Cstrong>\u003Ccode>reactive()\u003C\u002Fcode> wraps an object in a Proxy\u003C\u002Fstrong>, so Vue can run code when you read or write a property.\u003C\u002Fp>",{"id":161,"html":162,"type":83},"b20","\u003Cp>The half that actually matters: \u003Cstrong>that code only does something while an effect is running.\u003C\u002Fstrong> An effect is a function Vue is currently executing on your behalf — a component&#39;s render function, a \u003Ccode>computed\u003C\u002Fcode> getter, a \u003Ccode>watchEffect\u003C\u002Fcode> callback. When a property is read \u003Cem>during\u003C\u002Fem> an effect, Vue records &quot;this effect depends on this property.&quot; When the property is later written, Vue re-runs the effects that recorded it.\u003C\u002Fp>",{"id":164,"html":165,"type":166},"b21","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> reactivity is not a property of your data. It is a \u003Cstrong>subscription between one property and one effect, created at the moment the effect reads the property.\u003C\u002Fstrong> No read inside an effect, no subscription. No subscription, no update.\u003C\u002Fp>\n","quote",{"id":168,"html":169,"type":83},"b22","\u003Cp>That single sentence pays for the whole article. Run the four counters through it:\u003C\u002Fp>",{"id":171,"type":100,"items":172,"ordered":18},"b23",[173,174,175,176],"\u003Cstrong>A\u003C\u002Fstrong> — the template reads \u003Ccode>a.value\u003C\u002Fcode> while rendering. Subscription. \u003Ccode>a.value++\u003C\u002Fcode> writes it. Re-render. ✅","\u003Cstrong>B\u003C\u002Fstrong> — the template reads \u003Ccode>b.count\u003C\u002Fcode> (a property access on the proxy) while rendering. Subscription. ✅","\u003Cstrong>C\u003C\u002Fstrong> — \u003Ccode>count\u003C\u002Fcode> was read \u003Cstrong>once\u003C\u002Fstrong>, during setup, outside any effect, and its \u003Cem>value\u003C\u002Fem> (\u003Ccode>0\u003C\u002Fcode>) was copied into a new local variable. The template renders that plain number. There was never a subscription to create. ❌","\u003Cstrong>D\u003C\u002Fstrong> — the template reads \u003Ccode>d.count\u003C\u002Fcode> on the proxy, so there \u003Cem>is\u003C\u002Fem> a subscription — to the property \u003Ccode>count\u003C\u002Fcode> \u003Cstrong>of that proxy object\u003C\u002Fstrong>. Then \u003Ccode>incD\u003C\u002Fcode> throws the proxy away and points the local variable at a brand-new plain object. Nothing wrote to the property anyone subscribed to. ❌",{"id":178,"html":179,"type":83},"b24","\u003Cp>C and D are not two arbitrary rules. They are the same rule seen twice: \u003Cstrong>the subscription is to a property on a specific object, and you have to keep reading it, on that object, from inside an effect.\u003C\u002Fstrong>\u003C\u002Fp>",{"id":181,"html":182,"type":83},"b25","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> &quot;Is this reactive?&quot; is the wrong question. The right question is &quot;which effect is subscribed to which property, and am I writing to that exact property?&quot;\u003C\u002Fp>",{"id":184,"html":185,"text":186,"type":94,"level":31},"b26","\u003Ccode>ref()\u003C\u002Fcode>: a box you can pass around","ref(): a box you can pass around",{"id":188,"html":189,"type":83},"b27","\u003Cp>\u003Ccode>ref()\u003C\u002Fcode> sidesteps the problem C runs into by never handing you the value. It hands you a box with a \u003Ccode>.value\u003C\u002Fcode> property:\u003C\u002Fp>",{"id":191,"code":192,"type":143,"language":193,"highlight":194},"b28","import { ref } from 'vue'\n\nconst count = ref(0)\ncount.value++            \u002F\u002F a write to the `value` property of the ref","js",[],{"id":196,"html":197,"type":83},"b29","\u003Cp>Because the value lives behind a property, reading it is a property access — trackable — and passing the box around passes the subscription target with it. That&#39;s the whole trick.\u003C\u002Fp>",{"id":199,"html":200,"type":83},"b30","\u003Cp>Two conveniences worth knowing precisely, because they&#39;re where refs feel magical and magic is hard to debug:\u003C\u002Fp>",{"id":202,"html":203,"type":83},"b31","\u003Cp>\u003Cstrong>1. Refs are unwrapped in templates\u003C\u002Fstrong>, but only for top-level bindings from \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode>. \u003Ccode>{{ count }}\u003C\u002Fcode> works; \u003Ccode>{{ someObject.count }}\u003C\u002Fcode> where \u003Ccode>someObject\u003C\u002Fcode> is a plain object holding a ref does not.\u003C\u002Fp>",{"id":205,"html":206,"type":83},"b32","\u003Cp>\u003Cstrong>2. Refs are deep by default.\u003C\u002Fstrong> \u003Ccode>ref({ user: { name: &#39;Ada&#39; } })\u003C\u002Fcode> converts that inner object with \u003Ccode>reactive()\u003C\u002Fcode> under the hood, so \u003Ccode>obj.value.user.name = &#39;Grace&#39;\u003C\u002Fcode> triggers updates. This is convenient and it is not free — see \u003Ccode>shallowRef\u003C\u002Fcode> below.\u003C\u002Fp>",{"id":208,"code":209,"type":143,"language":193,"highlight":210},"b33","const state = ref({ user: { name: 'Ada' } })\nstate.value.user.name = 'Grace'   \u002F\u002F tracked: the inner object is reactive too",[],{"id":212,"html":213,"text":214,"type":94,"level":31},"b34","\u003Ccode>reactive()\u003C\u002Fcode>: a proxy you have to hold on to","reactive(): a proxy you have to hold on to",{"id":216,"html":217,"type":83},"b35","\u003Cp>\u003Ccode>reactive()\u003C\u002Fcode> returns a Proxy of the object you gave it. No \u003Ccode>.value\u003C\u002Fcode>, which reads more naturally:\u003C\u002Fp>",{"id":219,"code":220,"type":143,"language":193,"highlight":221},"b36","import { reactive } from 'vue'\n\nconst form = reactive({ email: '', agreed: false })\nform.email = 'ada@example.com'   \u002F\u002F tracked",[],{"id":223,"html":224,"type":83},"b37","\u003Cp>Three limits follow directly from &quot;it&#39;s a proxy around \u003Cem>that\u003C\u002Fem> object&quot;:\u003C\u002Fp>",{"id":226,"type":100,"items":227,"ordered":18},"b38",[228,229,230],"\u003Cstrong>Objects only.\u003C\u002Fstrong> \u003Ccode>reactive(0)\u003C\u002Fcode> doesn&#39;t work — there is nothing to proxy. Primitives need \u003Ccode>ref\u003C\u002Fcode>.","\u003Cstrong>The proxy is the reactive thing, not your original object.\u003C\u002Fstrong> \u003Ccode>reactive(obj) !== obj\u003C\u002Fcode>. Mutate \u003Ccode>obj\u003C\u002Fcode> directly and no effect hears about it.","\u003Cstrong>You cannot replace it.\u003C\u002Fstrong> Reassigning the variable (counter D) points your variable somewhere else and leaves every subscription behind.",{"id":232,"html":233,"type":83},"b39","\u003Cp>\u003Ccode>reactive()\u003C\u002Fcode> does handle arrays, \u003Ccode>Map\u003C\u002Fcode> and \u003Ccode>Set\u003C\u002Fcode> — mutations through the proxy (\u003Ccode>arr.push(x)\u003C\u002Fcode>, \u003Ccode>map.set(k, v)\u003C\u002Fcode>) are tracked. It&#39;s the \u003Cem>identity\u003C\u002Fem> of the container that you must not swap.\u003C\u002Fp>",{"id":235,"html":236,"text":236,"type":94,"level":31},"b40","The four ways reactivity gets lost",{"id":238,"html":239,"type":83},"b41","\u003Cp>Every &quot;why isn&#39;t my template updating&quot; question I&#39;ve seen is one of these four. All four are the same missing subscription.\u003C\u002Fp>",{"id":241,"html":242,"type":83},"b42","\u003Cp>\u003Cstrong>1. Destructuring a \u003Ccode>reactive()\u003C\u002Fcode> object.\u003C\u002Fstrong> \u003Ccode>const { count } = state\u003C\u002Fcode> copies the current value out. Fix it with \u003Ccode>toRefs\u003C\u002Fcode>, which converts each property into a ref that keeps pointing at the source:\u003C\u002Fp>",{"id":244,"code":245,"type":143,"language":193,"highlight":246},"b43","import { reactive, toRefs, toRef } from 'vue'\n\nconst state = reactive({ count: 0, name: 'Ada' })\n\nconst { count, name } = toRefs(state)   \u002F\u002F both are refs now\ncount.value++                            \u002F\u002F writes back to state.count\nconst justCount = toRef(state, 'count')  \u002F\u002F one property only",[],{"id":248,"html":249,"type":83},"b44","\u003Cp>\u003Cstrong>2. Replacing the object.\u003C\u002Fstrong> Keep the proxy and mutate it, or use a \u003Ccode>ref\u003C\u002Fcode> when you genuinely need to swap the whole value:\u003C\u002Fp>",{"id":251,"code":252,"type":143,"language":193,"highlight":253},"b45","const state = ref({ count: 0 })\nstate.value = { count: 1 }    \u002F\u002F fine — you're writing to `value`, which is tracked",[],{"id":255,"html":256,"type":83},"b46","\u003Cp>\u003Cstrong>3. Passing a value where you meant to pass a source.\u003C\u002Fstrong> A function that takes \u003Ccode>count: number\u003C\u002Fcode> receives a snapshot. If a composable needs to \u003Cem>keep watching\u003C\u002Fem> something, take the ref (or a getter) and unwrap inside:\u003C\u002Fp>",{"id":258,"code":259,"type":143,"language":193,"highlight":260},"b47","\u002F\u002F ❌ takes a snapshot\nfunction useDouble(n) { return computed(() => n * 2) }\n\u002F\u002F ✅ takes a source\nfunction useDouble(source) { return computed(() => unref(source) * 2) }",[],{"id":262,"html":263,"type":83},"b48","\u003Cp>\u003Cstrong>4. Reading after an \u003Ccode>await\u003C\u002Fcode>.\u003C\u002Fstrong> A \u003Ccode>watchEffect\u003C\u002Fcode> only tracks what it reads \u003Cstrong>synchronously\u003C\u002Fstrong>. Anything read after the first \u003Ccode>await\u003C\u002Fcode> in the callback is invisible to the tracker:\u003C\u002Fp>",{"id":265,"code":266,"type":143,"language":193,"highlight":267},"b49","watchEffect(async () => {\n  const id = props.id           \u002F\u002F ✅ tracked\n  await nextTick()\n  console.log(state.count)      \u002F\u002F ❌ NOT tracked — runs after the sync pass\n})",[],{"id":269,"html":270,"text":271,"type":94,"level":31},"b50","\u003Ccode>computed\u003C\u002Fcode>: cached, lazy, and worth understanding","computed: cached, lazy, and worth understanding",{"id":273,"html":274,"type":83},"b51","\u003Cp>A \u003Ccode>computed\u003C\u002Fcode> is an effect with a memory. It doesn&#39;t run when you create it — it runs when something reads it, then caches the result and returns the cache until one of its dependencies changes.\u003C\u002Fp>",{"id":276,"code":277,"type":143,"language":193,"highlight":278},"b52","const items = ref([...])\nconst total = computed(() => items.value.reduce((n, i) => n + i.price, 0))",[],{"id":280,"html":281,"type":83},"b53","\u003Cp>Two consequences that matter in real code:\u003C\u002Fp>",{"id":283,"type":100,"items":284,"ordered":18},"b54",[285,286],"\u003Cstrong>A \u003Ccode>computed\u003C\u002Fcode> is not a \u003Ccode>watch\u003C\u002Fcode>.\u003C\u002Fstrong> If the getter never gets read — the component isn&#39;t rendering it, nothing else touches it — it doesn&#39;t run. Never put side effects in one.","\u003Cstrong>Cheap reads.\u003C\u002Fstrong> Reading \u003Ccode>total.value\u003C\u002Fcode> a hundred times in a template costs one calculation. That&#39;s the reason to prefer \u003Ccode>computed\u003C\u002Fcode> over a method call for derived values.",{"id":288,"html":289,"text":290,"type":94,"level":31},"b55","\u003Ccode>watch\u003C\u002Fcode> vs \u003Ccode>watchEffect\u003C\u002Fcode>","watch vs watchEffect",{"id":292,"html":293,"type":83},"b56","\u003Cp>They look interchangeable and aren&#39;t:\u003C\u002Fp>",{"id":295,"code":296,"type":143,"language":193,"highlight":297},"b57","\u002F\u002F explicit source, lazy: does not run until `id` changes\nwatch(id, (next, prev) => load(next))\n\n\u002F\u002F implicit sources, eager: runs immediately, re-runs when anything it READ changes\nwatchEffect(() => load(id.value))",[],{"id":299,"html":300,"type":83},"b58","\u003Cp>Reach for \u003Ccode>watch\u003C\u002Fcode> when you need the old value, want it to stay lazy, or want to be explicit about the trigger. Reach for \u003Ccode>watchEffect\u003C\u002Fcode> when &quot;run this whenever anything it touches changes&quot; is genuinely what you mean.\u003C\u002Fp>",{"id":302,"html":303,"type":83},"b59","\u003Cp>\u003Cstrong>Timing:\u003C\u002Fstrong> callbacks flush \u003Cstrong>before\u003C\u002Fstrong> the component re-renders by default. If you need the updated DOM, ask for it — \u003Ccode>{ flush: &#39;post&#39; }\u003C\u002Fcode> — rather than reaching for \u003Ccode>nextTick\u003C\u002Fcode> inside the callback.\u003C\u002Fp>",{"id":305,"code":306,"type":143,"language":193,"highlight":307},"b60","watch(count, () => { \u002F* DOM is already updated here *\u002F }, { flush: 'post' })",[],{"id":309,"html":310,"text":310,"type":94,"level":31},"b61","Edge cases and gotchas",{"id":312,"type":100,"items":313,"ordered":18},"b62",[314,315,316,317,318,319],"\u003Cstrong>Proxy identity.\u003C\u002Fstrong> \u003Ccode>reactive(raw) !== raw\u003C\u002Fcode>. Comparing something from a reactive array against a raw object with \u003Ccode>===\u003C\u002Fcode> can fail even when it &quot;is&quot; the same item. \u003Ccode>toRaw()\u003C\u002Fcode> gets you back to the original when you need identity.","\u003Cstrong>A \u003Ccode>ref\u003C\u002Fcode> inside \u003Ccode>reactive\u003C\u002Fcode> is unwrapped.\u003C\u002Fstrong> \u003Ccode>const s = reactive({ n: ref(0) })\u003C\u002Fcode> then \u003Ccode>s.n\u003C\u002Fcode> is \u003Ccode>0\u003C\u002Fcode>, not the ref. Convenient in templates, surprising in logic. Inside a plain array or \u003Ccode>Map\u003C\u002Fcode> it is \u003Cem>not\u003C\u002Fem> unwrapped.","\u003Cstrong>Deep conversion has a cost.\u003C\u002Fstrong> \u003Ccode>ref(tenThousandRows)\u003C\u002Fcode> proxies objects as they&#39;re accessed. For large, read-mostly payloads use \u003Ccode>shallowRef\u003C\u002Fcode> and replace the whole value:\n\n\u003Ccode>js const rows = shallowRef([]) rows.value = await fetchRows()   \u002F\u002F one write, no deep proxying \u003C\u002Fcode>\n","\u003Cstrong>\u003Ccode>markRaw\u003C\u002Fcode>\u003C\u002Fstrong> keeps something out of the reactive system entirely — a class instance, a chart object, a third-party controller that a Proxy would break.","\u003Cstrong>Module-scope state is shared.\u003C\u002Fstrong> A \u003Ccode>ref\u003C\u002Fcode> created at the top level of a module is one instance for the whole app. That&#39;s a feature in the browser and a \u003Cstrong>cross-request data leak\u003C\u002Fstrong> on the server, where one process serves many users. If it renders on a server, create state per request.","\u003Cstrong>Arrays: index and length are tracked\u003C\u002Fstrong> through the proxy, so \u003Ccode>arr[0] = x\u003C\u002Fcode> and \u003Ccode>arr.length = 0\u003C\u002Fcode> both work in Vue 3 (this was a Vue 2 limitation people still carry around).",{"id":321,"html":322,"text":322,"type":94,"level":31},"b63","Best practices: which one, when",{"id":324,"type":100,"items":325,"ordered":18},"b64",[326,327,328,329,330,331],"\u003Cstrong>\u003Ccode>ref\u003C\u002Fcode> by default.\u003C\u002Fstrong> It works for every type, it survives being passed around, and \u003Ccode>.value\u003C\u002Fcode> is a visible marker that this is reactive state. A codebase that uses \u003Ccode>ref\u003C\u002Fcode> everywhere is boringly consistent.","\u003Cstrong>\u003Ccode>reactive\u003C\u002Fcode> for an object you always mutate and never replace\u003C\u002Fstrong> — a form model, a settings object. The nicer read syntax is a real benefit where it fits.","\u003Cstrong>Never destructure a \u003Ccode>reactive\u003C\u002Fcode> object\u003C\u002Fstrong> without \u003Ccode>toRefs\u003C\u002Fcode>. Never reassign one.","\u003Cstrong>Return refs from composables\u003C\u002Fstrong>, and accept refs or getters as inputs.","\u003Cstrong>\u003Ccode>computed\u003C\u002Fcode> for derived values, watchers for side effects.\u003C\u002Fstrong> If you&#39;re assigning to a \u003Ccode>ref\u003C\u002Fcode> inside a \u003Ccode>watch\u003C\u002Fcode>, ask whether it should have been a \u003Ccode>computed\u003C\u002Fcode>.","\u003Cstrong>Reach for \u003Ccode>shallowRef\u003C\u002Fcode>\u002F\u003Ccode>shallowReactive\u003C\u002Fcode>\u002F\u003Ccode>markRaw\u003C\u002Fcode>\u003C\u002Fstrong> when deep reactivity is cost without benefit.",{"id":333,"html":334,"type":83},"b65","\u003C!-- playground:start -->",{"id":336,"html":337,"text":337,"type":94,"level":31},"b66","🎮 Try it yourself",{"id":339,"html":340,"type":83},"b67","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":342,"html":343,"type":83},"b68","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":345,"html":346,"type":83},"b69","\u003C!-- playground:end -->",{"id":348,"html":349,"type":83},"b70","\u003C!-- quiz:start -->",{"id":351,"html":352,"text":352,"type":94,"level":31},"b71","🧠 Test yourself",{"id":354,"html":355,"type":83},"b72","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":357,"html":358,"type":83},"b73","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":360,"html":361,"type":83},"b74","\u003C!-- quiz:end -->",{"id":363,"html":364,"text":364,"type":94,"level":31},"b75","FAQ",{"id":366,"html":367,"text":368,"type":94,"level":43},"b76","Should I use \u003Ccode>ref\u003C\u002Fcode> or \u003Ccode>reactive\u003C\u002Fcode> in Vue 3?","Should I use ref or reactive in Vue 3?",{"id":370,"html":371,"type":83},"b77","\u003Cp>Default to \u003Ccode>ref\u003C\u002Fcode>. It handles primitives, survives destructuring of the \u003Cem>surrounding\u003C\u002Fem> object, and can be replaced wholesale. Use \u003Ccode>reactive\u003C\u002Fcode> for an object you&#39;ll only ever mutate, where reading \u003Ccode>form.email\u003C\u002Fcode> instead of \u003Ccode>form.value.email\u003C\u002Fcode> is worth the constraint.\u003C\u002Fp>",{"id":373,"html":374,"text":374,"type":94,"level":43},"b78","Why is my destructured reactive property not updating?",{"id":376,"html":377,"type":83},"b79","\u003Cp>Because destructuring copied the value out and left the subscription behind. \u003Ccode>const { count } = toRefs(state)\u003C\u002Fcode> keeps it, then use \u003Ccode>count.value\u003C\u002Fcode>.\u003C\u002Fp>",{"id":379,"html":380,"text":381,"type":94,"level":43},"b80","Does \u003Ccode>ref()\u003C\u002Fcode> make nested objects reactive?","Does ref() make nested objects reactive?",{"id":383,"html":384,"type":83},"b81","\u003Cp>Yes. \u003Ccode>ref\u003C\u002Fcode> applies deep conversion to object values, so nested mutations are tracked. Use \u003Ccode>shallowRef\u003C\u002Fcode> to opt out.\u003C\u002Fp>",{"id":386,"html":387,"text":388,"type":94,"level":43},"b82","Can I replace an entire \u003Ccode>reactive()\u003C\u002Fcode> object?","Can I replace an entire reactive() object?",{"id":390,"html":391,"type":83},"b83","\u003Cp>Not by reassigning the variable — that leaves every subscriber pointed at the old proxy. Either mutate it (\u003Ccode>Object.assign(state, next)\u003C\u002Fcode>) or hold the object in a \u003Ccode>ref\u003C\u002Fcode> and write \u003Ccode>state.value = next\u003C\u002Fcode>.\u003C\u002Fp>",{"id":393,"html":394,"text":395,"type":94,"level":43},"b84","Is \u003Ccode>computed\u003C\u002Fcode> cached?","Is computed cached?",{"id":397,"html":398,"type":83},"b85","\u003Cp>Yes, and lazily evaluated: it recomputes on the next read after a dependency changes, and not at all if nothing reads it.\u003C\u002Fp>",{"id":400,"html":401,"text":402,"type":94,"level":43},"b86","Does \u003Ccode>reactive()\u003C\u002Fcode> work with \u003Ccode>Map\u003C\u002Fcode> and \u003Ccode>Set\u003C\u002Fcode>?","Does reactive() work with Map and Set?",{"id":404,"html":405,"type":83},"b87","\u003Cp>Yes — Vue proxies the collection methods, so \u003Ccode>map.set()\u003C\u002Fcode> and \u003Ccode>set.add()\u003C\u002Fcode> trigger updates. Keep the proxy; don&#39;t swap it for a fresh collection.\u003C\u002Fp>",{"id":407,"html":408,"text":408,"type":94,"level":31},"b88","Cheat sheet",{"id":410,"head":411,"rows":415,"type":460},"b89",[412,413,414],"Task","Code","Notes",[416,420,424,428,432,436,440,444,448,452,456],[417,418,419],"Reactive primitive","\u003Ccode>const n = ref(0)\u003C\u002Fcode>","\u003Ccode>.value\u003C\u002Fcode> in JS, auto-unwrapped in templates",[421,422,423],"Reactive object","\u003Ccode>const s = reactive({})\u003C\u002Fcode>","no \u003Ccode>.value\u003C\u002Fcode>; never reassign",[425,426,427],"Shallow (big payload)","\u003Ccode>shallowRef([])\u003C\u002Fcode>","replace the whole value, no deep proxying",[429,430,431],"Destructure safely","\u003Ccode>const { a } = toRefs(state)\u003C\u002Fcode>","each property becomes a ref",[433,434,435],"One property as a ref","\u003Ccode>toRef(state, &#39;a&#39;)\u003C\u002Fcode>","writes flow back to \u003Ccode>state.a\u003C\u002Fcode>",[437,438,439],"Derived value","\u003Ccode>computed(() =&gt; a.value * 2)\u003C\u002Fcode>","cached, lazy, no side effects",[441,442,443],"React to a change","\u003Ccode>watch(a, (next, prev) =&gt; {})\u003C\u002Fcode>","lazy; gives you the old value",[445,446,447],"React to anything read","\u003Ccode>watchEffect(() =&gt; {})\u003C\u002Fcode>","eager; sync reads only",[449,450,451],"After the DOM updates","\u003Ccode>watch(a, cb, { flush: &#39;post&#39; })\u003C\u002Fcode>","default flush is pre-render",[453,454,455],"Escape the proxy","\u003Ccode>toRaw(s)\u003C\u002Fcode> \u002F \u003Ccode>markRaw(obj)\u003C\u002Fcode>","identity checks; opt out entirely",[457,458,459],"Accept ref or value","\u003Ccode>unref(source)\u003C\u002Fcode>","composable-friendly inputs","table",{"id":462,"code":463,"type":143,"language":193,"highlight":464},"b90","import { ref, reactive, computed, watch, toRefs, shallowRef } from 'vue'\n\n\u002F\u002F values you REPLACE → ref\nconst user = ref(null)\nuser.value = await fetchUser()\n\n\u002F\u002F objects you MUTATE → reactive\nconst form = reactive({ email: '', agreed: false })\nform.email = 'ada@example.com'\n\n\u002F\u002F destructuring: keep the subscription\nconst { email } = toRefs(form)\n\n\u002F\u002F derived → computed (cached, no side effects)\nconst valid = computed(() => email.value.includes('@') && form.agreed)\n\n\u002F\u002F side effects → watch\nwatch(valid, (isValid) => isValid && enableSubmit(), { flush: 'post' })\n\n\u002F\u002F large read-mostly data → shallowRef\nconst rows = shallowRef([])\nrows.value = await fetchRows()",[],{"id":466,"html":467,"text":467,"type":94,"level":31},"b91","Key takeaways",{"id":469,"type":100,"items":470,"ordered":18},"b92",[471,472,473,474,475],"Reactivity is a \u003Cstrong>subscription between a property and an effect, created when the effect reads the property.\u003C\u002Fstrong> Everything else follows.","Losing reactivity is always the same bug: you copied a value out, or you replaced the object the subscription pointed at.","\u003Ccode>ref\u003C\u002Fcode> for values you replace; \u003Ccode>reactive\u003C\u002Fcode> for objects you mutate and hold on to.","\u003Ccode>computed\u003C\u002Fcode> for derived data (cached, lazy, no side effects); watchers for side effects, with \u003Ccode>flush: &#39;post&#39;\u003C\u002Fcode> when you need the DOM.","Deep reactivity is a default, not a law — \u003Ccode>shallowRef\u003C\u002Fcode> and \u003Ccode>markRaw\u003C\u002Fcode> exist for when it costs more than it gives.",{"id":477,"html":478,"text":478,"type":94,"level":31},"b93","Back to that counter",{"id":480,"html":481,"type":83},"b94","\u003Cp>The number on screen said \u003Ccode>0\u003C\u002Fcode> while the log said \u003Ccode>3\u003C\u002Fcode> because the template subscribed to a property nobody was writing to. Not a broken framework, not a missing \u003Ccode>nextTick\u003C\u002Fcode> — a subscription pointed somewhere else.\u003C\u002Fp>",{"id":483,"html":484,"type":83},"b95","\u003Cp>That&#39;s the question worth asking every time reactivity &quot;doesn&#39;t work&quot;: \u003Cem>which effect subscribed to which property, and did I write to that one?\u003C\u002Fem> You&#39;ll answer it in about five seconds now.\u003C\u002Fp>",{"id":486,"html":487,"type":83},"b96","\u003Cp>Next Monday&#39;s episode goes one level down: what Vue does with those subscriptions when two of them change in the same tick.\u003C\u002Fp>",{"id":489,"html":490,"type":83},"b97","\u003Cp>Which of the four counters caught you out — or which reactivity bug cost you an afternoon?\u003C\u002Fp>",{"id":492,"type":493},"b98","divider",{"id":495,"html":496,"type":83},"b99","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":498,"html":499,"type":83},"b100","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":501,"type":100,"items":502,"ordered":18},"b101",[503,504,505],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","Your counter is wired up. The click handler runs — you added a `console.log`, it prints `3`. The number on screen still says `0`.\n\nNothing is broken. Vue never subscribed to the thing you changed.\n\nThat gap — between *the value changed* and *Vue knows the value changed* — is where almost every baffling Vue bug lives. It is also why `ref` and `reactive` feel like two competing ways to do the same job. They are not. Once you can see the subscription, the whole reactivity API stops being a list of functions to memorise and becomes one rule with a few consequences.\n\n## What you'll learn\n\nBy the end of this article you'll be able to:\n\n- Explain what Vue actually tracks, and at what moment it starts tracking\n- Choose between `ref()` and `reactive()` deliberately, not by habit\n- Spot the four ways code silently loses reactivity — destructuring being the famous one\n- Use `computed`, `watch` and `watchEffect` for what each is actually for\n- Reach for `shallowRef` and `toRaw` when deep reactivity is the wrong default\n- Keep the cheat sheet at the end open while you work\n\n## Who this is for\n\nYou've built at least one Vue 3 component with `\u003Cscript setup>`, and you've used `ref()` because a tutorial told you to. You don't need to know how a Proxy works — we'll build that up.\n\nThis article is written against **Vue 3.5.41**, the current stable release as of August 2026. (Vue 3.6 is in release candidate as this goes out; the reactivity semantics below are the stable, documented ones.)\n\n## Table of contents\n\n- [The problem: four counters, two silently broken](#the-problem-four-counters-two-silently-broken)\n- [The mental model: reactivity is a read-time subscription](#the-mental-model-reactivity-is-a-read-time-subscription)\n- [`ref()`: a box you can pass around](#ref-a-box-you-can-pass-around)\n- [`reactive()`: a proxy you have to hold on to](#reactive-a-proxy-you-have-to-hold-on-to)\n- [The four ways reactivity gets lost](#the-four-ways-reactivity-gets-lost)\n- [`computed`: cached, lazy, and worth understanding](#computed-cached-lazy-and-worth-understanding)\n- [`watch` vs `watchEffect`](#watch-vs-watcheffect)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices: which one, when](#best-practices-which-one-when)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n\n## The problem: four counters, two silently broken\n\nHere are four counters. Two work. Two update their data and never update the screen. Before reading on, decide which two.\n\n```vue\n\u003Cscript setup>\nimport { ref, reactive } from 'vue'\n\n\u002F\u002F A\nconst a = ref(0)\nconst incA = () => a.value++\n\n\u002F\u002F B\nconst b = reactive({ count: 0 })\nconst incB = () => b.count++\n\n\u002F\u002F C\nlet { count } = reactive({ count: 0 })\nconst incC = () => count++\n\n\u002F\u002F D\nlet d = reactive({ count: 0 })\nconst incD = () => { d = { count: d.count + 1 } }\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cbutton @click=\"incA\">A: {{ a }}\u003C\u002Fbutton>\n  \u003Cbutton @click=\"incB\">B: {{ b.count }}\u003C\u002Fbutton>\n  \u003Cbutton @click=\"incC\">C: {{ count }}\u003C\u002Fbutton>\n  \u003Cbutton @click=\"incD\">D: {{ d.count }}\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n```\n\n**A and B update. C and D don't.** And notice what C and D have in common: the numbers *do* change. `count` in C really does become 1, 2, 3. The bug is not in the arithmetic. It's that nobody told Vue to care.\n\nThe usual explanation is \"you can't destructure reactive objects.\" That's true, but it's a rule to memorise, and rules to memorise are what you fall back on when you don't have the model. Let's get the model instead.\n\n## The mental model: reactivity is a read-time subscription\n\nVue's reactivity has two halves, and almost everyone learns only the first.\n\nThe half people learn: **`reactive()` wraps an object in a Proxy**, so Vue can run code when you read or write a property.\n\nThe half that actually matters: **that code only does something while an effect is running.** An effect is a function Vue is currently executing on your behalf — a component's render function, a `computed` getter, a `watchEffect` callback. When a property is read *during* an effect, Vue records \"this effect depends on this property.\" When the property is later written, Vue re-runs the effects that recorded it.\n\n> **The mental model:** reactivity is not a property of your data. It is a **subscription between one property and one effect, created at the moment the effect reads the property.** No read inside an effect, no subscription. No subscription, no update.\n\nThat single sentence pays for the whole article. Run the four counters through it:\n\n- **A** — the template reads `a.value` while rendering. Subscription. `a.value++` writes it. Re-render. ✅\n- **B** — the template reads `b.count` (a property access on the proxy) while rendering. Subscription. ✅\n- **C** — `count` was read **once**, during setup, outside any effect, and its *value* (`0`) was copied into a new local variable. The template renders that plain number. There was never a subscription to create. ❌\n- **D** — the template reads `d.count` on the proxy, so there *is* a subscription — to the property `count` **of that proxy object**. Then `incD` throws the proxy away and points the local variable at a brand-new plain object. Nothing wrote to the property anyone subscribed to. ❌\n\nC and D are not two arbitrary rules. They are the same rule seen twice: **the subscription is to a property on a specific object, and you have to keep reading it, on that object, from inside an effect.**\n\n**Key concept:** \"Is this reactive?\" is the wrong question. The right question is \"which effect is subscribed to which property, and am I writing to that exact property?\"\n\n## `ref()`: a box you can pass around\n\n`ref()` sidesteps the problem C runs into by never handing you the value. It hands you a box with a `.value` property:\n\n```js\nimport { ref } from 'vue'\n\nconst count = ref(0)\ncount.value++            \u002F\u002F a write to the `value` property of the ref\n```\n\nBecause the value lives behind a property, reading it is a property access — trackable — and passing the box around passes the subscription target with it. That's the whole trick.\n\nTwo conveniences worth knowing precisely, because they're where refs feel magical and magic is hard to debug:\n\n**1. Refs are unwrapped in templates**, but only for top-level bindings from `\u003Cscript setup>`. `{{ count }}` works; `{{ someObject.count }}` where `someObject` is a plain object holding a ref does not.\n\n**2. Refs are deep by default.** `ref({ user: { name: 'Ada' } })` converts that inner object with `reactive()` under the hood, so `obj.value.user.name = 'Grace'` triggers updates. This is convenient and it is not free — see `shallowRef` below.\n\n```js\nconst state = ref({ user: { name: 'Ada' } })\nstate.value.user.name = 'Grace'   \u002F\u002F tracked: the inner object is reactive too\n```\n\n## `reactive()`: a proxy you have to hold on to\n\n`reactive()` returns a Proxy of the object you gave it. No `.value`, which reads more naturally:\n\n```js\nimport { reactive } from 'vue'\n\nconst form = reactive({ email: '', agreed: false })\nform.email = 'ada@example.com'   \u002F\u002F tracked\n```\n\nThree limits follow directly from \"it's a proxy around *that* object\":\n\n- **Objects only.** `reactive(0)` doesn't work — there is nothing to proxy. Primitives need `ref`.\n- **The proxy is the reactive thing, not your original object.** `reactive(obj) !== obj`. Mutate `obj` directly and no effect hears about it.\n- **You cannot replace it.** Reassigning the variable (counter D) points your variable somewhere else and leaves every subscription behind.\n\n`reactive()` does handle arrays, `Map` and `Set` — mutations through the proxy (`arr.push(x)`, `map.set(k, v)`) are tracked. It's the *identity* of the container that you must not swap.\n\n## The four ways reactivity gets lost\n\nEvery \"why isn't my template updating\" question I've seen is one of these four. All four are the same missing subscription.\n\n**1. Destructuring a `reactive()` object.** `const { count } = state` copies the current value out. Fix it with `toRefs`, which converts each property into a ref that keeps pointing at the source:\n\n```js\nimport { reactive, toRefs, toRef } from 'vue'\n\nconst state = reactive({ count: 0, name: 'Ada' })\n\nconst { count, name } = toRefs(state)   \u002F\u002F both are refs now\ncount.value++                            \u002F\u002F writes back to state.count\nconst justCount = toRef(state, 'count')  \u002F\u002F one property only\n```\n\n**2. Replacing the object.** Keep the proxy and mutate it, or use a `ref` when you genuinely need to swap the whole value:\n\n```js\nconst state = ref({ count: 0 })\nstate.value = { count: 1 }    \u002F\u002F fine — you're writing to `value`, which is tracked\n```\n\n**3. Passing a value where you meant to pass a source.** A function that takes `count: number` receives a snapshot. If a composable needs to *keep watching* something, take the ref (or a getter) and unwrap inside:\n\n```js\n\u002F\u002F ❌ takes a snapshot\nfunction useDouble(n) { return computed(() => n * 2) }\n\u002F\u002F ✅ takes a source\nfunction useDouble(source) { return computed(() => unref(source) * 2) }\n```\n\n**4. Reading after an `await`.** A `watchEffect` only tracks what it reads **synchronously**. Anything read after the first `await` in the callback is invisible to the tracker:\n\n```js\nwatchEffect(async () => {\n  const id = props.id           \u002F\u002F ✅ tracked\n  await nextTick()\n  console.log(state.count)      \u002F\u002F ❌ NOT tracked — runs after the sync pass\n})\n```\n\n## `computed`: cached, lazy, and worth understanding\n\nA `computed` is an effect with a memory. It doesn't run when you create it — it runs when something reads it, then caches the result and returns the cache until one of its dependencies changes.\n\n```js\nconst items = ref([...])\nconst total = computed(() => items.value.reduce((n, i) => n + i.price, 0))\n```\n\nTwo consequences that matter in real code:\n\n- **A `computed` is not a `watch`.** If the getter never gets read — the component isn't rendering it, nothing else touches it — it doesn't run. Never put side effects in one.\n- **Cheap reads.** Reading `total.value` a hundred times in a template costs one calculation. That's the reason to prefer `computed` over a method call for derived values.\n\n## `watch` vs `watchEffect`\n\nThey look interchangeable and aren't:\n\n```js\n\u002F\u002F explicit source, lazy: does not run until `id` changes\nwatch(id, (next, prev) => load(next))\n\n\u002F\u002F implicit sources, eager: runs immediately, re-runs when anything it READ changes\nwatchEffect(() => load(id.value))\n```\n\nReach for `watch` when you need the old value, want it to stay lazy, or want to be explicit about the trigger. Reach for `watchEffect` when \"run this whenever anything it touches changes\" is genuinely what you mean.\n\n**Timing:** callbacks flush **before** the component re-renders by default. If you need the updated DOM, ask for it — `{ flush: 'post' }` — rather than reaching for `nextTick` inside the callback.\n\n```js\nwatch(count, () => { \u002F* DOM is already updated here *\u002F }, { flush: 'post' })\n```\n\n## Edge cases and gotchas\n\n- **Proxy identity.** `reactive(raw) !== raw`. Comparing something from a reactive array against a raw object with `===` can fail even when it \"is\" the same item. `toRaw()` gets you back to the original when you need identity.\n- **A `ref` inside `reactive` is unwrapped.** `const s = reactive({ n: ref(0) })` then `s.n` is `0`, not the ref. Convenient in templates, surprising in logic. Inside a plain array or `Map` it is *not* unwrapped.\n- **Deep conversion has a cost.** `ref(tenThousandRows)` proxies objects as they're accessed. For large, read-mostly payloads use `shallowRef` and replace the whole value:\n\n  ```js\n  const rows = shallowRef([])\n  rows.value = await fetchRows()   \u002F\u002F one write, no deep proxying\n  ```\n\n- **`markRaw`** keeps something out of the reactive system entirely — a class instance, a chart object, a third-party controller that a Proxy would break.\n- **Module-scope state is shared.** A `ref` created at the top level of a module is one instance for the whole app. That's a feature in the browser and a **cross-request data leak** on the server, where one process serves many users. If it renders on a server, create state per request.\n- **Arrays: index and length are tracked** through the proxy, so `arr[0] = x` and `arr.length = 0` both work in Vue 3 (this was a Vue 2 limitation people still carry around).\n\n## Best practices: which one, when\n\n- **`ref` by default.** It works for every type, it survives being passed around, and `.value` is a visible marker that this is reactive state. A codebase that uses `ref` everywhere is boringly consistent.\n- **`reactive` for an object you always mutate and never replace** — a form model, a settings object. The nicer read syntax is a real benefit where it fits.\n- **Never destructure a `reactive` object** without `toRefs`. Never reassign one.\n- **Return refs from composables**, and accept refs or getters as inputs.\n- **`computed` for derived values, watchers for side effects.** If you're assigning to a `ref` inside a `watch`, ask whether it should have been a `computed`.\n- **Reach for `shallowRef`\u002F`shallowReactive`\u002F`markRaw`** when deep reactivity is cost without benefit.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n## FAQ\n\n### Should I use `ref` or `reactive` in Vue 3?\n\nDefault to `ref`. It handles primitives, survives destructuring of the *surrounding* object, and can be replaced wholesale. Use `reactive` for an object you'll only ever mutate, where reading `form.email` instead of `form.value.email` is worth the constraint.\n\n### Why is my destructured reactive property not updating?\n\nBecause destructuring copied the value out and left the subscription behind. `const { count } = toRefs(state)` keeps it, then use `count.value`.\n\n### Does `ref()` make nested objects reactive?\n\nYes. `ref` applies deep conversion to object values, so nested mutations are tracked. Use `shallowRef` to opt out.\n\n### Can I replace an entire `reactive()` object?\n\nNot by reassigning the variable — that leaves every subscriber pointed at the old proxy. Either mutate it (`Object.assign(state, next)`) or hold the object in a `ref` and write `state.value = next`.\n\n### Is `computed` cached?\n\nYes, and lazily evaluated: it recomputes on the next read after a dependency changes, and not at all if nothing reads it.\n\n### Does `reactive()` work with `Map` and `Set`?\n\nYes — Vue proxies the collection methods, so `map.set()` and `set.add()` trigger updates. Keep the proxy; don't swap it for a fresh collection.\n\n## Cheat sheet\n\n| Task | Code | Notes |\n| --- | --- | --- |\n| Reactive primitive | `const n = ref(0)` | `.value` in JS, auto-unwrapped in templates |\n| Reactive object | `const s = reactive({})` | no `.value`; never reassign |\n| Shallow (big payload) | `shallowRef([])` | replace the whole value, no deep proxying |\n| Destructure safely | `const { a } = toRefs(state)` | each property becomes a ref |\n| One property as a ref | `toRef(state, 'a')` | writes flow back to `state.a` |\n| Derived value | `computed(() => a.value * 2)` | cached, lazy, no side effects |\n| React to a change | `watch(a, (next, prev) => {})` | lazy; gives you the old value |\n| React to anything read | `watchEffect(() => {})` | eager; sync reads only |\n| After the DOM updates | `watch(a, cb, { flush: 'post' })` | default flush is pre-render |\n| Escape the proxy | `toRaw(s)` \u002F `markRaw(obj)` | identity checks; opt out entirely |\n| Accept ref or value | `unref(source)` | composable-friendly inputs |\n\n```js\nimport { ref, reactive, computed, watch, toRefs, shallowRef } from 'vue'\n\n\u002F\u002F values you REPLACE → ref\nconst user = ref(null)\nuser.value = await fetchUser()\n\n\u002F\u002F objects you MUTATE → reactive\nconst form = reactive({ email: '', agreed: false })\nform.email = 'ada@example.com'\n\n\u002F\u002F destructuring: keep the subscription\nconst { email } = toRefs(form)\n\n\u002F\u002F derived → computed (cached, no side effects)\nconst valid = computed(() => email.value.includes('@') && form.agreed)\n\n\u002F\u002F side effects → watch\nwatch(valid, (isValid) => isValid && enableSubmit(), { flush: 'post' })\n\n\u002F\u002F large read-mostly data → shallowRef\nconst rows = shallowRef([])\nrows.value = await fetchRows()\n```\n\n## Key takeaways\n\n- Reactivity is a **subscription between a property and an effect, created when the effect reads the property.** Everything else follows.\n- Losing reactivity is always the same bug: you copied a value out, or you replaced the object the subscription pointed at.\n- `ref` for values you replace; `reactive` for objects you mutate and hold on to.\n- `computed` for derived data (cached, lazy, no side effects); watchers for side effects, with `flush: 'post'` when you need the DOM.\n- Deep reactivity is a default, not a law — `shallowRef` and `markRaw` exist for when it costs more than it gives.\n\n## Back to that counter\n\nThe number on screen said `0` while the log said `3` because the template subscribed to a property nobody was writing to. Not a broken framework, not a missing `nextTick` — a subscription pointed somewhere else.\n\nThat's the question worth asking every time reactivity \"doesn't work\": *which effect subscribed to which property, and did I write to that one?* You'll answer it in about five seconds now.\n\nNext Monday's episode goes one level down: what Vue does with those subscriptions when two of them change in the same tick.\n\nWhich of the four counters caught you out — or which reactivity bug cost you an afternoon?\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":47,"canonical":508,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive","01a03410-2db9-7718-8aad-b4067a6de823",{"id":511,"locked":18},"01a03410-2e0b-71dc-8f4c-6b767adc2d3a",[513],{"id":514,"slug":46,"title":515,"_count":516},"01a03410-2e39-77b0-b6fc-7e6bfc919301","Vue Reactivity: ref vs reactive",{"questions":517},8,[519],{"locale":13,"slug":46},{"id":514,"slug":46,"title":515,"_count":521,"questionCount":517},{"questions":517},[523,525,529,531,535,539,543,545,548,552,556,559],{"slug":72,"name":73,"articles":524},65,{"slug":526,"name":527,"articles":528},"frontend","Frontend",57,{"slug":66,"name":67,"articles":530},55,{"slug":532,"name":533,"articles":534},"css","Css",24,{"slug":536,"name":537,"articles":538},"typescript","Typescript",10,{"slug":540,"name":541,"articles":542},"performance","Performance",9,{"slug":69,"name":70,"articles":544},6,{"slug":546,"name":547,"articles":544},"grammar","Grammar",{"slug":549,"name":550,"articles":551},"react","React",5,{"slug":553,"name":554,"articles":555},"node","Node",4,{"slug":557,"name":558,"articles":555},"html","Html",{"slug":560,"name":561,"articles":43},"ai","Ai",{"items":563,"meta":576},[564],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":565,"likeCount":19,"commentCount":19,"author":566,"vertical":567,"topic":568,"tags":569,"_count":574,"playground":575,"hasQuiz":17,"hasPlayground":17},91,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[570,571,572,573],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":46},{"page":75,"perPage":577,"total":75,"totalPages":75},7,"\u003Cdiv class=\"shj shj-lang-vue shj-multiline\" data-lang=\"vue\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003Cdiv>21\u003C\u002Fdiv>\u003Cdiv>22\u003C\u002Fdiv>\u003Cdiv>23\u003C\u002Fdiv>\u003Cdiv>24\u003C\u002Fdiv>\u003Cdiv>25\u003C\u002Fdiv>\u003Cdiv>26\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-class\">setup\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> ref\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> reactive \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'vue'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F A\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> a \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-func\">incA\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> a\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F B\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> b \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">reactive\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> count\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-func\">incB\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> b\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>count\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F C\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> count \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">reactive\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> count\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-func\">incC\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> count\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F D\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> d \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">reactive\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> count\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-func\">incD\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> d \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> count\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> d\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>count \u003Cspan class=\"shj-oper\">+\u003C\u002Fspan> \u003Cspan class=\"shj-num\">1\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">@click\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"incA\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>A: \u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> a \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">@click\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"incB\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>B: \u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> b\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>count \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">@click\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"incC\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>C: \u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> count \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">@click\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"incD\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>D: \u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> d\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>count \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">template\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> ref \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'vue'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> count \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\ncount\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>            \u003Cspan class=\"shj-cmnt\">\u002F\u002F a write to the `value` property of the ref\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> state \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> user\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> name\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Ada'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\nstate\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>user\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>name \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Grace'\u003C\u002Fspan>   \u003Cspan class=\"shj-cmnt\">\u002F\u002F tracked: the inner object is reactive too\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> reactive \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'vue'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> form \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">reactive\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> email\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">''\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> agreed\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\nform\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>email \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'ada@example.com'\u003C\u002Fspan>   \u003Cspan class=\"shj-cmnt\">\u002F\u002F tracked\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> reactive\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> toRefs\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> toRef \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'vue'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> state \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">reactive\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> count\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> name\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Ada'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> count\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> name \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">toRefs\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>state\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>   \u003Cspan class=\"shj-cmnt\">\u002F\u002F both are refs now\n\u003C\u002Fspan>count\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>                            \u003Cspan class=\"shj-cmnt\">\u002F\u002F writes back to state.count\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> justCount \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">toRef\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>state\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'count'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>  \u003Cspan class=\"shj-cmnt\">\u002F\u002F one property only\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> state \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> count\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\nstate\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> count\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">1\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>    \u003Cspan class=\"shj-cmnt\">\u002F\u002F fine — you're writing to `value`, which is tracked\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F ❌ takes a snapshot\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">useDouble\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>n\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-func\">computed\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> n \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> \u003Cspan class=\"shj-num\">2\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F ✅ takes a source\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">useDouble\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>source\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-func\">computed\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">unref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>source\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> \u003Cspan class=\"shj-num\">2\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-func\">watchEffect\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> id \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> props\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>id           \u003Cspan class=\"shj-cmnt\">\u002F\u002F ✅ tracked\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">nextTick\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>state\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>count\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>      \u003Cspan class=\"shj-cmnt\">\u002F\u002F ❌ NOT tracked — runs after the sync pass\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> items \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> total \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">computed\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> items\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">reduce\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>n\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> i\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> n \u003Cspan class=\"shj-oper\">+\u003C\u002Fspan> i\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>price\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F explicit source, lazy: does not run until `id` changes\n\u003C\u002Fspan>\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>id\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>next\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> prev\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">load\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>next\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F implicit sources, eager: runs immediately, re-runs when anything it READ changes\n\u003C\u002Fspan>\u003Cspan class=\"shj-func\">watchEffect\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">load\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>id\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-oneline\" data-lang=\"js\">\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>count\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F* DOM is already updated here *\u002F\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> flush\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'post'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003Cdiv>21\u003C\u002Fdiv>\u003Cdiv>22\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> ref\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> reactive\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> computed\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> watch\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> toRefs\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> shallowRef \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'vue'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F values you REPLACE → ref\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> user \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">null\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\nuser\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetchUser\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F objects you MUTATE → reactive\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> form \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">reactive\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> email\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">''\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> agreed\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\nform\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>email \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'ada@example.com'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F destructuring: keep the subscription\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> email \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">toRefs\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>form\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F derived → computed (cached, no side effects)\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> valid \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">computed\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> email\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">includes\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'@'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">&#38;&#38;\u003C\u002Fspan> form\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>agreed\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F side effects → watch\n\u003C\u002Fspan>\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>valid\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>isValid\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> isValid \u003Cspan class=\"shj-oper\">&#38;&#38;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">enableSubmit\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> flush\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'post'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F large read-mostly data → shallowRef\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> rows \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">shallowRef\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\nrows\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetchRows\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":591},[]]