[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-vue-weekly-computed-caching-invalidation":44,"search-suggestions":442,"related-vue-weekly-computed-caching-invalidation":482,"code:vue:true:xlmfv2":542,"code:vue:true:11mxwqd":543,"code:js:true:1rz836o":544,"code:js:true:1ydwcw":545,"code:js:true:14gmkqf":546,"code:js:true:nurchc":547,"code:js:true:y51xlu":548,"comments-01a08af7-9d6f-74df-b76e-f6c445f54a47":549},[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":407,"seo":408,"translationGroupId":410,"series":411,"podcastUrl":48,"verticalId":5,"thread":430,"assessments":432,"translations":438,"quiz":440},"01a08af7-9d6f-74df-b76e-f6c445f54a47","vue-weekly-computed-caching-invalidation","Vue computed(): What It Caches and When It Reruns",null,"Vue's computed() caches its result and only reruns when a tracked dependency changes, not on every read. The exact caching and invalidation model, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-computed-caching-invalidation.png",12,"2026-09-30T12:10:53.362Z",39,{"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 computed() caching &amp; invalidation — interactive playground",{"blocks":79,"version":75},[80,84,89,92,101,104,107,110,121,124,127,130,135,138,141,144,147,150,155,158,161,164,168,171,174,179,182,185,189,192,195,199,202,205,208,212,215,218,221,227,230,233,241,244,252,255,259,262,266,269,272,275,278,281,284,287,290,324,328,331,338,341,344,347,350,353,356,359,362,365,368,371,374,377,380,383,389,392,395,398,401],{"id":81,"html":82,"type":83},"b1","\u003Cp>You call a \u003Ccode>computed()\u003C\u002Fcode> getter fifty times in one render and it only runs once. You mutate a \u003Ccode>ref\u003C\u002Fcode> it depends on and — nothing happens, not yet. Then you read \u003Ccode>.value\u003C\u002Fcode> and it recomputes. If that sequence feels slightly magical, it&#39;s because \u003Ccode>computed()\u003C\u002Fcode> isn&#39;t a function you call. It&#39;s a cached, lazily-evaluated effect with its own subscription list, and once you see the actual rule, none of it is magic anymore.\u003C\u002Fp>","paragraph",{"id":85,"html":86,"text":87,"type":88,"level":31},"b2","What You&#39;ll Learn","What You'll Learn","heading",{"id":90,"html":91,"type":83},"b3","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":93,"type":94,"items":95,"ordered":18},"b4","list",[96,97,98,99,100],"Explain exactly when a \u003Ccode>computed()\u003C\u002Fcode> getter runs and when it doesn&#39;t","Tell the difference between a dependency changing and a computed \u003Cstrong>recomputing\u003C\u002Fstrong>","Predict how many times a getter actually executes in a given sequence of writes and reads","Choose correctly between \u003Ccode>computed()\u003C\u002Fcode>, \u003Ccode>watch()\u003C\u002Fcode>, and \u003Ccode>watchEffect()\u003C\u002Fcode> for a given job","Spot the two most common ways developers accidentally break computed caching",{"id":102,"html":103,"text":103,"type":88,"level":31},"b5","Who This Is For",{"id":105,"html":106,"type":83},"b6","\u003Cp>You&#39;ve written a Vue 3 component with \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode> and used \u003Ccode>ref\u003C\u002Fcode>, \u003Ccode>reactive\u003C\u002Fcode>, and \u003Ccode>computed\u003C\u002Fcode> at least once. This article assumes you know \u003Cem>what\u003C\u002Fem> a computed property looks like in code; it&#39;s here to fix \u003Cem>how\u003C\u002Fem> you think it behaves internally, which the syntax alone doesn&#39;t teach you. If the read-time tracking that makes any of this possible is still fuzzy, \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-reactivity-explained-ref-vs-reactive-cheat-sheet-4nij\">Vue Reactivity: ref vs reactive\u003C\u002Fa> builds that foundation — useful background, not required reading.\u003C\u002Fp>",{"id":108,"html":109,"text":109,"type":88,"level":31},"b7","Table of Contents",{"id":111,"type":94,"items":112,"ordered":18},"b8",[113,114,115,116,117,118,119,120],"\u003Ca href=\"#the-problem-recomputing-what-you-already-computed\">The Problem: Recomputing What You Already Computed\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-a-lazy-cached-effect\">The Mental Model: A Lazy, Cached Effect\u003C\u002Fa>","\u003Ca href=\"#building-it-up-how-computed-actually-behaves\">Building It Up: How computed() Actually Behaves\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge Cases and Gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices\">Best Practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat Sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key Takeaways\u003C\u002Fa>",{"id":122,"html":123,"type":83},"b9","\u003Cp>This is written against \u003Cstrong>Vue 3.5.x\u003C\u002Fstrong> (verified 3.5.43 on npm, September 2026). Vue 3.6 is currently a release candidate adding an alien-signals-based reactivity core and an opt-in &quot;Vapor Mode&quot; compiler — neither changes the caching contract described here, so this article holds for both.\u003C\u002Fp>",{"id":125,"html":126,"text":126,"type":88,"level":31},"b10","The Problem: Recomputing What You Already Computed",{"id":128,"html":129,"type":83},"b11","\u003Cp>Say you&#39;re rendering a cart summary and you need a formatted total:\u003C\u002Fp>",{"id":131,"code":132,"type":133,"language":61,"highlight":134},"b12","\u003Cscript setup>\nimport { ref } from 'vue'\n\nconst items = ref([\n  { name: 'Keyboard', price: 89.5, qty: 1 },\n  { name: 'Mouse', price: 24.99, qty: 2 },\n])\n\nfunction formattedTotal() {\n  console.log('recalculating total…')\n  const total = items.value.reduce((sum, i) => sum + i.price * i.qty, 0)\n  return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(total)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>Total: {{ formattedTotal() }}\u003C\u002Fp>\n  \u003Cp>Total (again): {{ formattedTotal() }}\u003C\u002Fp>\n  \u003Cp>Total (once more): {{ formattedTotal() }}\u003C\u002Fp>\n\u003C\u002Ftemplate>","code",[],{"id":136,"html":137,"type":83},"b13","\u003Cp>\u003Ccode>formattedTotal\u003C\u002Fcode> is a plain method, so the template calls it every time it appears, and again on every re-render triggered by anything else in the component — a hover state, an unrelated ref, a parent re-rendering. The console fills with \u003Ccode>recalculating total…\u003C\u002Fcode> for work whose answer hasn&#39;t changed. It&#39;s correct, but it&#39;s wasteful, and the waste scales with how many places in the template read the value and how often the component re-renders for unrelated reasons.\u003C\u002Fp>",{"id":139,"html":140,"type":83},"b14","\u003Cp>The fix looks trivial — wrap it in \u003Ccode>computed()\u003C\u002Fcode> — but \u003Cem>why\u003C\u002Fem> that fixes it, and exactly what it changes, is the actual subject of this article.\u003C\u002Fp>",{"id":142,"html":143,"text":143,"type":88,"level":31},"b15","The Mental Model: A Lazy, Cached Effect",{"id":145,"html":146,"type":83},"b16","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> a \u003Ccode>computed()\u003C\u002Fcode> ref is a special reactive effect that (1) tracks whatever reactive state its getter reads, exactly like a component render or a \u003Ccode>watchEffect\u003C\u002Fcode> does, (2) caches the return value instead of re-running immediately when a dependency changes, and (3) only re-runs the getter the next time something reads \u003Ccode>.value\u003C\u002Fcode> \u003Cem>after\u003C\u002Fem> it has been marked dirty.\u003C\u002Fp>",{"id":148,"html":149,"type":83},"b17","\u003Cp>That&#39;s two separate events, not one:\u003C\u002Fp>",{"id":151,"type":94,"items":152,"ordered":18},"b18",[153,154],"\u003Cstrong>Invalidation\u003C\u002Fstrong> happens the instant a tracked dependency is written to. It&#39;s synchronous and cheap: Vue marks the computed dirty (in 3.5, a computed nobody subscribes to isn&#39;t even notified — it compares dependency version counters on its next read instead). Nothing is recomputed yet.","\u003Cstrong>Recomputation\u003C\u002Fstrong> happens lazily, on the next \u003Ccode>.value\u003C\u002Fcode> read, and only if the dirty flag is set. If nobody ever reads it again, the getter never reruns — you paid for a flag flip, not a recalculation.",{"id":156,"html":157,"type":83},"b19","\u003Cp>This is the same pull-based laziness that makes \u003Ccode>computed\u003C\u002Fcode> cheaper than a method call under repeated reads, and it&#39;s also exactly the behavior that surprises people the first time they log something inside a getter and see it fire at a moment they didn&#39;t expect.\u003C\u002Fp>",{"id":159,"html":160,"text":160,"type":88,"level":31},"b20","Building It Up: How computed() Actually Behaves",{"id":162,"html":163,"text":163,"type":88,"level":43},"b21","Stage 1: The Cache Is Real — Prove It With a Counter",{"id":165,"code":166,"type":133,"language":61,"highlight":167},"b22","\u003Cscript setup>\nimport { ref, computed } from 'vue'\n\nconst a = ref(2)\nconst b = ref(3)\nlet getterRuns = 0\n\nconst sum = computed(() => {\n  getterRuns++\n  return a.value + b.value\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>{{ sum }} — {{ sum }} — {{ sum }}\u003C\u002Fp>\n  \u003Cp>Getter ran {{ getterRuns }} time(s)\u003C\u002Fp>\n\u003C\u002Ftemplate>",[],{"id":169,"html":170,"type":83},"b23","\u003Cp>Even though the template reads \u003Ccode>sum\u003C\u002Fcode> three times in one render, \u003Ccode>getterRuns\u003C\u002Fcode> is \u003Ccode>1\u003C\u002Fcode>. \u003Cstrong>Key concept:\u003C\u002Fstrong> the first read evaluates and caches; every subsequent read before an invalidation returns the cached value without touching the getter at all.\u003C\u002Fp>",{"id":172,"html":173,"text":173,"type":88,"level":43},"b24","Stage 2: Invalidation Is Synchronous, Recomputation Is Lazy",{"id":175,"code":176,"type":133,"language":177,"highlight":178},"b25","a.value = 10          \u002F\u002F invalidation: dirty flag set, getter has NOT run\nconsole.log(getterRuns) \u002F\u002F still 1\n\nconsole.log(sum.value)  \u002F\u002F recomputation happens here\nconsole.log(getterRuns) \u002F\u002F now 2","js",[],{"id":180,"html":181,"type":83},"b26","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> writing to \u003Ccode>a\u003C\u002Fcode> doesn&#39;t run the getter — it only marks the cache stale. The getter runs on the \u003Cem>next read\u003C\u002Fem>, which might be immediately, might be several ticks later, or might never happen if nothing ever reads that computed again (say, the component that used it unmounted first).\u003C\u002Fp>",{"id":183,"html":184,"type":83},"b27","\u003Cp>This is also why mutating a dependency ten times in a row before anything reads the computed still only costs \u003Cstrong>one\u003C\u002Fstrong> recompute, not ten:\u003C\u002Fp>",{"id":186,"code":187,"type":133,"language":177,"highlight":188},"b28","a.value++\na.value++\na.value++\nconsole.log(sum.value) \u002F\u002F getter runs exactly once here, using the final values",[],{"id":190,"html":191,"text":191,"type":88,"level":43},"b29","Stage 3: Dependency Tracking Is Re-Done Every Run",{"id":193,"html":194,"type":83},"b30","\u003Cp>A computed&#39;s dependency list isn&#39;t fixed at creation — it&#39;s rebuilt on every evaluation, so branches matter:\u003C\u002Fp>",{"id":196,"code":197,"type":133,"language":177,"highlight":198},"b31","const useB = ref(false)\nconst result = computed(() => (useB.value ? b.value : a.value))",[],{"id":200,"html":201,"type":83},"b32","\u003Cp>If \u003Ccode>useB\u003C\u002Fcode> is \u003Ccode>false\u003C\u002Fcode>, this run only reads \u003Ccode>useB\u003C\u002Fcode> and \u003Ccode>a\u003C\u002Fcode> — writing to \u003Ccode>b\u003C\u002Fcode> will not invalidate \u003Ccode>result\u003C\u002Fcode> at all, because \u003Ccode>b\u003C\u002Fcode> was never touched during the last evaluation. Flip \u003Ccode>useB\u003C\u002Fcode> to \u003Ccode>true\u003C\u002Fcode> and the \u003Cem>next\u003C\u002Fem> evaluation reads \u003Ccode>b\u003C\u002Fcode> instead, and from then on \u003Ccode>b\u003C\u002Fcode> is a real dependency and \u003Ccode>a\u003C\u002Fcode> is not. \u003Cstrong>Key concept:\u003C\u002Fstrong> the subscription list is a snapshot of what the getter actually read last time, not what it could theoretically read.\u003C\u002Fp>",{"id":203,"html":204,"text":204,"type":88,"level":43},"b33","Stage 4: Writable Computed",{"id":206,"html":207,"type":83},"b34","\u003Cp>A computed can accept writes by passing \u003Ccode>{ get, set }\u003C\u002Fcode> instead of a bare function:\u003C\u002Fp>",{"id":209,"code":210,"type":133,"language":177,"highlight":211},"b35","const first = ref('Ada')\nconst last = ref('Lovelace')\n\nconst fullName = computed({\n  get: () => `${first.value} ${last.value}`,\n  set(value) {\n    [first.value, last.value] = value.split(' ')\n  },\n})\n\nfullName.value = 'Grace Hopper' \u002F\u002F runs the setter, which writes first & last",[],{"id":213,"html":214,"type":83},"b36","\u003Cp>The caching rule for the \u003Ccode>get\u003C\u002Fcode> side is identical to the read-only form. The \u003Ccode>set\u003C\u002Fcode> side isn&#39;t cached at all — it&#39;s a plain function call every time you assign.\u003C\u002Fp>",{"id":216,"html":217,"text":217,"type":88,"level":43},"b37","Stage 5: computed() vs watch() vs watchEffect()",{"id":219,"html":220,"type":83},"b38","\u003Cp>All three are reactive effects that track dependencies the same way. What they&#39;re \u003Cem>for\u003C\u002Fem> is different:\u003C\u002Fp>",{"id":222,"type":94,"items":223,"ordered":18},"b39",[224,225,226],"\u003Cstrong>\u003Ccode>computed()\u003C\u002Fcode>\u003C\u002Fstrong> — derive a value from other reactive state, synchronously, with no side effects. Use it whenever the answer is &quot;a new piece of data computed from data I already have.&quot;","\u003Cstrong>\u003Ccode>watchEffect()\u003C\u002Fcode>\u003C\u002Fstrong> — run a side effect (fetch, log, DOM measurement) and automatically track whatever reactive state it reads. Runs immediately on creation, then again whenever a tracked dependency changes.","\u003Cstrong>\u003Ccode>watch()\u003C\u002Fcode>\u003C\u002Fstrong> — run a side effect in response to \u003Cem>specific\u003C\u002Fem> sources you name explicitly, with access to both the old and new value, and control over timing (\u003Ccode>flush: &#39;post&#39;\u003C\u002Fcode>) and whether it fires immediately (\u003Ccode>immediate: true\u003C\u002Fcode>).",{"id":228,"html":229,"type":83},"b40","\u003Cp>If you find yourself putting \u003Ccode>fetch()\u003C\u002Fcode>, \u003Ccode>console.log()\u003C\u002Fcode>, or a router push inside a \u003Ccode>computed()\u003C\u002Fcode> getter, that&#39;s the signal you wanted \u003Ccode>watch\u003C\u002Fcode> or \u003Ccode>watchEffect\u003C\u002Fcode> instead — a computed getter must stay pure, because Vue may call it, skip it, or defer it based on caching, and a getter with side effects makes those decisions unpredictable for you.\u003C\u002Fp>",{"id":231,"html":232,"text":232,"type":88,"level":31},"b41","Edge Cases and Gotchas",{"id":234,"type":94,"items":235,"ordered":18},"b42",[236,237,238,239,240],"\u003Cstrong>An impure getter breaks the contract.\u003C\u002Fstrong> Because Vue decides \u003Cem>when\u003C\u002Fem> (or whether) to rerun a computed getter, any side effect inside it runs an unpredictable number of times — including zero, if nothing ever reads the computed after invalidation.","\u003Cstrong>Async getters don&#39;t work.\u003C\u002Fstrong> \u003Ccode>computed()\u003C\u002Fcode> expects a synchronous return value; an async function returns a \u003Ccode>Promise\u003C\u002Fcode>, and Vue has no way to &quot;await&quot; a cache fill. For async derived state, use \u003Ccode>watchEffect\u003C\u002Fcode> (or \u003Ccode>watch\u003C\u002Fcode>) to populate a plain \u003Ccode>ref\u003C\u002Fcode> yourself, or reach for a dedicated async-state composable.","\u003Cstrong>Destructuring a \u003Ccode>reactive()\u003C\u002Fcode> object breaks tracking\u003C\u002Fstrong>, and that silently breaks any computed built from the destructured variable, because there&#39;s no longer a reactive property to subscribe to. Use \u003Ccode>toRefs()\u003C\u002Fcode> (or read through the object) before destructuring.","\u003Cstrong>Since Vue 3.4, a computed only propagates when its \u003Cem>value\u003C\u002Fem> actually changed\u003C\u002Fstrong>, not merely when a dependency changed. If \u003Ccode>isEven\u003C\u002Fcode> is \u003Ccode>computed(() =&gt; count.value % 2 === 0)\u003C\u002Fcode> and \u003Ccode>count\u003C\u002Fcode> goes from \u003Ccode>2\u003C\u002Fcode> to \u003Ccode>4\u003C\u002Fcode>, the getter reruns but returns the same \u003Ccode>true\u003C\u002Fcode>, so \u003Ccode>isEven\u003C\u002Fcode>&#39;s own dependents don&#39;t rerun — this specifically helps chains of computed-on-computed avoid cascading work when a dependency changed but the derived result didn&#39;t. As of 3.4 you can also read the previous result as the getter&#39;s first argument: \u003Ccode>computed((prev) =&gt; …)\u003C\u002Fcode>.","\u003Cstrong>A computed with no active subscriber can still be read directly\u003C\u002Fstrong> (e.g. from a \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode> variable used outside the template) — it doesn&#39;t need a component render as its consumer, any effect reading \u003Ccode>.value\u003C\u002Fcode> keeps the caching rule intact.",{"id":242,"html":243,"text":243,"type":88,"level":31},"b43","Best Practices",{"id":245,"type":94,"items":246,"ordered":18},"b44",[247,248,249,250,251],"\u003Cstrong>Reach for \u003Ccode>computed()\u003C\u002Fcode>\u003C\u002Fstrong> whenever the value is &quot;purely derived from state I already have&quot; and doesn&#39;t need to log, fetch, or otherwise reach outside itself.","\u003Cstrong>Reach for \u003Ccode>watch()\u003C\u002Fcode>\u003C\u002Fstrong> when you need the previous value, need to react to one specific source, or need to control flush timing around DOM updates.","\u003Cstrong>Reach for \u003Ccode>watchEffect()\u003C\u002Fcode>\u003C\u002Fstrong> for a side effect whose dependencies you&#39;re happy to let Vue infer automatically, and that should also run once immediately.","\u003Cstrong>Never mutate other reactive state from inside a computed getter.\u003C\u002Fstrong> It works today by accident and turns into an infinite-invalidation bug the moment the mutated state is also one of the computed&#39;s own dependencies.","\u003Cstrong>Keep computed getters cheap enough that recomputing occasionally is fine\u003C\u002Fstrong> — the cache saves you from \u003Cem>repeated\u003C\u002Fem> reads between invalidations, not from the cost of a single run.",{"id":253,"html":254,"text":254,"type":88,"level":31},"b45","FAQ",{"id":256,"html":257,"text":258,"type":88,"level":43},"b46","Does computed() rerun every time I read \u003Ccode>.value\u003C\u002Fcode>?","Does computed() rerun every time I read .value?",{"id":260,"html":261,"type":83},"b47","\u003Cp>No. It reruns only on a read that happens \u003Cem>after\u003C\u002Fem> a tracked dependency has changed since the last evaluation. Reading the same, non-invalidated computed any number of times returns the cached value.\u003C\u002Fp>",{"id":263,"html":264,"text":265,"type":88,"level":43},"b48","Why didn&#39;t my computed update?","Why didn't my computed update?",{"id":267,"html":268,"type":83},"b49","\u003Cp>Almost always one of two reasons: the property you changed was never read during the getter&#39;s last run (so it isn&#39;t a tracked dependency — see Stage 3), or you mutated a plain destructured variable instead of the reactive source itself. A third, rarer one: the getter reads something that isn&#39;t reactive at all — \u003Ccode>computed(() =&gt; Date.now())\u003C\u002Fcode> never updates.\u003C\u002Fp>",{"id":270,"html":271,"text":271,"type":88,"level":43},"b50","Can a computed getter be async?",{"id":273,"html":274,"type":83},"b51","\u003Cp>No — the getter must return a value synchronously. Model async derived state with a plain \u003Ccode>ref\u003C\u002Fcode> updated from inside \u003Ccode>watchEffect\u003C\u002Fcode> or \u003Ccode>watch\u003C\u002Fcode> instead.\u003C\u002Fp>",{"id":276,"html":277,"text":277,"type":88,"level":43},"b52","Is computed() just memoization?",{"id":279,"html":280,"type":83},"b53","\u003Cp>It&#39;s memoization plus an automatic, fine-grained invalidation signal — you don&#39;t hand it a dependency array like \u003Ccode>useMemo\u003C\u002Fcode>; it discovers its dependencies by watching what the getter reads.\u003C\u002Fp>",{"id":282,"html":283,"text":283,"type":88,"level":43},"b54","Do multiple components reading the same computed cause multiple recomputes?",{"id":285,"html":286,"type":83},"b55","\u003Cp>No. The cached value and the dirty flag live on the computed ref itself, shared by every reader — the getter still runs at most once per invalidation, regardless of how many places read \u003Ccode>.value\u003C\u002Fcode>.\u003C\u002Fp>",{"id":288,"html":289,"text":289,"type":88,"level":31},"b56","Cheat Sheet",{"id":291,"head":292,"rows":295,"type":323},"b57",[293,294],"Situation","What happens",[296,299,302,305,308,311,314,317,320],[297,298],"Read \u003Ccode>computed.value\u003C\u002Fcode> repeatedly, no writes between","Getter runs once; every later read returns the cache",[300,301],"Write to a tracked dependency","Marks the computed dirty \u003Cstrong>synchronously\u003C\u002Fstrong>; getter does \u003Cem>not\u003C\u002Fem> run yet",[303,304],"Read \u003Ccode>.value\u003C\u002Fcode> after a dirty mark","Getter runs now, result re-cached",[306,307],"Write to a dependency multiple times before any read","Still exactly one recompute, on the next read",[309,310],"Write to a property the getter didn&#39;t read last run","No invalidation — it isn&#39;t a tracked dependency",[312,313],"Getter branches on a condition","Only the branch actually taken this run is tracked",[315,316],"Need previous value","\u003Ccode>computed((prev) =&gt; …)\u003C\u002Fcode> or \u003Ccode>computed({ get(prev) {…}, set })\u003C\u002Fcode> (Vue 3.4+)",[318,319],"Need to write to it","\u003Ccode>computed({ get, set })\u003C\u002Fcode>",[321,322],"Need a side effect, not a derived value","\u003Ccode>watch()\u003C\u002Fcode> or \u003Ccode>watchEffect()\u003C\u002Fcode>, not \u003Ccode>computed()\u003C\u002Fcode>","table",{"id":325,"code":326,"type":133,"language":177,"highlight":327},"b58","import { ref, computed } from 'vue'\n\nconst price = ref(20)\nconst qty = ref(3)\n\n\u002F\u002F Derived, pure, cached — recomputes only when price or qty change\n\u002F\u002F and something actually reads .value afterward.\nconst total = computed(() => price.value * qty.value)\n\n\u002F\u002F Writable computed\nconst discountedTotal = computed({\n  get: () => total.value * 0.9,\n  set(value) {\n    price.value = value \u002F 0.9 \u002F qty.value\n  },\n})",[],{"id":329,"html":330,"text":330,"type":88,"level":31},"b59","Key Takeaways",{"id":332,"type":94,"items":333,"ordered":18},"b60",[334,335,336,337],"\u003Ccode>computed()\u003C\u002Fcode> caches its result and reevaluates lazily — invalidation (a dependency write) and recomputation (the next read) are two separate moments, not one.","Its dependency list is rebuilt every run, so conditional branches inside the getter can add or drop dependencies between evaluations.","Since Vue 3.4, a computed only propagates to \u003Cem>its\u003C\u002Fem> dependents when the recomputed value actually differs from the last one.","Keep computed getters pure and synchronous; reach for \u003Ccode>watch()\u003C\u002Fcode> or \u003Ccode>watchEffect()\u003C\u002Fcode> the moment you need a side effect or async work.",{"id":339,"html":340,"text":340,"type":88,"level":31},"b61","Wrapping Up",{"id":342,"html":343,"type":83},"b62","\u003Cp>That console full of \u003Ccode>recalculating total…\u003C\u002Fcode> from the opening example disappears the moment \u003Ccode>formattedTotal()\u003C\u002Fcode> becomes a \u003Ccode>computed()\u003C\u002Fcode> — not because Vue got smarter about \u003Cem>when\u003C\u002Fem> to call it, but because you stopped calling it directly at all. You&#39;re reading a cache that knows, dependency by dependency, exactly when it&#39;s stale. Once you can picture that dirty flag and the lazy pull that clears it, computed properties stop being a syntax you memorized and become a piece of Vue&#39;s design you can reason about.\u003C\u002Fp>",{"id":345,"html":346,"type":83},"b63","\u003Cp>If you&#39;ve been burned by a computed that &quot;wouldn&#39;t update,&quot; what turned out to be the actual dependency it was missing? Drop it in the comments — it&#39;s almost always Stage 3 in disguise.\u003C\u002Fp>",{"id":348,"html":349,"type":83},"b64","\u003C!-- playground:start -->",{"id":351,"html":352,"text":352,"type":88,"level":31},"b65","🎮 Try it yourself",{"id":354,"html":355,"type":83},"b66","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-computed-caching-invalidation\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":357,"html":358,"type":83},"b67","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":360,"html":361,"type":83},"b68","\u003C!-- playground:end -->",{"id":363,"html":364,"type":83},"b69","\u003C!-- quiz:start -->",{"id":366,"html":367,"text":367,"type":88,"level":31},"b70","🧠 Test yourself",{"id":369,"html":370,"type":83},"b71","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-computed-caching-invalidation\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":372,"html":373,"type":83},"b72","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":375,"html":376,"type":83},"b73","\u003C!-- quiz:end -->",{"id":378,"html":379,"type":83},"b74","\u003C!-- related:start -->",{"id":381,"html":382,"text":382,"type":88,"level":31},"b75","📚 Read next",{"id":384,"type":94,"items":385,"ordered":18},"b76",[386,387,388],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-composables-shared-state-trap\">Vue Composables: The Shared State Trap (+ Cheat Sheet)\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-nexttick-batching-dom-updates\">Vue nextTick Explained: How DOM Updates Are Batched\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive\">Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)\u003C\u002Fa>",{"id":390,"html":391,"type":83},"b77","\u003C!-- related:end -->",{"id":393,"type":394},"b78","divider",{"id":396,"html":397,"type":83},"b79","\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":399,"html":400,"type":83},"b80","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":402,"type":94,"items":403,"ordered":18},"b81",[404,405,406],"⭐ \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>","You call a `computed()` getter fifty times in one render and it only runs once. You mutate a `ref` it depends on and — nothing happens, not yet. Then you read `.value` and it recomputes. If that sequence feels slightly magical, it's because `computed()` isn't a function you call. It's a cached, lazily-evaluated effect with its own subscription list, and once you see the actual rule, none of it is magic anymore.\n\n## What You'll Learn\n\nBy the end of this article you'll be able to:\n\n- Explain exactly when a `computed()` getter runs and when it doesn't\n- Tell the difference between a dependency changing and a computed **recomputing**\n- Predict how many times a getter actually executes in a given sequence of writes and reads\n- Choose correctly between `computed()`, `watch()`, and `watchEffect()` for a given job\n- Spot the two most common ways developers accidentally break computed caching\n\n## Who This Is For\n\nYou've written a Vue 3 component with `\u003Cscript setup>` and used `ref`, `reactive`, and `computed` at least once. This article assumes you know *what* a computed property looks like in code; it's here to fix *how* you think it behaves internally, which the syntax alone doesn't teach you. If the read-time tracking that makes any of this possible is still fuzzy, [Vue Reactivity: ref vs reactive](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-reactivity-explained-ref-vs-reactive-cheat-sheet-4nij) builds that foundation — useful background, not required reading.\n\n## Table of Contents\n\n- [The Problem: Recomputing What You Already Computed](#the-problem-recomputing-what-you-already-computed)\n- [The Mental Model: A Lazy, Cached Effect](#the-mental-model-a-lazy-cached-effect)\n- [Building It Up: How computed() Actually Behaves](#building-it-up-how-computed-actually-behaves)\n- [Edge Cases and Gotchas](#edge-cases-and-gotchas)\n- [Best Practices](#best-practices)\n- [FAQ](#faq)\n- [Cheat Sheet](#cheat-sheet)\n- [Key Takeaways](#key-takeaways)\n\nThis is written against **Vue 3.5.x** (verified 3.5.43 on npm, September 2026). Vue 3.6 is currently a release candidate adding an alien-signals-based reactivity core and an opt-in \"Vapor Mode\" compiler — neither changes the caching contract described here, so this article holds for both.\n\n## The Problem: Recomputing What You Already Computed\n\nSay you're rendering a cart summary and you need a formatted total:\n\n```vue\n\u003Cscript setup>\nimport { ref } from 'vue'\n\nconst items = ref([\n  { name: 'Keyboard', price: 89.5, qty: 1 },\n  { name: 'Mouse', price: 24.99, qty: 2 },\n])\n\nfunction formattedTotal() {\n  console.log('recalculating total…')\n  const total = items.value.reduce((sum, i) => sum + i.price * i.qty, 0)\n  return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(total)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>Total: {{ formattedTotal() }}\u003C\u002Fp>\n  \u003Cp>Total (again): {{ formattedTotal() }}\u003C\u002Fp>\n  \u003Cp>Total (once more): {{ formattedTotal() }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n```\n\n`formattedTotal` is a plain method, so the template calls it every time it appears, and again on every re-render triggered by anything else in the component — a hover state, an unrelated ref, a parent re-rendering. The console fills with `recalculating total…` for work whose answer hasn't changed. It's correct, but it's wasteful, and the waste scales with how many places in the template read the value and how often the component re-renders for unrelated reasons.\n\nThe fix looks trivial — wrap it in `computed()` — but *why* that fixes it, and exactly what it changes, is the actual subject of this article.\n\n## The Mental Model: A Lazy, Cached Effect\n\n**The mental model:** a `computed()` ref is a special reactive effect that (1) tracks whatever reactive state its getter reads, exactly like a component render or a `watchEffect` does, (2) caches the return value instead of re-running immediately when a dependency changes, and (3) only re-runs the getter the next time something reads `.value` *after* it has been marked dirty.\n\nThat's two separate events, not one:\n\n- **Invalidation** happens the instant a tracked dependency is written to. It's synchronous and cheap: Vue marks the computed dirty (in 3.5, a computed nobody subscribes to isn't even notified — it compares dependency version counters on its next read instead). Nothing is recomputed yet.\n- **Recomputation** happens lazily, on the next `.value` read, and only if the dirty flag is set. If nobody ever reads it again, the getter never reruns — you paid for a flag flip, not a recalculation.\n\nThis is the same pull-based laziness that makes `computed` cheaper than a method call under repeated reads, and it's also exactly the behavior that surprises people the first time they log something inside a getter and see it fire at a moment they didn't expect.\n\n## Building It Up: How computed() Actually Behaves\n\n### Stage 1: The Cache Is Real — Prove It With a Counter\n\n```vue\n\u003Cscript setup>\nimport { ref, computed } from 'vue'\n\nconst a = ref(2)\nconst b = ref(3)\nlet getterRuns = 0\n\nconst sum = computed(() => {\n  getterRuns++\n  return a.value + b.value\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>{{ sum }} — {{ sum }} — {{ sum }}\u003C\u002Fp>\n  \u003Cp>Getter ran {{ getterRuns }} time(s)\u003C\u002Fp>\n\u003C\u002Ftemplate>\n```\n\nEven though the template reads `sum` three times in one render, `getterRuns` is `1`. **Key concept:** the first read evaluates and caches; every subsequent read before an invalidation returns the cached value without touching the getter at all.\n\n### Stage 2: Invalidation Is Synchronous, Recomputation Is Lazy\n\n```js\na.value = 10          \u002F\u002F invalidation: dirty flag set, getter has NOT run\nconsole.log(getterRuns) \u002F\u002F still 1\n\nconsole.log(sum.value)  \u002F\u002F recomputation happens here\nconsole.log(getterRuns) \u002F\u002F now 2\n```\n\n**Key concept:** writing to `a` doesn't run the getter — it only marks the cache stale. The getter runs on the *next read*, which might be immediately, might be several ticks later, or might never happen if nothing ever reads that computed again (say, the component that used it unmounted first).\n\nThis is also why mutating a dependency ten times in a row before anything reads the computed still only costs **one** recompute, not ten:\n\n```js\na.value++\na.value++\na.value++\nconsole.log(sum.value) \u002F\u002F getter runs exactly once here, using the final values\n```\n\n### Stage 3: Dependency Tracking Is Re-Done Every Run\n\nA computed's dependency list isn't fixed at creation — it's rebuilt on every evaluation, so branches matter:\n\n```js\nconst useB = ref(false)\nconst result = computed(() => (useB.value ? b.value : a.value))\n```\n\nIf `useB` is `false`, this run only reads `useB` and `a` — writing to `b` will not invalidate `result` at all, because `b` was never touched during the last evaluation. Flip `useB` to `true` and the *next* evaluation reads `b` instead, and from then on `b` is a real dependency and `a` is not. **Key concept:** the subscription list is a snapshot of what the getter actually read last time, not what it could theoretically read.\n\n### Stage 4: Writable Computed\n\nA computed can accept writes by passing `{ get, set }` instead of a bare function:\n\n```js\nconst first = ref('Ada')\nconst last = ref('Lovelace')\n\nconst fullName = computed({\n  get: () => `${first.value} ${last.value}`,\n  set(value) {\n    [first.value, last.value] = value.split(' ')\n  },\n})\n\nfullName.value = 'Grace Hopper' \u002F\u002F runs the setter, which writes first & last\n```\n\nThe caching rule for the `get` side is identical to the read-only form. The `set` side isn't cached at all — it's a plain function call every time you assign.\n\n### Stage 5: computed() vs watch() vs watchEffect()\n\nAll three are reactive effects that track dependencies the same way. What they're *for* is different:\n\n- **`computed()`** — derive a value from other reactive state, synchronously, with no side effects. Use it whenever the answer is \"a new piece of data computed from data I already have.\"\n- **`watchEffect()`** — run a side effect (fetch, log, DOM measurement) and automatically track whatever reactive state it reads. Runs immediately on creation, then again whenever a tracked dependency changes.\n- **`watch()`** — run a side effect in response to *specific* sources you name explicitly, with access to both the old and new value, and control over timing (`flush: 'post'`) and whether it fires immediately (`immediate: true`).\n\nIf you find yourself putting `fetch()`, `console.log()`, or a router push inside a `computed()` getter, that's the signal you wanted `watch` or `watchEffect` instead — a computed getter must stay pure, because Vue may call it, skip it, or defer it based on caching, and a getter with side effects makes those decisions unpredictable for you.\n\n## Edge Cases and Gotchas\n\n- **An impure getter breaks the contract.** Because Vue decides *when* (or whether) to rerun a computed getter, any side effect inside it runs an unpredictable number of times — including zero, if nothing ever reads the computed after invalidation.\n- **Async getters don't work.** `computed()` expects a synchronous return value; an async function returns a `Promise`, and Vue has no way to \"await\" a cache fill. For async derived state, use `watchEffect` (or `watch`) to populate a plain `ref` yourself, or reach for a dedicated async-state composable.\n- **Destructuring a `reactive()` object breaks tracking**, and that silently breaks any computed built from the destructured variable, because there's no longer a reactive property to subscribe to. Use `toRefs()` (or read through the object) before destructuring.\n- **Since Vue 3.4, a computed only propagates when its *value* actually changed**, not merely when a dependency changed. If `isEven` is `computed(() => count.value % 2 === 0)` and `count` goes from `2` to `4`, the getter reruns but returns the same `true`, so `isEven`'s own dependents don't rerun — this specifically helps chains of computed-on-computed avoid cascading work when a dependency changed but the derived result didn't. As of 3.4 you can also read the previous result as the getter's first argument: `computed((prev) => …)`.\n- **A computed with no active subscriber can still be read directly** (e.g. from a `\u003Cscript setup>` variable used outside the template) — it doesn't need a component render as its consumer, any effect reading `.value` keeps the caching rule intact.\n\n## Best Practices\n\n- **Reach for `computed()`** whenever the value is \"purely derived from state I already have\" and doesn't need to log, fetch, or otherwise reach outside itself.\n- **Reach for `watch()`** when you need the previous value, need to react to one specific source, or need to control flush timing around DOM updates.\n- **Reach for `watchEffect()`** for a side effect whose dependencies you're happy to let Vue infer automatically, and that should also run once immediately.\n- **Never mutate other reactive state from inside a computed getter.** It works today by accident and turns into an infinite-invalidation bug the moment the mutated state is also one of the computed's own dependencies.\n- **Keep computed getters cheap enough that recomputing occasionally is fine** — the cache saves you from *repeated* reads between invalidations, not from the cost of a single run.\n\n## FAQ\n\n### Does computed() rerun every time I read `.value`?\n\nNo. It reruns only on a read that happens *after* a tracked dependency has changed since the last evaluation. Reading the same, non-invalidated computed any number of times returns the cached value.\n\n### Why didn't my computed update?\n\nAlmost always one of two reasons: the property you changed was never read during the getter's last run (so it isn't a tracked dependency — see Stage 3), or you mutated a plain destructured variable instead of the reactive source itself. A third, rarer one: the getter reads something that isn't reactive at all — `computed(() => Date.now())` never updates.\n\n### Can a computed getter be async?\n\nNo — the getter must return a value synchronously. Model async derived state with a plain `ref` updated from inside `watchEffect` or `watch` instead.\n\n### Is computed() just memoization?\n\nIt's memoization plus an automatic, fine-grained invalidation signal — you don't hand it a dependency array like `useMemo`; it discovers its dependencies by watching what the getter reads.\n\n### Do multiple components reading the same computed cause multiple recomputes?\n\nNo. The cached value and the dirty flag live on the computed ref itself, shared by every reader — the getter still runs at most once per invalidation, regardless of how many places read `.value`.\n\n## Cheat Sheet\n\n| Situation | What happens |\n| --- | --- |\n| Read `computed.value` repeatedly, no writes between | Getter runs once; every later read returns the cache |\n| Write to a tracked dependency | Marks the computed dirty **synchronously**; getter does *not* run yet |\n| Read `.value` after a dirty mark | Getter runs now, result re-cached |\n| Write to a dependency multiple times before any read | Still exactly one recompute, on the next read |\n| Write to a property the getter didn't read last run | No invalidation — it isn't a tracked dependency |\n| Getter branches on a condition | Only the branch actually taken this run is tracked |\n| Need previous value | `computed((prev) => …)` or `computed({ get(prev) {…}, set })` (Vue 3.4+) |\n| Need to write to it | `computed({ get, set })` |\n| Need a side effect, not a derived value | `watch()` or `watchEffect()`, not `computed()` |\n\n```js\nimport { ref, computed } from 'vue'\n\nconst price = ref(20)\nconst qty = ref(3)\n\n\u002F\u002F Derived, pure, cached — recomputes only when price or qty change\n\u002F\u002F and something actually reads .value afterward.\nconst total = computed(() => price.value * qty.value)\n\n\u002F\u002F Writable computed\nconst discountedTotal = computed({\n  get: () => total.value * 0.9,\n  set(value) {\n    price.value = value \u002F 0.9 \u002F qty.value\n  },\n})\n```\n\n## Key Takeaways\n\n- `computed()` caches its result and reevaluates lazily — invalidation (a dependency write) and recomputation (the next read) are two separate moments, not one.\n- Its dependency list is rebuilt every run, so conditional branches inside the getter can add or drop dependencies between evaluations.\n- Since Vue 3.4, a computed only propagates to *its* dependents when the recomputed value actually differs from the last one.\n- Keep computed getters pure and synchronous; reach for `watch()` or `watchEffect()` the moment you need a side effect or async work.\n\n## Wrapping Up\n\nThat console full of `recalculating total…` from the opening example disappears the moment `formattedTotal()` becomes a `computed()` — not because Vue got smarter about *when* to call it, but because you stopped calling it directly at all. You're reading a cache that knows, dependency by dependency, exactly when it's stale. Once you can picture that dirty flag and the lazy pull that clears it, computed properties stop being a syntax you memorized and become a piece of Vue's design you can reason about.\n\nIf you've been burned by a computed that \"wouldn't update,\" what turned out to be the actual dependency it was missing? Drop it in the comments — it's almost always Stage 3 in disguise.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-computed-caching-invalidation\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-computed-caching-invalidation\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\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [Vue Composables: The Shared State Trap (+ Cheat Sheet)](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-composables-shared-state-trap)\n- [Vue nextTick Explained: How DOM Updates Are Batched](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-nexttick-batching-dom-updates)\n- [Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive)\n\n\u003C!-- related:end -->\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":409,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-computed-caching-invalidation","01a08af7-9d70-757c-a0a5-b3d950fdf5f7",{"name":412,"part":413,"total":413,"items":414},"Vue Deep Dive",4,[415,420,424,429],{"slug":416,"title":417,"publishedAt":418,"readingMinutes":419},"vue-weekly-reactivity-ref-vs-reactive","Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)","2026-08-24T13:57:38.080Z",14,{"slug":421,"title":422,"publishedAt":423,"readingMinutes":51},"vue-weekly-nexttick-batching-dom-updates","Vue nextTick Explained: How DOM Updates Are Batched","2026-09-14T12:19:00.606Z",{"slug":425,"title":426,"publishedAt":427,"readingMinutes":428},"vue-weekly-composables-shared-state-trap","Vue Composables: The Shared State Trap (+ Cheat Sheet)","2026-09-22T19:37:11.460Z",13,{"slug":46,"title":47,"publishedAt":52,"readingMinutes":51},{"id":431,"locked":18},"01a08af7-9dcb-7314-bca8-803a27757da0",[433],{"id":434,"slug":46,"title":435,"_count":436},"01a08af7-9e26-778b-b5d7-af0317eabe59","Vue computed(): Caching & Invalidation",{"questions":437},8,[439],{"locale":13,"slug":46},{"id":434,"slug":46,"title":435,"_count":441,"questionCount":437},{"questions":437},[443,445,447,451,453,457,461,464,467,471,475,478],{"slug":72,"name":73,"articles":444},117,{"slug":66,"name":67,"articles":446},98,{"slug":448,"name":449,"articles":450},"frontend","Frontend",76,{"slug":69,"name":70,"articles":452},43,{"slug":454,"name":455,"articles":456},"css","Css",37,{"slug":458,"name":459,"articles":460},"typescript","Typescript",17,{"slug":462,"name":463,"articles":419},"performance","Performance",{"slug":465,"name":466,"articles":428},"react","React",{"slug":468,"name":469,"articles":470},"browser","Browser",11,{"slug":472,"name":473,"articles":474},"node","Node",10,{"slug":476,"name":477,"articles":437},"html","Html",{"slug":479,"name":480,"articles":481},"accessibility","Accessibility",7,{"items":483,"meta":541},[484,496,511,526],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":485,"likeCount":19,"commentCount":19,"author":486,"vertical":487,"topic":488,"tags":489,"_count":494,"playground":495,"hasQuiz":17,"hasPlayground":17},40,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[490,491,492,493],{"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},{"id":497,"slug":425,"title":426,"subtitle":48,"excerpt":498,"coverUrl":499,"locale":13,"readingMinutes":428,"publishedAt":427,"viewCount":500,"likeCount":19,"commentCount":19,"author":501,"vertical":502,"topic":503,"tags":504,"_count":509,"playground":510,"hasQuiz":17,"hasPlayground":17},"01a066ea-3098-75c9-b7e5-1d5426f773eb","Why two Vue components using the same composable can end up sharing state by accident, the mental model that explains it, and how to fix or embrace it.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-composables-shared-state-trap.png",307,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[505,506,507,508],{"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":425},{"id":512,"slug":421,"title":422,"subtitle":48,"excerpt":513,"coverUrl":514,"locale":13,"readingMinutes":51,"publishedAt":423,"viewCount":515,"likeCount":19,"commentCount":19,"author":516,"vertical":517,"topic":518,"tags":519,"_count":524,"playground":525,"hasQuiz":17,"hasPlayground":17},"01a04446-5a00-7624-b775-c73c6c035e67","How Vue batches reactive writes into one DOM update, why the DOM looks stale right after a change, and how nextTick and watch's flush option fix it.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-nexttick-batching-dom-updates.png",372,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[520,521,522,523],{"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":421},{"id":527,"slug":416,"title":417,"subtitle":48,"excerpt":528,"coverUrl":529,"locale":13,"readingMinutes":419,"publishedAt":418,"viewCount":530,"likeCount":19,"commentCount":75,"author":531,"vertical":532,"topic":533,"tags":534,"_count":539,"playground":540,"hasQuiz":17,"hasPlayground":17},"01a03410-2db8-735b-88b2-5285459dc915","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",703,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[535,536,537,538],{"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":416},{"page":75,"perPage":481,"total":413,"totalPages":75},"\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>\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-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> 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>\n  \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> name\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Keyboard'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> price\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">89.5\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> qty\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">1\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> name\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Mouse'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> price\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">24.99\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> qty\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">2\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">formattedTotal\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\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>\u003Cspan class=\"shj-str\">'recalculating total…'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> total \u003Cspan class=\"shj-oper\">=\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>sum\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> i\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> sum \u003Cspan class=\"shj-oper\">+\u003C\u002Fspan> i\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>price \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> i\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>qty\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Intl\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-class\">NumberFormat\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'en-US'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> style\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'currency'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> currency\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'USD'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">format\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>total\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\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\">p\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Total: \u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> \u003Cspan class=\"shj-func\">formattedTotal\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">p\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\">p\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Total (again): \u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> \u003Cspan class=\"shj-func\">formattedTotal\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">p\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\">p\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Total (once more): \u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> \u003Cspan class=\"shj-func\">formattedTotal\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">p\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-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>\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> computed \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> a \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">2\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> b \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">3\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> getterRuns \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> sum \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> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  getterRuns\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> a\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">+\u003C\u002Fspan> b\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\n\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\">p\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> sum \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan> — \u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> sum \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan> — \u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> sum \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan>\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">p\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\">p\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Getter ran \u003Cspan class=\"shj-oper\">{{\u003C\u002Fspan> getterRuns \u003Cspan class=\"shj-oper\">}}\u003C\u002Fspan> time(s)\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">p\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>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">a\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">10\u003C\u002Fspan>          \u003Cspan class=\"shj-cmnt\">\u002F\u002F invalidation: dirty flag set, getter has NOT run\n\u003C\u002Fspan>console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>getterRuns\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F still 1\n\u003C\u002Fspan>\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>sum\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>  \u003Cspan class=\"shj-cmnt\">\u002F\u002F recomputation happens here\n\u003C\u002Fspan>console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>getterRuns\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F now 2\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\">a\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\na\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\na\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>sum\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F getter runs exactly once here, using the final values\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> useB \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bool\">false\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> result \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> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>useB\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">?\u003C\u002Fspan> b\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> a\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-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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> first \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'Ada'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> last \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'Lovelace'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> fullName \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>\n  get\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-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\"> \u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>last\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> last\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> value\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">split\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">' '\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\nfullName\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Grace Hopper'\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F runs the setter, which writes first &#38; last\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>\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>\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> computed \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> price \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">20\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> qty \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">3\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Derived, pure, cached — recomputes only when price or qty change\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F and something actually reads .value afterward.\n\u003C\u002Fspan>\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> price\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> qty\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Writable computed\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> discountedTotal \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>\n  get\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> total\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0.9\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    price\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> value \u003Cspan class=\"shj-oper\">\u002F 0.9 \u002F\u003C\u002Fspan> qty\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":550},[]]