[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-vue-weekly-watch-vs-watcheffect":44,"search-suggestions":479,"related-vue-weekly-watch-vs-watcheffect":518,"code:vue:true:k50aqj":609,"code:ts:true:5l7wmx":610,"code:ts:true:hvi7ek":611,"code:ts:true:m2hbqe":612,"code:ts:true:13p6k81":613,"code:ts:true:iw57p0":614,"code:ts:true:112fqcw":615,"code:vue:true:j17fs2":616,"code:ts:true:1ts1zfw":617,"code:ts:true:1wsspd6":618,"code:ts:true:1k2d215":619,"comments-01a0ec73-2e41-711f-9f8a-b6c9bae1e054":620},[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":435,"seo":436,"translationGroupId":438,"series":439,"podcastUrl":48,"verticalId":5,"thread":467,"assessments":469,"translations":475,"quiz":477},"01a0ec73-2e41-711f-9f8a-b6c9bae1e054","vue-weekly-watch-vs-watcheffect","Vue watch vs watchEffect: The Complete Guide",null,"Vue's watch() and watchEffect() look interchangeable but track dependencies, run timing, and cleanup differently. The exact rules, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-watch-vs-watcheffect.png",15,"2026-10-12T06:10:54.504Z",34,{"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 watch vs watchEffect — race condition playground",{"blocks":79,"version":75},[80,84,87,92,95,104,107,110,113,116,133,136,139,144,147,150,153,156,161,164,167,172,175,178,182,185,188,191,194,198,201,205,208,211,214,218,221,224,227,246,249,253,256,259,262,266,269,272,276,279,282,285,289,292,295,303,306,314,317,320,323,327,330,334,337,340,343,346,349,352,355,358,362,365,372,375,378,381,384,387,390,393,396,399,402,405,408,411,417,420,423,426,429],{"id":81,"html":82,"type":83},"b1","\u003Cp>You type &quot;vue&quot; into a search box, one letter at a time. Four requests go out — for \u003Ccode>v\u003C\u002Fcode>, \u003Ccode>vu\u003C\u002Fcode>, \u003Ccode>vue\u003C\u002Fcode>, and the deleted-and-retyped \u003Ccode>vue\u003C\u002Fcode> again — and the one for \u003Ccode>v\u003C\u002Fcode> happens to come back last. The screen ends up showing results for a single letter, on top of a \u003Ccode>watch()\u003C\u002Fcode> callback that looks completely correct. Nothing in that callback is wrong. The watcher just never learned that a newer run had already made it obsolete.\u003C\u002Fp>","paragraph",{"id":85,"html":86,"type":83},"b2","\u003Cp>This is the article that fixes that, and along the way answers the question every Vue developer eventually asks: \u003Cstrong>when do I reach for \u003Ccode>watch()\u003C\u002Fcode>, when do I reach for \u003Ccode>watchEffect()\u003C\u002Fcode>, and why does my callback keep firing at the wrong moment?\u003C\u002Fstrong>\u003C\u002Fp>",{"id":88,"html":89,"text":90,"type":91,"level":31},"b3","What you&#39;ll learn","What you'll learn","heading",{"id":93,"html":94,"type":83},"b4","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":96,"type":97,"items":98,"ordered":18},"b5","list",[99,100,101,102,103],"Explain the real difference between \u003Ccode>watch()\u003C\u002Fcode> and \u003Ccode>watchEffect()\u003C\u002Fcode> — not &quot;one is explicit, one is automatic,&quot; but \u003Cem>what that difference costs you\u003C\u002Fem>","Pick the correct \u003Ccode>flush\u003C\u002Fcode> timing (\u003Ccode>pre\u003C\u002Fcode>, \u003Ccode>post\u003C\u002Fcode>, \u003Ccode>sync\u003C\u002Fcode>) for a watcher that needs to read the DOM","Use \u003Ccode>onWatcherCleanup\u003C\u002Fcode> to cancel a stale async response before it overwrites a newer one","Stop a watcher manually, and know when Vue stops it for you","Recognize the specific ways both APIs quietly track (or fail to track) the wrong things",{"id":105,"html":106,"text":106,"type":91,"level":31},"b6","Prerequisites",{"id":108,"html":109,"type":83},"b7","\u003Cp>You should already be comfortable with \u003Ccode>ref()\u003C\u002Fcode>, \u003Ccode>reactive()\u003C\u002Fcode>, and \u003Ccode>computed()\u003C\u002Fcode> in the Composition API. If &quot;why does destructuring a \u003Ccode>reactive()\u003C\u002Fcode> object lose reactivity&quot; isn&#39;t yet second nature, read \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-reactivity-explained-ref-vs-reactive-cheat-sheet-4nij\">Vue Reactivity Explained: ref vs reactive\u003C\u002Fa> first — this article assumes that mental model and builds on it.\u003C\u002Fp>",{"id":111,"html":112,"type":83},"b8","\u003Cp>This is written against \u003Cstrong>Vue 3.5.x\u003C\u002Fstrong> (verified September 2026). Version-specific notes (the \u003Ccode>once\u003C\u002Fcode> option, \u003Ccode>onWatcherCleanup\u003C\u002Fcode>) are called out where they apply.\u003C\u002Fp>",{"id":114,"html":115,"text":115,"type":91,"level":31},"b9","Table of contents",{"id":117,"type":97,"items":118,"ordered":18},"b10",[119,120,121,122,123,124,125,126,127,128,129,130,131,132],"\u003Ca href=\"#the-problem-a-watcher-with-no-memory\">The problem: a watcher with no memory\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-pull-vs-push-tracking\">The mental model: pull vs. push tracking\u003C\u002Fa>","\u003Ca href=\"#stage-1-the-smallest-watch\">Stage 1: the smallest watch()\u003C\u002Fa>","\u003Ca href=\"#stage-2-the-same-job-with-watcheffect\">Stage 2: the same job with watchEffect()\u003C\u002Fa>","\u003Ca href=\"#stage-3-multiple-sources-and-watching-inside-an-object\">Stage 3: multiple sources, and watching inside an object\u003C\u002Fa>","\u003Ca href=\"#stage-4-immediate-deep-and-once\">Stage 4: immediate, deep, and once\u003C\u002Fa>","\u003Ca href=\"#stage-5-flush-timing--pre-post-sync\">Stage 5: flush timing — pre, post, sync\u003C\u002Fa>","\u003Ca href=\"#stage-6-cleanup-and-the-race-condition-fixed\">Stage 6: cleanup and the race condition, fixed\u003C\u002Fa>","\u003Ca href=\"#stopping-a-watcher\">Stopping a watcher\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":134,"html":135,"text":135,"type":91,"level":31},"b11","The problem: a watcher with no memory",{"id":137,"html":138,"type":83},"b12","\u003Cp>Here&#39;s the search box, written the way it&#39;s tempting to write it the first time:\u003C\u002Fp>",{"id":140,"code":141,"type":142,"language":61,"highlight":143},"b13","\u003Cscript setup>\nimport { ref, watch } from \"vue\";\n\nconst query = ref(\"\");\nconst results = ref([]);\n\nwatch(query, async (q) => {\n  if (!q) {\n    results.value = [];\n    return;\n  }\n  const res = await fetch(`\u002Fapi\u002Fsearch?q=${encodeURIComponent(q)}`);\n  results.value = await res.json();\n});\n\u003C\u002Fscript>","code",[],{"id":145,"html":146,"type":83},"b14","\u003Cp>Every keystroke fires a new request, and requests do not resolve in the order they were sent. If the request for \u003Ccode>v\u003C\u002Fcode> is slow and the request for \u003Ccode>vue\u003C\u002Fcode> is fast, \u003Ccode>vue\u003C\u002Fcode>&#39;s results land first and \u003Ccode>v\u003C\u002Fcode>&#39;s results land \u003Cem>after\u003C\u002Fem>, silently overwriting them. The callback did exactly what it was written to do; it just never asked &quot;am I still the most recent run?&quot;\u003C\u002Fp>",{"id":148,"html":149,"type":83},"b15","\u003Cp>That question is exactly what a watcher&#39;s cleanup mechanism answers. Getting there first requires being precise about what \u003Ccode>watch()\u003C\u002Fcode> and \u003Ccode>watchEffect()\u003C\u002Fcode> actually are.\u003C\u002Fp>",{"id":151,"html":152,"text":152,"type":91,"level":31},"b16","The mental model: pull vs. push tracking",{"id":154,"html":155,"type":83},"b17","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> \u003Ccode>watch()\u003C\u002Fcode> is \u003Cem>told\u003C\u002Fem> what to watch. \u003Ccode>watchEffect()\u003C\u002Fcode> \u003Cem>discovers\u003C\u002Fem> what to watch by running once and recording every reactive value it reads. Everything else — old\u002Fnew values, laziness, immediacy — falls out of that one distinction.\u003C\u002Fp>",{"id":157,"type":97,"items":158,"ordered":18},"b18",[159,160],"\u003Cstrong>\u003Ccode>watch(source, callback)\u003C\u002Fcode>\u003C\u002Fstrong> takes an explicit source (a ref, a getter, a \u003Ccode>reactive()\u003C\u002Fcode> object, or an array of them) and a callback. It does \u003Cstrong>not\u003C\u002Fstrong> run on creation by default. It runs only when the \u003Cem>named\u003C\u002Fem> source changes, and it hands the callback both the new and the old value.","\u003Cstrong>\u003Ccode>watchEffect(fn)\u003C\u002Fcode>\u003C\u002Fstrong> takes a single function with no separate source. It runs \u003Cstrong>immediately\u003C\u002Fstrong>, once, synchronously (well — see flush timing below) to figure out what it read, and re-runs itself in full whenever any of those dependencies change. There is no &quot;old value&quot; concept, because there&#39;s no fixed source to diff against — the whole function reruns.",{"id":162,"html":163,"type":83},"b19","\u003Cp>Put differently: \u003Ccode>watch\u003C\u002Fcode> is a subscription to a specific address. \u003Ccode>watchEffect\u003C\u002Fcode> is a subscription to \u003Cem>whatever addresses this function happens to visit today\u003C\u002Fem> — and it can visit different addresses on different runs, exactly like a \u003Ccode>computed()\u003C\u002Fcode> getter does. The dependency-tracking mechanism (a run records every reactive value it reads) is shared across \u003Ccode>computed\u003C\u002Fcode>, \u003Ccode>watch\u003C\u002Fcode>&#39;s getter-form sources, and \u003Ccode>watchEffect\u003C\u002Fcode> alike.\u003C\u002Fp>",{"id":165,"html":166,"text":166,"type":91,"level":31},"b20","Stage 1: the smallest watch()",{"id":168,"code":169,"type":142,"language":170,"highlight":171},"b21","import { ref, watch } from \"vue\";\n\nconst count = ref(0);\n\nwatch(count, (newVal, oldVal) => {\n  console.log(`count went from ${oldVal} to ${newVal}`);\n});\n\ncount.value++; \u002F\u002F logs: \"count went from 0 to 1\"","ts",[],{"id":173,"html":174,"type":83},"b22","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> the callback did not run when \u003Ccode>watch\u003C\u002Fcode> was declared — only after \u003Ccode>count\u003C\u002Fcode> changed. That&#39;s the &quot;lazy by default&quot; behavior, and it&#39;s the first thing that trips people up coming from \u003Ccode>watchEffect\u003C\u002Fcode>.\u003C\u002Fp>",{"id":176,"html":177,"text":177,"type":91,"level":31},"b23","Stage 2: the same job with watchEffect()",{"id":179,"code":180,"type":142,"language":170,"highlight":181},"b24","import { ref, watchEffect } from \"vue\";\n\nconst count = ref(0);\n\nwatchEffect(() => {\n  console.log(`count is now ${count.value}`);\n});\n\u002F\u002F logs immediately: \"count is now 0\"\n\ncount.value++; \u002F\u002F logs: \"count is now 1\"",[],{"id":183,"html":184,"type":83},"b25","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>watchEffect\u003C\u002Fcode> ran once immediately, on creation, before anything changed — because it has to run once just to discover that \u003Ccode>count\u003C\u002Fcode> is a dependency. \u003Ccode>watch\u003C\u002Fcode> never needed to run to know its source; you named it.\u003C\u002Fp>",{"id":186,"html":187,"type":83},"b26","\u003Cp>If the job is &quot;sync this state to something external, right now and on every future change&quot; — a document title, a \u003Ccode>localStorage\u003C\u002Fcode> key, an analytics call — \u003Ccode>watchEffect\u003C\u002Fcode> is usually the shorter answer. Need the \u003Cem>old\u003C\u002Fem> value, or need to stay silent until one specific thing changes? Reach for \u003Ccode>watch\u003C\u002Fcode>.\u003C\u002Fp>",{"id":189,"html":190,"text":190,"type":91,"level":31},"b27","Stage 3: multiple sources, and watching inside an object",{"id":192,"html":193,"type":83},"b28","\u003Cp>\u003Ccode>watch\u003C\u002Fcode> accepts an array of sources, and hands the callback matching arrays of new\u002Fold values:\u003C\u002Fp>",{"id":195,"code":196,"type":142,"language":170,"highlight":197},"b29","watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {\n  console.log(`${oldFirst} ${oldLast} -> ${newFirst} ${newLast}`);\n});",[],{"id":199,"html":200,"type":83},"b30","\u003Cp>Watching a single property of a \u003Ccode>reactive()\u003C\u002Fcode> object requires a \u003Cstrong>getter function\u003C\u002Fstrong>, not the bare property — because \u003Ccode>reactive().someProp\u003C\u002Fcode> is just a plain value the instant you read it out of the object, with no reference back to the proxy:\u003C\u002Fp>",{"id":202,"code":203,"type":142,"language":170,"highlight":204},"b31","const state = reactive({ count: 0, name: \"vue\" });\n\n\u002F\u002F Wrong: this is a snapshot number, not a reactive source.\n\u002F\u002F watch(state.count, cb) never fires.\n\n\u002F\u002F Correct: a getter re-reads the proxy every time Vue checks it.\nwatch(\n  () => state.count,\n  (newCount, oldCount) => console.log(newCount, oldCount)\n);",[],{"id":206,"html":207,"type":83},"b32","\u003Cp>This is the same &quot;reactivity is a subscription made at read time&quot; rule from the \u003Ccode>ref\u003C\u002Fcode> vs \u003Ccode>reactive\u003C\u002Fcode> article — \u003Ccode>watch\u003C\u002Fcode> needs something it can re-read, and a getter is how you hand it that.\u003C\u002Fp>",{"id":209,"html":210,"text":210,"type":91,"level":31},"b33","Stage 4: immediate, deep, and once",{"id":212,"html":213,"type":83},"b34","\u003Cp>Three options on \u003Ccode>watch\u003C\u002Fcode> change when and how often it fires (\u003Ccode>watchEffect\u003C\u002Fcode> accepts only \u003Ccode>flush\u003C\u002Fcode>, covered next, plus the dev-only \u003Ccode>onTrack\u003C\u002Fcode>\u002F\u003Ccode>onTrigger\u003C\u002Fcode> — it runs on creation and tracks everything it reads, so \u003Ccode>immediate\u003C\u002Fcode>, \u003Ccode>deep\u003C\u002Fcode> and \u003Ccode>once\u003C\u002Fcode> don&#39;t apply):\u003C\u002Fp>",{"id":215,"code":216,"type":142,"language":170,"highlight":217},"b35","watch(source, callback, {\n  immediate: true, \u002F\u002F also run once, right away, with oldValue undefined\n  deep: true,      \u002F\u002F walk the whole object\u002Farray, not just the top-level reference\n  once: true,      \u002F\u002F (Vue 3.4+) auto-stop after the callback fires the first time\n});",[],{"id":219,"html":220,"type":83},"b36","\u003Cp>\u003Ccode>deep: true\u003C\u002Fcode> is the one to use sparingly. Watching a getter that returns an object (\u003Ccode>() =&gt; state.someObject\u003C\u002Fcode>) or a \u003Ccode>ref\u003C\u002Fcode> holding one only fires when the whole object is replaced (a \u003Ccode>reactive()\u003C\u002Fcode> object passed directly is already implicitly deep); \u003Ccode>deep\u003C\u002Fcode> walks every nested property so a change three levels down also fires — at the cost of Vue actually traversing the structure to set that tracking up. On a large or deeply nested object, that traversal is real work.\u003C\u002Fp>",{"id":222,"html":223,"text":223,"type":91,"level":31},"b37","Stage 5: flush timing — pre, post, sync",{"id":225,"html":226,"type":83},"b38","\u003Cp>Both \u003Ccode>watch\u003C\u002Fcode> and \u003Ccode>watchEffect\u003C\u002Fcode> accept \u003Ccode>flush\u003C\u002Fcode>, and it answers a different question than the options above: \u003Cem>relative to a component&#39;s own re-render, when does this callback run?\u003C\u002Fem>\u003C\u002Fp>",{"id":228,"head":229,"rows":233,"type":245},"b39",[230,231,232],"\u003Ccode>flush\u003C\u002Fcode> value","When it runs","Default?",[234,238,242],[235,236,237],"\u003Ccode>&#39;pre&#39;\u003C\u002Fcode>","Before the component re-renders","Yes, for both APIs",[239,240,241],"\u003Ccode>&#39;post&#39;\u003C\u002Fcode>","After the component has re-rendered and the DOM is updated","No",[243,244,241],"\u003Ccode>&#39;sync&#39;\u003C\u002Fcode>","Synchronously, the instant the dependency is written","table",{"id":247,"html":248,"type":83},"b40","\u003Cp>This connects directly to the batching behavior from \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-nexttick-explained-how-dom-updates-are-batched-3gnp\">Vue nextTick Explained\u003C\u002Fa>: a \u003Ccode>pre\u003C\u002Fcode>-flush watcher (the default) can run \u003Cem>before\u003C\u002Fem> the DOM reflects the change that triggered it. If your callback needs to measure or read an element that just changed size or content, \u003Ccode>flush: &#39;pre&#39;\u003C\u002Fcode> will read stale layout:\u003C\u002Fp>",{"id":250,"code":251,"type":142,"language":170,"highlight":252},"b41","watch(\n  items,\n  () => {\n    \u002F\u002F With the default 'pre' flush, the DOM here may not yet\n    \u002F\u002F reflect the new `items` — this measurement can be one step behind.\n    console.log(listRef.value.scrollHeight);\n  },\n  { flush: \"post\" } \u002F\u002F wait for the DOM update first\n);",[],{"id":254,"html":255,"type":83},"b42","\u003Cp>\u003Ccode>flush: &#39;sync&#39;\u003C\u002Fcode> skips Vue&#39;s batching queue entirely and runs the instant the dependency is written — useful for invalidating an external cache the moment something changes, but dangerous on sources mutated many times synchronously (e.g. an array filled in a loop), since it fires on every single mutation with no batching.\u003C\u002Fp>",{"id":257,"html":258,"text":258,"type":91,"level":31},"b43","Stage 6: cleanup and the race condition, fixed",{"id":260,"html":261,"type":83},"b44","\u003Cp>Back to the search box. The fix is to give each run a way to say &quot;abort me if a newer run has already started&quot; — and both \u003Ccode>watch\u003C\u002Fcode> and \u003Ccode>watchEffect\u003C\u002Fcode> give you exactly that, as \u003Ccode>onWatcherCleanup()\u003C\u002Fcode> (3.5+), which must be called \u003Cstrong>synchronously\u003C\u002Fstrong> during the callback (before any \u003Ccode>await\u003C\u002Fcode>):\u003C\u002Fp>",{"id":263,"code":264,"type":142,"language":61,"highlight":265},"b45","\u003Cscript setup>\nimport { ref, watch, onWatcherCleanup } from \"vue\"; \u002F\u002F onWatcherCleanup: Vue 3.5+\n\nconst query = ref(\"\");\nconst results = ref([]);\n\nwatch(query, async (q) => {\n  if (!q) {\n    results.value = [];\n    return;\n  }\n\n  const controller = new AbortController();\n  onWatcherCleanup(() => controller.abort());\n\n  try {\n    const res = await fetch(`\u002Fapi\u002Fsearch?q=${encodeURIComponent(q)}`, {\n      signal: controller.signal,\n    });\n    results.value = await res.json();\n  } catch (e) {\n    if (e.name !== \"AbortError\") throw e; \u002F\u002F an aborted run is expected, not an error\n  }\n});\n\u003C\u002Fscript>",[],{"id":267,"html":268,"type":83},"b46","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>onWatcherCleanup\u003C\u002Fcode> registers a function that Vue calls \u003Cem>right before the next run of this same watcher\u003C\u002Fem> — and also when the component unmounts. Type \u003Ccode>vue\u003C\u002Fcode>, and the moment the \u003Ccode>e\u003C\u002Fcode> keystroke starts a new run, the previous run&#39;s cleanup fires first, aborting its in-flight fetch. An aborted request rejects with an \u003Ccode>AbortError\u003C\u002Fcode> instead of resolving, so it can never reach \u003Ccode>results.value\u003C\u002Fcode> — and the \u003Ccode>try\u003C\u002Fcode>\u002F\u003Ccode>catch\u003C\u002Fcode> swallows just that expected rejection, which would otherwise surface through Vue&#39;s error handler on every superseded keystroke. Order of arrival stops mattering, because only the current, un-aborted request is allowed to write.\u003C\u002Fp>",{"id":270,"html":271,"type":83},"b47","\u003Cp>The same mechanism is also passed as the callback&#39;s third parameter (the only option before 3.5, and still supported):\u003C\u002Fp>",{"id":273,"code":274,"type":142,"language":170,"highlight":275},"b48","watch(query, async (q, oldQ, onCleanup) => {\n  const controller = new AbortController();\n  onCleanup(() => controller.abort());\n  \u002F\u002F ...\n});",[],{"id":277,"html":278,"type":83},"b49","\u003Cp>The two forms differ here: \u003Ccode>onWatcherCleanup\u003C\u002Fcode> must be called before the first \u003Ccode>await\u003C\u002Fcode> — after it, Vue can&#39;t tell which watcher run it belongs to, so dev mode warns and the cleanup is silently dropped. The \u003Ccode>onCleanup\u003C\u002Fcode> argument is bound to its watcher, so it isn&#39;t subject to that constraint.\u003C\u002Fp>",{"id":280,"html":281,"text":281,"type":91,"level":31},"b50","Stopping a watcher",{"id":283,"html":284,"type":83},"b51","\u003Cp>Both \u003Ccode>watch\u003C\u002Fcode> and \u003Ccode>watchEffect\u003C\u002Fcode> return a handle — call it (or \u003Ccode>.stop()\u003C\u002Fcode>) to stop; since 3.5 it also has \u003Ccode>.pause()\u003C\u002Fcode> \u002F \u003Ccode>.resume()\u003C\u002Fcode>:\u003C\u002Fp>",{"id":286,"code":287,"type":142,"language":170,"highlight":288},"b52","const stop = watchEffect(() => {\n  console.log(count.value);\n});\n\nstop(); \u002F\u002F this effect will never run again",[],{"id":290,"html":291,"type":83},"b53","\u003Cp>You rarely need to call this yourself inside a component: a watcher created \u003Cstrong>synchronously\u003C\u002Fstrong> during \u003Ccode>setup()\u003C\u002Fcode> or \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode> is automatically stopped when the component unmounts; one created later from an async callback (a \u003Ccode>setTimeout\u003C\u002Fcode>, a \u003Ccode>.then\u003C\u002Fcode>) inside that same \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode> is not. You need the manual \u003Ccode>stop()\u003C\u002Fcode> mostly for watchers created outside a component&#39;s setup — inside a composable that runs after mount, in a store, or in plain JS — where Vue has no unmount to tie it to.\u003C\u002Fp>",{"id":293,"html":294,"text":294,"type":91,"level":31},"b54","Edge cases and gotchas",{"id":296,"type":97,"items":297,"ordered":18},"b55",[298,299,300,301,302],"\u003Cstrong>\u003Ccode>await\u003C\u002Fcode> breaks dependency discovery.\u003C\u002Fstrong> \u003Ccode>watchEffect\u003C\u002Fcode> only tracks reads that happen synchronously before the first \u003Ccode>await\u003C\u002Fcode>. Anything read after an \u003Ccode>await\u003C\u002Fcode> inside the function is invisible to it and will not trigger a re-run if it changes.","\u003Cstrong>A getter that reads nothing reactive never fires.\u003C\u002Fstrong> \u003Ccode>watch(() =&gt; someObj.plainProperty, cb)\u003C\u002Fcode> looks correct but never runs if \u003Ccode>plainProperty\u003C\u002Fcode> isn&#39;t itself reactive — there&#39;s nothing for Vue to subscribe to.","\u003Cstrong>\u003Ccode>watchEffect\u003C\u002Fcode> can over-track.\u003C\u002Fstrong> Because it tracks everything the function body reads, a large conditional inside it can pick up dependencies you didn&#39;t intend, causing extra re-runs. \u003Ccode>watch\u003C\u002Fcode> with an explicit source avoids this by construction.","\u003Cstrong>\u003Ccode>.push\u003C\u002Fcode>\u002F\u003Ccode>.splice\u003C\u002Fcode> fire a non-deep watcher only when the source is the \u003Ccode>reactive()\u003C\u002Fcode> array itself\u003C\u002Fstrong> (implicitly deep). \u003Ccode>watch(itemsRef, cb)\u003C\u002Fcode> on a \u003Ccode>ref([])\u003C\u002Fcode>, or a getter like \u003Ccode>() =&gt; state.items\u003C\u002Fcode>, tracks only the array reference — in-place mutations need \u003Ccode>deep: true\u003C\u002Fcode>, or watch \u003Ccode>() =&gt; items.value.length\u003C\u002Fcode>.","\u003Cstrong>\u003Ccode>immediate: true\u003C\u002Fcode> hands \u003Ccode>undefined\u003C\u002Fcode> as \u003Ccode>oldValue\u003C\u002Fcode>\u003C\u002Fstrong> on that first synthetic run (an empty array \u003Ccode>[]\u003C\u002Fcode> when watching an array of sources) — always guard against it if the callback compares old and new.",{"id":304,"html":305,"text":305,"type":91,"level":31},"b56","Best practices",{"id":307,"type":97,"items":308,"ordered":18},"b57",[309,310,311,312,313],"\u003Cstrong>Reach for \u003Ccode>watchEffect\u003C\u002Fcode>\u003C\u002Fstrong> when the job is &quot;keep this side effect in sync with whatever it reads&quot; and you don&#39;t need the old value — document titles, \u003Ccode>localStorage\u003C\u002Fcode>, imperative DOM syncing.","\u003Cstrong>Reach for \u003Ccode>watch\u003C\u002Fcode>\u003C\u002Fstrong> when you need the previous value, need the callback to stay silent until one \u003Cem>specific\u003C\u002Fem> thing changes (not everything the function happens to read), or want lazy behavior by default.","\u003Cstrong>Default to \u003Ccode>flush: &#39;pre&#39;\u003C\u002Fcode>\u003C\u002Fstrong> (the default) unless the callback reads the DOM — then use \u003Ccode>&#39;post&#39;\u003C\u002Fcode>. Reserve \u003Ccode>&#39;sync&#39;\u003C\u002Fcode> for cache-invalidation-style work that must happen before Vue&#39;s own batching, and never on a source that&#39;s mutated many times in one tick, like an array.","\u003Cstrong>Always pair an async watcher with cleanup\u003C\u002Fstrong> the moment more than one run can be in flight at once — a search box, a resource fetched by an ID prop, anything driven by fast user input.","\u003Cstrong>Avoid \u003Ccode>deep: true\u003C\u002Fcode> on large objects\u003C\u002Fstrong> if you can watch a specific derived getter instead; the traversal cost is real.",{"id":315,"html":316,"text":316,"type":91,"level":31},"b58","FAQ",{"id":318,"html":319,"text":319,"type":91,"level":43},"b59","Does watchEffect run on component mount?",{"id":321,"html":322,"type":83},"b60","\u003Cp>It runs when it&#39;s created — during \u003Ccode>setup()\u003C\u002Fcode>, before mount — synchronously with the default \u003Ccode>flush: &#39;pre&#39;\u003C\u002Fcode>; with \u003Ccode>flush: &#39;post&#39;\u003C\u002Fcode> (\u003Ccode>watchPostEffect\u003C\u002Fcode>) even the first run waits until after render. That first run is also how it discovers its dependencies.\u003C\u002Fp>",{"id":324,"html":325,"text":326,"type":91,"level":43},"b61","What&#39;s the difference between watch&#39;s deep option and watchEffect?","What's the difference between watch's deep option and watchEffect?",{"id":328,"html":329,"type":83},"b62","\u003Cp>They&#39;re unrelated axes. \u003Ccode>deep\u003C\u002Fcode> controls how far into a \u003Cem>reactive object\u003C\u002Fem> \u003Ccode>watch\u003C\u002Fcode> traverses to detect a change. \u003Ccode>watchEffect\u003C\u002Fcode> always tracks only what it actually reads during a run, deep or shallow, because it has no fixed source to traverse — it just doesn&#39;t re-read nested properties unless your function body does.\u003C\u002Fp>",{"id":331,"html":332,"text":333,"type":91,"level":43},"b63","Why isn&#39;t my watch callback firing when I mutate a reactive object property?","Why isn't my watch callback firing when I mutate a reactive object property?",{"id":335,"html":336,"type":83},"b64","\u003Cp>The most common cause is watching a plain snapshot instead of a getter — see Stage 3. The second most common cause is watching a \u003Ccode>ref\u003C\u002Fcode> holding an object and mutating a nested property without \u003Ccode>deep: true\u003C\u002Fcode> (the object is reactive, but \u003Ccode>watch(ref)\u003C\u002Fcode> only tracks \u003Ccode>.value\u003C\u002Fcode>); the ref&#39;s own \u003Ccode>.value\u003C\u002Fcode> reference hasn&#39;t changed, so a shallow watch sees nothing.\u003C\u002Fp>",{"id":338,"html":339,"text":339,"type":91,"level":43},"b65","How do I stop a watcher manually?",{"id":341,"html":342,"type":83},"b66","\u003Cp>Call the function \u003Ccode>watch\u003C\u002Fcode>\u002F\u003Ccode>watchEffect\u003C\u002Fcode> returns: \u003Ccode>const stop = watch(...); stop();\u003C\u002Fcode>. For a watcher created synchronously inside \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode>, you rarely need to — unmount does it for you.\u003C\u002Fp>",{"id":344,"html":345,"text":345,"type":91,"level":43},"b67","Is watchEffect the same as a computed?",{"id":347,"html":348,"type":83},"b68","\u003Cp>No. \u003Ccode>computed()\u003C\u002Fcode> returns a cached \u003Cem>value\u003C\u002Fem> meant to be read elsewhere and must stay pure (no side effects). \u003Ccode>watchEffect\u003C\u002Fcode> runs a \u003Cem>side effect\u003C\u002Fem> and returns nothing meant to be read — it&#39;s for doing something, not deriving something.\u003C\u002Fp>",{"id":350,"html":351,"text":351,"type":91,"level":43},"b69","Does watch support async callbacks?",{"id":353,"html":354,"type":83},"b70","\u003Cp>Yes — \u003Ccode>async (newVal, oldVal) =&gt; { ... }\u003C\u002Fcode> works exactly as shown above. Just remember that \u003Ccode>oldVal\u003C\u002Fcode> and \u003Ccode>newVal\u003C\u002Fcode> are captured before the \u003Ccode>await\u003C\u002Fcode>, and that any \u003Ccode>onWatcherCleanup()\u003C\u002Fcode> call must happen before the first \u003Ccode>await\u003C\u002Fcode>.\u003C\u002Fp>",{"id":356,"html":357,"text":357,"type":91,"level":31},"b71","Cheat sheet",{"id":359,"code":360,"type":142,"language":170,"highlight":361},"b72","import { ref, reactive, watch, watchEffect, onWatcherCleanup } from \"vue\";\n\n\u002F\u002F watch(): explicit source, lazy by default, gives old + new value\nwatch(source, (newVal, oldVal) => { \u002F* ... *\u002F });\nwatch([a, b], ([newA, newB], [oldA, oldB]) => { \u002F* ... *\u002F });\nwatch(() => reactiveObj.prop, (newVal, oldVal) => { \u002F* ... *\u002F }); \u002F\u002F getter for a nested property\n\n\u002F\u002F watchEffect(): auto-tracked, runs immediately, no old value\nwatchEffect(() => { document.title = title.value; });\n\n\u002F\u002F Options (watch only, except flush which both accept)\nwatch(source, cb, {\n  immediate: true, \u002F\u002F also run once immediately (oldValue is undefined that time)\n  deep: true,      \u002F\u002F traverse nested properties, not just the top reference\n  once: true,      \u002F\u002F Vue 3.4+: auto-stop after first fire\n  flush: \"post\",   \u002F\u002F 'pre' (default) | 'post' (after DOM update) | 'sync' (no batching)\n});\n\n\u002F\u002F Cleanup: cancel a stale async run before the next one starts (Vue 3.5+)\nwatch(source, async (val) => {\n  const controller = new AbortController();\n  onWatcherCleanup(() => controller.abort()); \u002F\u002F before the first await\n  try {\n    await fetch(url, { signal: controller.signal });\n  } catch (e) {\n    if (e.name !== \"AbortError\") throw e;\n  }\n});\n\n\u002F\u002F Stopping\nconst stop = watchEffect(() => { \u002F* ... *\u002F });\nstop(); \u002F\u002F manual — usually unnecessary for watchers created synchronously in \u003Cscript setup>, which auto-stop on unmount\n\u002F\u002F Vue 3.5+: the handle also has stop.pause() \u002F stop.resume()",[],{"id":363,"html":364,"text":364,"type":91,"level":31},"b73","Key takeaways",{"id":366,"type":97,"items":367,"ordered":18},"b74",[368,369,370,371],"\u003Ccode>watch\u003C\u002Fcode> is told its source explicitly and is lazy by default; \u003Ccode>watchEffect\u003C\u002Fcode> discovers its sources by running once, immediately, and re-runs the whole function when any of them change.","\u003Ccode>flush\u003C\u002Fcode> decides \u003Cem>when\u003C\u002Fem> relative to the DOM update — \u003Ccode>&#39;pre&#39;\u003C\u002Fcode> (default) can run before a DOM change, \u003Ccode>&#39;post&#39;\u003C\u002Fcode> waits for it, \u003Ccode>&#39;sync&#39;\u003C\u002Fcode> skips batching entirely.","\u003Ccode>onWatcherCleanup\u003C\u002Fcode> (Vue 3.5+) — or the callback&#39;s third \u003Ccode>onCleanup\u003C\u002Fcode> argument, the only option on earlier versions — fires right before the next run, which is exactly what stops a slow, stale async response from overwriting a fresher one.","A watcher created synchronously in \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode> stops itself on unmount; one created outside a component&#39;s setup needs its returned \u003Ccode>stop()\u003C\u002Fcode> called by hand.",{"id":373,"html":374,"type":83},"b75","\u003Cp>The search box from the top of this article isn&#39;t really a search-box bug — it&#39;s what happens any time a watcher&#39;s \u003Cem>previous\u003C\u002Fem> run outlives its \u003Cem>next\u003C\u002Fem> run and nobody told it to stop. Once cleanup is a habit rather than an afterthought, that entire category of bug stops happening. What&#39;s the last race condition you tracked down that turned out to be a missing cleanup? Drop it in the comments.\u003C\u002Fp>",{"id":376,"html":377,"type":83},"b76","\u003C!-- playground:start -->",{"id":379,"html":380,"text":380,"type":91,"level":31},"b77","🎮 Try it yourself",{"id":382,"html":383,"type":83},"b78","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-watch-vs-watcheffect\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":385,"html":386,"type":83},"b79","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":388,"html":389,"type":83},"b80","\u003C!-- playground:end -->",{"id":391,"html":392,"type":83},"b81","\u003C!-- quiz:start -->",{"id":394,"html":395,"text":395,"type":91,"level":31},"b82","🧠 Test yourself",{"id":397,"html":398,"type":83},"b83","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-watch-vs-watcheffect\u002Fquiz\">Take the 9-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":400,"html":401,"type":83},"b84","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":403,"html":404,"type":83},"b85","\u003C!-- quiz:end -->",{"id":406,"html":407,"type":83},"b86","\u003C!-- related:start -->",{"id":409,"html":410,"text":410,"type":91,"level":31},"b87","📚 Read next",{"id":412,"type":97,"items":413,"ordered":18},"b88",[414,415,416],"\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":418,"html":419,"type":83},"b89","\u003C!-- related:end -->",{"id":421,"type":422},"b90","divider",{"id":424,"html":425,"type":83},"b91","\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":427,"html":428,"type":83},"b92","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":430,"type":97,"items":431,"ordered":18},"b93",[432,433,434],"⭐ \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 type \"vue\" into a search box, one letter at a time. Four requests go out — for `v`, `vu`, `vue`, and the deleted-and-retyped `vue` again — and the one for `v` happens to come back last. The screen ends up showing results for a single letter, on top of a `watch()` callback that looks completely correct. Nothing in that callback is wrong. The watcher just never learned that a newer run had already made it obsolete.\n\nThis is the article that fixes that, and along the way answers the question every Vue developer eventually asks: **when do I reach for `watch()`, when do I reach for `watchEffect()`, and why does my callback keep firing at the wrong moment?**\n\n## What you'll learn\n\nBy the end of this article you'll be able to:\n\n- Explain the real difference between `watch()` and `watchEffect()` — not \"one is explicit, one is automatic,\" but *what that difference costs you*\n- Pick the correct `flush` timing (`pre`, `post`, `sync`) for a watcher that needs to read the DOM\n- Use `onWatcherCleanup` to cancel a stale async response before it overwrites a newer one\n- Stop a watcher manually, and know when Vue stops it for you\n- Recognize the specific ways both APIs quietly track (or fail to track) the wrong things\n\n## Prerequisites\n\nYou should already be comfortable with `ref()`, `reactive()`, and `computed()` in the Composition API. If \"why does destructuring a `reactive()` object lose reactivity\" isn't yet second nature, read [Vue Reactivity Explained: ref vs reactive](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-reactivity-explained-ref-vs-reactive-cheat-sheet-4nij) first — this article assumes that mental model and builds on it.\n\nThis is written against **Vue 3.5.x** (verified September 2026). Version-specific notes (the `once` option, `onWatcherCleanup`) are called out where they apply.\n\n## Table of contents\n\n- [The problem: a watcher with no memory](#the-problem-a-watcher-with-no-memory)\n- [The mental model: pull vs. push tracking](#the-mental-model-pull-vs-push-tracking)\n- [Stage 1: the smallest watch()](#stage-1-the-smallest-watch)\n- [Stage 2: the same job with watchEffect()](#stage-2-the-same-job-with-watcheffect)\n- [Stage 3: multiple sources, and watching inside an object](#stage-3-multiple-sources-and-watching-inside-an-object)\n- [Stage 4: immediate, deep, and once](#stage-4-immediate-deep-and-once)\n- [Stage 5: flush timing — pre, post, sync](#stage-5-flush-timing--pre-post-sync)\n- [Stage 6: cleanup and the race condition, fixed](#stage-6-cleanup-and-the-race-condition-fixed)\n- [Stopping a watcher](#stopping-a-watcher)\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\n## The problem: a watcher with no memory\n\nHere's the search box, written the way it's tempting to write it the first time:\n\n```vue\n\u003Cscript setup>\nimport { ref, watch } from \"vue\";\n\nconst query = ref(\"\");\nconst results = ref([]);\n\nwatch(query, async (q) => {\n  if (!q) {\n    results.value = [];\n    return;\n  }\n  const res = await fetch(`\u002Fapi\u002Fsearch?q=${encodeURIComponent(q)}`);\n  results.value = await res.json();\n});\n\u003C\u002Fscript>\n```\n\nEvery keystroke fires a new request, and requests do not resolve in the order they were sent. If the request for `v` is slow and the request for `vue` is fast, `vue`'s results land first and `v`'s results land *after*, silently overwriting them. The callback did exactly what it was written to do; it just never asked \"am I still the most recent run?\"\n\nThat question is exactly what a watcher's cleanup mechanism answers. Getting there first requires being precise about what `watch()` and `watchEffect()` actually are.\n\n## The mental model: pull vs. push tracking\n\n**The mental model:** `watch()` is *told* what to watch. `watchEffect()` *discovers* what to watch by running once and recording every reactive value it reads. Everything else — old\u002Fnew values, laziness, immediacy — falls out of that one distinction.\n\n- **`watch(source, callback)`** takes an explicit source (a ref, a getter, a `reactive()` object, or an array of them) and a callback. It does **not** run on creation by default. It runs only when the *named* source changes, and it hands the callback both the new and the old value.\n- **`watchEffect(fn)`** takes a single function with no separate source. It runs **immediately**, once, synchronously (well — see flush timing below) to figure out what it read, and re-runs itself in full whenever any of those dependencies change. There is no \"old value\" concept, because there's no fixed source to diff against — the whole function reruns.\n\nPut differently: `watch` is a subscription to a specific address. `watchEffect` is a subscription to *whatever addresses this function happens to visit today* — and it can visit different addresses on different runs, exactly like a `computed()` getter does. The dependency-tracking mechanism (a run records every reactive value it reads) is shared across `computed`, `watch`'s getter-form sources, and `watchEffect` alike.\n\n## Stage 1: the smallest watch()\n\n```ts\nimport { ref, watch } from \"vue\";\n\nconst count = ref(0);\n\nwatch(count, (newVal, oldVal) => {\n  console.log(`count went from ${oldVal} to ${newVal}`);\n});\n\ncount.value++; \u002F\u002F logs: \"count went from 0 to 1\"\n```\n\n**Key concept:** the callback did not run when `watch` was declared — only after `count` changed. That's the \"lazy by default\" behavior, and it's the first thing that trips people up coming from `watchEffect`.\n\n## Stage 2: the same job with watchEffect()\n\n```ts\nimport { ref, watchEffect } from \"vue\";\n\nconst count = ref(0);\n\nwatchEffect(() => {\n  console.log(`count is now ${count.value}`);\n});\n\u002F\u002F logs immediately: \"count is now 0\"\n\ncount.value++; \u002F\u002F logs: \"count is now 1\"\n```\n\n**Key concept:** `watchEffect` ran once immediately, on creation, before anything changed — because it has to run once just to discover that `count` is a dependency. `watch` never needed to run to know its source; you named it.\n\nIf the job is \"sync this state to something external, right now and on every future change\" — a document title, a `localStorage` key, an analytics call — `watchEffect` is usually the shorter answer. Need the *old* value, or need to stay silent until one specific thing changes? Reach for `watch`.\n\n## Stage 3: multiple sources, and watching inside an object\n\n`watch` accepts an array of sources, and hands the callback matching arrays of new\u002Fold values:\n\n```ts\nwatch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {\n  console.log(`${oldFirst} ${oldLast} -> ${newFirst} ${newLast}`);\n});\n```\n\nWatching a single property of a `reactive()` object requires a **getter function**, not the bare property — because `reactive().someProp` is just a plain value the instant you read it out of the object, with no reference back to the proxy:\n\n```ts\nconst state = reactive({ count: 0, name: \"vue\" });\n\n\u002F\u002F Wrong: this is a snapshot number, not a reactive source.\n\u002F\u002F watch(state.count, cb) never fires.\n\n\u002F\u002F Correct: a getter re-reads the proxy every time Vue checks it.\nwatch(\n  () => state.count,\n  (newCount, oldCount) => console.log(newCount, oldCount)\n);\n```\n\nThis is the same \"reactivity is a subscription made at read time\" rule from the `ref` vs `reactive` article — `watch` needs something it can re-read, and a getter is how you hand it that.\n\n## Stage 4: immediate, deep, and once\n\nThree options on `watch` change when and how often it fires (`watchEffect` accepts only `flush`, covered next, plus the dev-only `onTrack`\u002F`onTrigger` — it runs on creation and tracks everything it reads, so `immediate`, `deep` and `once` don't apply):\n\n```ts\nwatch(source, callback, {\n  immediate: true, \u002F\u002F also run once, right away, with oldValue undefined\n  deep: true,      \u002F\u002F walk the whole object\u002Farray, not just the top-level reference\n  once: true,      \u002F\u002F (Vue 3.4+) auto-stop after the callback fires the first time\n});\n```\n\n`deep: true` is the one to use sparingly. Watching a getter that returns an object (`() => state.someObject`) or a `ref` holding one only fires when the whole object is replaced (a `reactive()` object passed directly is already implicitly deep); `deep` walks every nested property so a change three levels down also fires — at the cost of Vue actually traversing the structure to set that tracking up. On a large or deeply nested object, that traversal is real work.\n\n## Stage 5: flush timing — pre, post, sync\n\nBoth `watch` and `watchEffect` accept `flush`, and it answers a different question than the options above: *relative to a component's own re-render, when does this callback run?*\n\n| `flush` value | When it runs | Default? |\n| --- | --- | --- |\n| `'pre'` | Before the component re-renders | Yes, for both APIs |\n| `'post'` | After the component has re-rendered and the DOM is updated | No |\n| `'sync'` | Synchronously, the instant the dependency is written | No |\n\nThis connects directly to the batching behavior from [Vue nextTick Explained](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-nexttick-explained-how-dom-updates-are-batched-3gnp): a `pre`-flush watcher (the default) can run *before* the DOM reflects the change that triggered it. If your callback needs to measure or read an element that just changed size or content, `flush: 'pre'` will read stale layout:\n\n```ts\nwatch(\n  items,\n  () => {\n    \u002F\u002F With the default 'pre' flush, the DOM here may not yet\n    \u002F\u002F reflect the new `items` — this measurement can be one step behind.\n    console.log(listRef.value.scrollHeight);\n  },\n  { flush: \"post\" } \u002F\u002F wait for the DOM update first\n);\n```\n\n`flush: 'sync'` skips Vue's batching queue entirely and runs the instant the dependency is written — useful for invalidating an external cache the moment something changes, but dangerous on sources mutated many times synchronously (e.g. an array filled in a loop), since it fires on every single mutation with no batching.\n\n## Stage 6: cleanup and the race condition, fixed\n\nBack to the search box. The fix is to give each run a way to say \"abort me if a newer run has already started\" — and both `watch` and `watchEffect` give you exactly that, as `onWatcherCleanup()` (3.5+), which must be called **synchronously** during the callback (before any `await`):\n\n```vue\n\u003Cscript setup>\nimport { ref, watch, onWatcherCleanup } from \"vue\"; \u002F\u002F onWatcherCleanup: Vue 3.5+\n\nconst query = ref(\"\");\nconst results = ref([]);\n\nwatch(query, async (q) => {\n  if (!q) {\n    results.value = [];\n    return;\n  }\n\n  const controller = new AbortController();\n  onWatcherCleanup(() => controller.abort());\n\n  try {\n    const res = await fetch(`\u002Fapi\u002Fsearch?q=${encodeURIComponent(q)}`, {\n      signal: controller.signal,\n    });\n    results.value = await res.json();\n  } catch (e) {\n    if (e.name !== \"AbortError\") throw e; \u002F\u002F an aborted run is expected, not an error\n  }\n});\n\u003C\u002Fscript>\n```\n\n**Key concept:** `onWatcherCleanup` registers a function that Vue calls *right before the next run of this same watcher* — and also when the component unmounts. Type `vue`, and the moment the `e` keystroke starts a new run, the previous run's cleanup fires first, aborting its in-flight fetch. An aborted request rejects with an `AbortError` instead of resolving, so it can never reach `results.value` — and the `try`\u002F`catch` swallows just that expected rejection, which would otherwise surface through Vue's error handler on every superseded keystroke. Order of arrival stops mattering, because only the current, un-aborted request is allowed to write.\n\nThe same mechanism is also passed as the callback's third parameter (the only option before 3.5, and still supported):\n\n```ts\nwatch(query, async (q, oldQ, onCleanup) => {\n  const controller = new AbortController();\n  onCleanup(() => controller.abort());\n  \u002F\u002F ...\n});\n```\n\nThe two forms differ here: `onWatcherCleanup` must be called before the first `await` — after it, Vue can't tell which watcher run it belongs to, so dev mode warns and the cleanup is silently dropped. The `onCleanup` argument is bound to its watcher, so it isn't subject to that constraint.\n\n## Stopping a watcher\n\nBoth `watch` and `watchEffect` return a handle — call it (or `.stop()`) to stop; since 3.5 it also has `.pause()` \u002F `.resume()`:\n\n```ts\nconst stop = watchEffect(() => {\n  console.log(count.value);\n});\n\nstop(); \u002F\u002F this effect will never run again\n```\n\nYou rarely need to call this yourself inside a component: a watcher created **synchronously** during `setup()` or `\u003Cscript setup>` is automatically stopped when the component unmounts; one created later from an async callback (a `setTimeout`, a `.then`) inside that same `\u003Cscript setup>` is not. You need the manual `stop()` mostly for watchers created outside a component's setup — inside a composable that runs after mount, in a store, or in plain JS — where Vue has no unmount to tie it to.\n\n## Edge cases and gotchas\n\n- **`await` breaks dependency discovery.** `watchEffect` only tracks reads that happen synchronously before the first `await`. Anything read after an `await` inside the function is invisible to it and will not trigger a re-run if it changes.\n- **A getter that reads nothing reactive never fires.** `watch(() => someObj.plainProperty, cb)` looks correct but never runs if `plainProperty` isn't itself reactive — there's nothing for Vue to subscribe to.\n- **`watchEffect` can over-track.** Because it tracks everything the function body reads, a large conditional inside it can pick up dependencies you didn't intend, causing extra re-runs. `watch` with an explicit source avoids this by construction.\n- **`.push`\u002F`.splice` fire a non-deep watcher only when the source is the `reactive()` array itself** (implicitly deep). `watch(itemsRef, cb)` on a `ref([])`, or a getter like `() => state.items`, tracks only the array reference — in-place mutations need `deep: true`, or watch `() => items.value.length`.\n- **`immediate: true` hands `undefined` as `oldValue`** on that first synthetic run (an empty array `[]` when watching an array of sources) — always guard against it if the callback compares old and new.\n\n## Best practices\n\n- **Reach for `watchEffect`** when the job is \"keep this side effect in sync with whatever it reads\" and you don't need the old value — document titles, `localStorage`, imperative DOM syncing.\n- **Reach for `watch`** when you need the previous value, need the callback to stay silent until one *specific* thing changes (not everything the function happens to read), or want lazy behavior by default.\n- **Default to `flush: 'pre'`** (the default) unless the callback reads the DOM — then use `'post'`. Reserve `'sync'` for cache-invalidation-style work that must happen before Vue's own batching, and never on a source that's mutated many times in one tick, like an array.\n- **Always pair an async watcher with cleanup** the moment more than one run can be in flight at once — a search box, a resource fetched by an ID prop, anything driven by fast user input.\n- **Avoid `deep: true` on large objects** if you can watch a specific derived getter instead; the traversal cost is real.\n\n## FAQ\n\n### Does watchEffect run on component mount?\n\nIt runs when it's created — during `setup()`, before mount — synchronously with the default `flush: 'pre'`; with `flush: 'post'` (`watchPostEffect`) even the first run waits until after render. That first run is also how it discovers its dependencies.\n\n### What's the difference between watch's deep option and watchEffect?\n\nThey're unrelated axes. `deep` controls how far into a *reactive object* `watch` traverses to detect a change. `watchEffect` always tracks only what it actually reads during a run, deep or shallow, because it has no fixed source to traverse — it just doesn't re-read nested properties unless your function body does.\n\n### Why isn't my watch callback firing when I mutate a reactive object property?\n\nThe most common cause is watching a plain snapshot instead of a getter — see Stage 3. The second most common cause is watching a `ref` holding an object and mutating a nested property without `deep: true` (the object is reactive, but `watch(ref)` only tracks `.value`); the ref's own `.value` reference hasn't changed, so a shallow watch sees nothing.\n\n### How do I stop a watcher manually?\n\nCall the function `watch`\u002F`watchEffect` returns: `const stop = watch(...); stop();`. For a watcher created synchronously inside `\u003Cscript setup>`, you rarely need to — unmount does it for you.\n\n### Is watchEffect the same as a computed?\n\nNo. `computed()` returns a cached *value* meant to be read elsewhere and must stay pure (no side effects). `watchEffect` runs a *side effect* and returns nothing meant to be read — it's for doing something, not deriving something.\n\n### Does watch support async callbacks?\n\nYes — `async (newVal, oldVal) => { ... }` works exactly as shown above. Just remember that `oldVal` and `newVal` are captured before the `await`, and that any `onWatcherCleanup()` call must happen before the first `await`.\n\n## Cheat sheet\n\n```ts\nimport { ref, reactive, watch, watchEffect, onWatcherCleanup } from \"vue\";\n\n\u002F\u002F watch(): explicit source, lazy by default, gives old + new value\nwatch(source, (newVal, oldVal) => { \u002F* ... *\u002F });\nwatch([a, b], ([newA, newB], [oldA, oldB]) => { \u002F* ... *\u002F });\nwatch(() => reactiveObj.prop, (newVal, oldVal) => { \u002F* ... *\u002F }); \u002F\u002F getter for a nested property\n\n\u002F\u002F watchEffect(): auto-tracked, runs immediately, no old value\nwatchEffect(() => { document.title = title.value; });\n\n\u002F\u002F Options (watch only, except flush which both accept)\nwatch(source, cb, {\n  immediate: true, \u002F\u002F also run once immediately (oldValue is undefined that time)\n  deep: true,      \u002F\u002F traverse nested properties, not just the top reference\n  once: true,      \u002F\u002F Vue 3.4+: auto-stop after first fire\n  flush: \"post\",   \u002F\u002F 'pre' (default) | 'post' (after DOM update) | 'sync' (no batching)\n});\n\n\u002F\u002F Cleanup: cancel a stale async run before the next one starts (Vue 3.5+)\nwatch(source, async (val) => {\n  const controller = new AbortController();\n  onWatcherCleanup(() => controller.abort()); \u002F\u002F before the first await\n  try {\n    await fetch(url, { signal: controller.signal });\n  } catch (e) {\n    if (e.name !== \"AbortError\") throw e;\n  }\n});\n\n\u002F\u002F Stopping\nconst stop = watchEffect(() => { \u002F* ... *\u002F });\nstop(); \u002F\u002F manual — usually unnecessary for watchers created synchronously in \u003Cscript setup>, which auto-stop on unmount\n\u002F\u002F Vue 3.5+: the handle also has stop.pause() \u002F stop.resume()\n```\n\n## Key takeaways\n\n- `watch` is told its source explicitly and is lazy by default; `watchEffect` discovers its sources by running once, immediately, and re-runs the whole function when any of them change.\n- `flush` decides *when* relative to the DOM update — `'pre'` (default) can run before a DOM change, `'post'` waits for it, `'sync'` skips batching entirely.\n- `onWatcherCleanup` (Vue 3.5+) — or the callback's third `onCleanup` argument, the only option on earlier versions — fires right before the next run, which is exactly what stops a slow, stale async response from overwriting a fresher one.\n- A watcher created synchronously in `\u003Cscript setup>` stops itself on unmount; one created outside a component's setup needs its returned `stop()` called by hand.\n\nThe search box from the top of this article isn't really a search-box bug — it's what happens any time a watcher's *previous* run outlives its *next* run and nobody told it to stop. Once cleanup is a habit rather than an afterthought, that entire category of bug stops happening. What's the last race condition you tracked down that turned out to be a missing cleanup? Drop it in the comments.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-watch-vs-watcheffect\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 9-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-watch-vs-watcheffect\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":437,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-watch-vs-watcheffect","01a0ec73-2e42-76ec-87cd-d31b9166ab40",{"name":440,"part":441,"total":441,"items":442},"Vue Deep Dive",6,[443,448,453,457,462,466],{"slug":444,"title":445,"publishedAt":446,"readingMinutes":447},"vue-weekly-reactivity-ref-vs-reactive","Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)","2026-08-24T13:57:38.080Z",14,{"slug":449,"title":450,"publishedAt":451,"readingMinutes":452},"vue-weekly-nexttick-batching-dom-updates","Vue nextTick Explained: How DOM Updates Are Batched","2026-09-14T12:19:00.606Z",13,{"slug":454,"title":455,"publishedAt":456,"readingMinutes":452},"vue-weekly-composables-shared-state-trap","Vue Composables: The Shared State Trap (+ Cheat Sheet)","2026-09-22T19:37:11.460Z",{"slug":458,"title":459,"publishedAt":460,"readingMinutes":461},"vue-weekly-computed-caching-invalidation","Vue computed(): What It Caches and When It Reruns","2026-09-30T12:10:53.362Z",12,{"slug":463,"title":464,"publishedAt":465,"readingMinutes":452},"vue-weekly-definemodel-v-model-contract","Vue defineModel: The v-model Contract, Explained","2026-10-05T06:11:02.623Z",{"slug":46,"title":47,"publishedAt":52,"readingMinutes":51},{"id":468,"locked":18},"01a0ec73-2eec-754e-8b83-736a2ce917c3",[470],{"id":471,"slug":46,"title":472,"_count":473},"01a0ec73-2f40-7369-a7f5-edde626bf628","Vue watch() vs watchEffect()",{"questions":474},9,[476],{"locale":13,"slug":46},{"id":471,"slug":46,"title":472,"_count":478,"questionCount":474},{"questions":474},[480,482,484,488,490,494,498,502,506,509,512,515],{"slug":72,"name":73,"articles":481},135,{"slug":66,"name":67,"articles":483},111,{"slug":485,"name":486,"articles":487},"frontend","Frontend",82,{"slug":69,"name":70,"articles":489},52,{"slug":491,"name":492,"articles":493},"css","Css",42,{"slug":495,"name":496,"articles":497},"performance","Performance",19,{"slug":499,"name":500,"articles":501},"typescript","Typescript",18,{"slug":503,"name":504,"articles":505},"react","React",16,{"slug":507,"name":508,"articles":452},"browser","Browser",{"slug":510,"name":511,"articles":461},"node","Node",{"slug":513,"name":514,"articles":474},"accessibility","Accessibility",{"slug":516,"name":517,"articles":474},"programming","Programming",{"items":519,"meta":607},[520,532,547,562,577,592],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":521,"likeCount":19,"commentCount":19,"author":522,"vertical":523,"topic":524,"tags":525,"_count":530,"playground":531,"hasQuiz":17,"hasPlayground":17},35,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[526,527,528,529],{"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":533,"slug":463,"title":464,"subtitle":48,"excerpt":534,"coverUrl":535,"locale":13,"readingMinutes":452,"publishedAt":465,"viewCount":536,"likeCount":19,"commentCount":19,"author":537,"vertical":538,"topic":539,"tags":540,"_count":545,"playground":546,"hasQuiz":17,"hasPlayground":17},"01a0af18-2d15-7347-b2f4-6cdfa2ce56fa","Vue's defineModel macro replaces the modelValue prop and update:modelValue event with one ref. The exact v-model contract behind it, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-definemodel-v-model-contract.png",426,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[541,542,543,544],{"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":463},{"id":548,"slug":458,"title":459,"subtitle":48,"excerpt":549,"coverUrl":550,"locale":13,"readingMinutes":461,"publishedAt":460,"viewCount":551,"likeCount":19,"commentCount":19,"author":552,"vertical":553,"topic":554,"tags":555,"_count":560,"playground":561,"hasQuiz":17,"hasPlayground":17},"01a08af7-9d6f-74df-b76e-f6c445f54a47","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",379,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[556,557,558,559],{"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":458},{"id":563,"slug":454,"title":455,"subtitle":48,"excerpt":564,"coverUrl":565,"locale":13,"readingMinutes":452,"publishedAt":456,"viewCount":566,"likeCount":19,"commentCount":19,"author":567,"vertical":568,"topic":569,"tags":570,"_count":575,"playground":576,"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",498,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[571,572,573,574],{"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":454},{"id":578,"slug":449,"title":450,"subtitle":48,"excerpt":579,"coverUrl":580,"locale":13,"readingMinutes":452,"publishedAt":451,"viewCount":581,"likeCount":19,"commentCount":19,"author":582,"vertical":583,"topic":584,"tags":585,"_count":590,"playground":591,"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",539,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[586,587,588,589],{"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":449},{"id":593,"slug":444,"title":445,"subtitle":48,"excerpt":594,"coverUrl":595,"locale":13,"readingMinutes":447,"publishedAt":446,"viewCount":596,"likeCount":19,"commentCount":75,"author":597,"vertical":598,"topic":599,"tags":600,"_count":605,"playground":606,"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",859,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[601,602,603,604],{"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":444},{"page":75,"perPage":608,"total":441,"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>\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> watch \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> query \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> results \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-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    results\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> res \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u002Fapi\u002Fsearch?q=\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>\u003Cspan class=\"shj-func\">encodeURIComponent\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  results\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> res\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">json\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\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>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\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>\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> watch \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>;\n\n\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>newVal\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> oldVal\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\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\">`count went from \u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>oldVal\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\"> to \u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>newVal\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\ncount\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F logs: \"count went from 0 to 1\"\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\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>\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> watchEffect \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>;\n\n\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-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\">`count is now \u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>count\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F logs immediately: \"count is now 0\"\n\u003C\u002Fspan>\ncount\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F logs: \"count is now 1\"\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>firstName\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> lastName\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>newFirst\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> newLast\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>oldFirst\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> oldLast\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  console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>oldFirst\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\"> \u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>oldLast\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\"> -&gt; \u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>newFirst\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\"> \u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>newLast\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\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\">\"vue\"\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Wrong: this is a snapshot number, not a reactive source.\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F watch(state.count, cb) never fires.\n\u003C\u002Fspan>\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Correct: a getter re-reads the proxy every time Vue checks it.\n\u003C\u002Fspan>\u003Cspan class=\"shj-func\">watch\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\">=&gt;\u003C\u002Fspan> state\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>count\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>newCount\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> oldCount\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>newCount\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> oldCount\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\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\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>source\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> callback\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  immediate\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F also run once, right away, with oldValue undefined\n\u003C\u002Fspan>  deep\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>      \u003Cspan class=\"shj-cmnt\">\u002F\u002F walk the whole object\u002Farray, not just the top-level reference\n\u003C\u002Fspan>  once\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>      \u003Cspan class=\"shj-cmnt\">\u002F\u002F (Vue 3.4+) auto-stop after the callback fires the first time\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-ts shj-multiline\" data-lang=\"ts\">\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n  items\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \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-cmnt\">\u002F\u002F With the default 'pre' flush, the DOM here may not yet\n\u003C\u002Fspan>    \u003Cspan class=\"shj-cmnt\">\u002F\u002F reflect the new `items` — this measurement can be one step behind.\n\u003C\u002Fspan>    console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>listRef\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>scrollHeight\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> flush\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"post\"\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F wait for the DOM update first\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\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>\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>\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> watch\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> onWatcherCleanup \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"vue\"\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F onWatcherCleanup: Vue 3.5+\n\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> query \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> results \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-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    results\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> controller \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">AbortController\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">onWatcherCleanup\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> controller\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">abort\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">try\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> res \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u002Fapi\u002Fsearch?q=\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>\u003Cspan class=\"shj-func\">encodeURIComponent\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      signal\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> controller\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>signal\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    results\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> res\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">json\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">catch\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>name \u003Cspan class=\"shj-oper\">!==\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"AbortError\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">throw\u003C\u002Fspan> e; \u003Cspan class=\"shj-cmnt\">\u002F\u002F an aborted run is expected, not an error\n\u003C\u002Fspan>  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\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>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\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\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>q\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> oldQ\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> onCleanup\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> controller \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">AbortController\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">onCleanup\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> controller\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">abort\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-cmnt\">\u002F\u002F ...\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-ts shj-multiline\" data-lang=\"ts\">\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-kwd\">const\u003C\u002Fspan> stop \u003Cspan class=\"shj-oper\">=\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-bracket\">{\u003C\u002Fspan>\n  console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>count\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-func\">stop\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F this effect will never run again\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\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>\u003Cdiv>27\u003C\u002Fdiv>\u003Cdiv>28\u003C\u002Fdiv>\u003Cdiv>29\u003C\u002Fdiv>\u003Cdiv>30\u003C\u002Fdiv>\u003Cdiv>31\u003C\u002Fdiv>\u003Cdiv>32\u003C\u002Fdiv>\u003Cdiv>33\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> watch\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> watchEffect\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> onWatcherCleanup \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 watch(): explicit source, lazy by default, gives old + new value\n\u003C\u002Fspan>\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>source\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>newVal\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> oldVal\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F* ... *\u002F\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>a\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> b\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>newA\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> newB\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>oldA\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> oldB\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* ... *\u002F\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-func\">watch\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> reactiveObj\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>prop\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>newVal\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> oldVal\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F* ... *\u002F\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F getter for a nested property\n\u003C\u002Fspan>\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F watchEffect(): auto-tracked, runs immediately, no old value\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-bracket\">{\u003C\u002Fspan> document\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>title \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> title\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value; \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Options (watch only, except flush which both accept)\n\u003C\u002Fspan>\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>source\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> cb\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  immediate\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F also run once immediately (oldValue is undefined that time)\n\u003C\u002Fspan>  deep\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>      \u003Cspan class=\"shj-cmnt\">\u002F\u002F traverse nested properties, not just the top reference\n\u003C\u002Fspan>  once\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>      \u003Cspan class=\"shj-cmnt\">\u002F\u002F Vue 3.4+: auto-stop after first fire\n\u003C\u002Fspan>  flush\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"post\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>   \u003Cspan class=\"shj-cmnt\">\u002F\u002F 'pre' (default) | 'post' (after DOM update) | 'sync' (no batching)\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Cleanup: cancel a stale async run before the next one starts (Vue 3.5+)\n\u003C\u002Fspan>\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>source\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>val\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> controller \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">AbortController\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">onWatcherCleanup\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> controller\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">abort\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F before the first await\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">try\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>url\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> signal\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> controller\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>signal \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">catch\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>name \u003Cspan class=\"shj-oper\">!==\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"AbortError\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">throw\u003C\u002Fspan> e;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Stopping\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> stop \u003Cspan class=\"shj-oper\">=\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-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F* ... *\u002F\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-func\">stop\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F manual — usually unnecessary for watchers created synchronously in &lt;script setup&gt;, which auto-stop on unmount\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F Vue 3.5+: the handle also has stop.pause() \u002F stop.resume()\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":621},[]]