[{"data":1,"prerenderedAt":2064},["ShallowReactive",2],{"notes-\u002Fnotes\u002Fvue3-computed-watch":3},{"id":4,"title":5,"body":6,"date":2052,"description":2053,"draft":2054,"extension":2055,"meta":2056,"navigation":233,"path":2057,"seo":2058,"stem":2059,"tags":2060,"__hash__":2063},"notes\u002Fnotes\u002Fvue3-computed-watch.md","Vue 3 的 computed 和 watch：算出一個值，還是做一件事",{"type":7,"value":8,"toc":2024},"minimark",[9,13,41,45,52,134,138,143,150,303,330,349,352,355,359,370,560,567,666,670,681,750,753,757,764,799,806,833,837,840,863,866,871,887,892,900,905,908,918,922,928,930,993,1000,1099,1101,1151,1158,1316,1326,1329,1379,1383,1438,1441,1445,1448,1573,1599,1650,1674,1680,1684,1738,1742,1745,1768,1772,1926,1929,1933,1985,1990,2020],[10,11,12],"h2",{"id":12},"目錄",[14,15,16,20,23,26,29,32,35,38],"ol",{},[17,18,19],"li",{},"一句話",[17,21,22],{},"computed：算出一個值",[17,24,25],{},"watch：值變了之後去做一件事",[17,27,28],{},"怎麼判斷要用哪一個",[17,30,31],{},"寫法",[17,33,34],{},"例子：購物車的免運門檻",[17,36,37],{},"我原本的誤解",[17,39,40],{},"練習",[10,42,44],{"id":43},"_1-一句話","1. 一句話",[46,47,48],"p",{},[49,50,51],"strong",{},"要「得到一個值」用 computed，要「做一件事」用 watch。",[53,54,55,71],"table",{},[56,57,58],"thead",{},[59,60,61,65,68],"tr",{},[62,63,64],"th",{},"差異",[62,66,67],{},"computed",[62,69,70],{},"watch",[72,73,74,86,97,108,123],"tbody",{},[59,75,76,80,83],{},[77,78,79],"td",{},"目的",[77,81,82],{},"從現有資料算出一個值",[77,84,85],{},"值變了之後去做一件事（副作用）",[59,87,88,91,94],{},[77,89,90],{},"有無回傳值",[77,92,93],{},"有，拿來在 template 或其他地方用",[77,95,96],{},"沒有，執行 callback",[59,98,99,102,105],{},[77,100,101],{},"依賴",[77,103,104],{},"自動追蹤",[77,106,107],{},"明確指定要看哪一個",[59,109,110,113,116],{},[77,111,112],{},"舊值",[77,114,115],{},"通常不用（3.4 起 getter 可以拿到上一次的結果）",[77,117,118,122],{},[119,120,121],"code",{},"(newVal, oldVal)"," 都拿得到",[59,124,125,128,131],{},[77,126,127],{},"適合",[77,129,130],{},"篩選清單、組字串、加總、判斷布林",[77,132,133],{},"打 API、寫 localStorage、改網址、送分析事件",[10,135,137],{"id":136},"_2-computed算出一個值","2. computed：算出一個值",[139,140,142],"h3",{"id":141},"追蹤的是實際讀到的資料","追蹤的是「實際讀到的」資料",[46,144,145,146,149],{},"Vue 不是去看函式裡寫了哪些變數，而是在",[49,147,148],{},"執行時","記下這次讀到了哪些響應式資料。有條件分支時，沒走到的分支裡的資料不會被追蹤：",[151,152,157],"pre",{"className":153,"code":154,"language":155,"meta":156,"style":156},"language-js shiki shiki-themes github-dark","const showDetail = ref(false)\nconst name = ref('Jason')\nconst detail = ref('...')\n\nconst text = computed(() => {\n  if (!showDetail.value) return name.value\n  return name.value + detail.value   \u002F\u002F showDetail 是 false 時，detail 沒被讀到\n})\n","js","",[119,158,159,189,209,228,235,257,278,297],{"__ignoreMap":156},[160,161,164,168,172,175,179,183,186],"span",{"class":162,"line":163},"line",1,[160,165,167],{"class":166},"snl16","const",[160,169,171],{"class":170},"sDLfK"," showDetail",[160,173,174],{"class":166}," =",[160,176,178],{"class":177},"svObZ"," ref",[160,180,182],{"class":181},"s95oV","(",[160,184,185],{"class":170},"false",[160,187,188],{"class":181},")\n",[160,190,192,194,197,199,201,203,207],{"class":162,"line":191},2,[160,193,167],{"class":166},[160,195,196],{"class":170}," name",[160,198,174],{"class":166},[160,200,178],{"class":177},[160,202,182],{"class":181},[160,204,206],{"class":205},"sU2Wk","'Jason'",[160,208,188],{"class":181},[160,210,212,214,217,219,221,223,226],{"class":162,"line":211},3,[160,213,167],{"class":166},[160,215,216],{"class":170}," detail",[160,218,174],{"class":166},[160,220,178],{"class":177},[160,222,182],{"class":181},[160,224,225],{"class":205},"'...'",[160,227,188],{"class":181},[160,229,231],{"class":162,"line":230},4,[160,232,234],{"emptyLinePlaceholder":233},true,"\n",[160,236,238,240,243,245,248,251,254],{"class":162,"line":237},5,[160,239,167],{"class":166},[160,241,242],{"class":170}," text",[160,244,174],{"class":166},[160,246,247],{"class":177}," computed",[160,249,250],{"class":181},"(() ",[160,252,253],{"class":166},"=>",[160,255,256],{"class":181}," {\n",[160,258,260,263,266,269,272,275],{"class":162,"line":259},6,[160,261,262],{"class":166},"  if",[160,264,265],{"class":181}," (",[160,267,268],{"class":166},"!",[160,270,271],{"class":181},"showDetail.value) ",[160,273,274],{"class":166},"return",[160,276,277],{"class":181}," name.value\n",[160,279,281,284,287,290,293],{"class":162,"line":280},7,[160,282,283],{"class":166},"  return",[160,285,286],{"class":181}," name.value ",[160,288,289],{"class":166},"+",[160,291,292],{"class":181}," detail.value   ",[160,294,296],{"class":295},"sAwPA","\u002F\u002F showDetail 是 false 時，detail 沒被讀到\n",[160,298,300],{"class":162,"line":299},8,[160,301,302],{"class":181},"})\n",[46,304,305,308,309,311,312,315,316,319,320,322,323,326,327,329],{},[119,306,307],{},"showDetail"," 是 ",[119,310,185],{}," 時，改 ",[119,313,314],{},"detail"," 不會讓 ",[119,317,318],{},"text"," 重新計算。等 ",[119,321,307],{}," 變成 ",[119,324,325],{},"true","、重算一次之後，",[119,328,314],{}," 才會被加進追蹤。",[46,331,332,333,336,337,340,341,344,345,348],{},"資料一定要是",[49,334,335],{},"響應式的","（",[119,338,339],{},"ref","、",[119,342,343],{},"reactive","、props、其他 computed）。一般的 ",[119,346,347],{},"let x = 1"," 改了，Vue 不會知道。",[139,350,351],{"id":351},"懶計算和快取",[46,353,354],{},"依賴改了，computed 不會馬上重算，只會被標記成「過期」，等下次有人讀它才真的重算。依賴沒改，讀幾次都是拿快取。這是它和 method 的差別：method 每次呼叫都會重跑。",[139,356,358],{"id":357},"值跟型別無關","「值」跟型別無關",[46,360,361,362,365,366,369],{},"數字、布林、字串、陣列、物件都可以是 computed。重點在兩件事：",[49,363,364],{},"有沒有回傳結果","，以及",[49,367,368],{},"有沒有改到別的東西","。",[151,371,373],{"className":153,"code":372,"language":155,"meta":156,"style":156},"const total = computed(() => cart.value.reduce((s, i) => s + i.price * i.qty, 0))  \u002F\u002F 數字\nconst freeShipping = computed(() => total.value > 1000)                             \u002F\u002F 布林\nconst summary = computed(() => `共 ${cart.value.length} 件，NT$ ${total.value}`)    \u002F\u002F 字串\nconst inStock = computed(() => products.value.filter(p => p.stock > 0))             \u002F\u002F 陣列\n",[119,374,375,437,467,518],{"__ignoreMap":156},[160,376,377,379,382,384,386,388,390,393,396,399,403,406,409,412,414,417,419,422,425,428,431,434],{"class":162,"line":163},[160,378,167],{"class":166},[160,380,381],{"class":170}," total",[160,383,174],{"class":166},[160,385,247],{"class":177},[160,387,250],{"class":181},[160,389,253],{"class":166},[160,391,392],{"class":181}," cart.value.",[160,394,395],{"class":177},"reduce",[160,397,398],{"class":181},"((",[160,400,402],{"class":401},"s9osk","s",[160,404,405],{"class":181},", ",[160,407,408],{"class":401},"i",[160,410,411],{"class":181},") ",[160,413,253],{"class":166},[160,415,416],{"class":181}," s ",[160,418,289],{"class":166},[160,420,421],{"class":181}," i.price ",[160,423,424],{"class":166},"*",[160,426,427],{"class":181}," i.qty, ",[160,429,430],{"class":170},"0",[160,432,433],{"class":181},"))  ",[160,435,436],{"class":295},"\u002F\u002F 數字\n",[160,438,439,441,444,446,448,450,452,455,458,461,464],{"class":162,"line":191},[160,440,167],{"class":166},[160,442,443],{"class":170}," freeShipping",[160,445,174],{"class":166},[160,447,247],{"class":177},[160,449,250],{"class":181},[160,451,253],{"class":166},[160,453,454],{"class":181}," total.value ",[160,456,457],{"class":166},">",[160,459,460],{"class":170}," 1000",[160,462,463],{"class":181},")                             ",[160,465,466],{"class":295},"\u002F\u002F 布林\n",[160,468,469,471,474,476,478,480,482,485,488,491,494,496,499,502,505,507,509,512,515],{"class":162,"line":211},[160,470,167],{"class":166},[160,472,473],{"class":170}," summary",[160,475,174],{"class":166},[160,477,247],{"class":177},[160,479,250],{"class":181},[160,481,253],{"class":166},[160,483,484],{"class":205}," `共 ${",[160,486,487],{"class":181},"cart",[160,489,490],{"class":205},".",[160,492,493],{"class":181},"value",[160,495,490],{"class":205},[160,497,498],{"class":170},"length",[160,500,501],{"class":205},"} 件，NT$ ${",[160,503,504],{"class":181},"total",[160,506,490],{"class":205},[160,508,493],{"class":181},[160,510,511],{"class":205},"}`",[160,513,514],{"class":181},")    ",[160,516,517],{"class":295},"\u002F\u002F 字串\n",[160,519,520,522,525,527,529,531,533,536,539,541,543,546,549,551,554,557],{"class":162,"line":230},[160,521,167],{"class":166},[160,523,524],{"class":170}," inStock",[160,526,174],{"class":166},[160,528,247],{"class":177},[160,530,250],{"class":181},[160,532,253],{"class":166},[160,534,535],{"class":181}," products.value.",[160,537,538],{"class":177},"filter",[160,540,182],{"class":181},[160,542,46],{"class":401},[160,544,545],{"class":166}," =>",[160,547,548],{"class":181}," p.stock ",[160,550,457],{"class":166},[160,552,553],{"class":170}," 0",[160,555,556],{"class":181},"))             ",[160,558,559],{"class":295},"\u002F\u002F 陣列\n",[46,561,562,563,566],{},"常見的陷阱是 ",[119,564,565],{},"sort()","，它會直接改原本的陣列，回傳的型別看起來沒問題，但已經是在 computed 裡「做事」了：",[151,568,570],{"className":153,"code":569,"language":155,"meta":156,"style":156},"\u002F\u002F ❌ 改到了 products\nconst sorted = computed(() => products.value.sort((a, b) => a.price - b.price))\n\n\u002F\u002F ✅ 回傳新陣列\nconst sorted = computed(() => products.value.toSorted((a, b) => a.price - b.price))\n",[119,571,572,577,620,624,629],{"__ignoreMap":156},[160,573,574],{"class":162,"line":163},[160,575,576],{"class":295},"\u002F\u002F ❌ 改到了 products\n",[160,578,579,581,584,586,588,590,592,594,597,599,602,604,607,609,611,614,617],{"class":162,"line":191},[160,580,167],{"class":166},[160,582,583],{"class":170}," sorted",[160,585,174],{"class":166},[160,587,247],{"class":177},[160,589,250],{"class":181},[160,591,253],{"class":166},[160,593,535],{"class":181},[160,595,596],{"class":177},"sort",[160,598,398],{"class":181},[160,600,601],{"class":401},"a",[160,603,405],{"class":181},[160,605,606],{"class":401},"b",[160,608,411],{"class":181},[160,610,253],{"class":166},[160,612,613],{"class":181}," a.price ",[160,615,616],{"class":166},"-",[160,618,619],{"class":181}," b.price))\n",[160,621,622],{"class":162,"line":211},[160,623,234],{"emptyLinePlaceholder":233},[160,625,626],{"class":162,"line":230},[160,627,628],{"class":295},"\u002F\u002F ✅ 回傳新陣列\n",[160,630,631,633,635,637,639,641,643,645,648,650,652,654,656,658,660,662,664],{"class":162,"line":237},[160,632,167],{"class":166},[160,634,583],{"class":170},[160,636,174],{"class":166},[160,638,247],{"class":177},[160,640,250],{"class":181},[160,642,253],{"class":166},[160,644,535],{"class":181},[160,646,647],{"class":177},"toSorted",[160,649,398],{"class":181},[160,651,601],{"class":401},[160,653,405],{"class":181},[160,655,606],{"class":401},[160,657,411],{"class":181},[160,659,253],{"class":166},[160,661,613],{"class":181},[160,663,616],{"class":166},[160,665,619],{"class":181},[10,667,669],{"id":668},"_3-watch值變了之後去做一件事","3. watch：值變了之後去做一件事",[46,671,672,673,676,677,680],{},"watch 處理的通常是 ",[49,674,675],{},"Vue 管不到的地方","：API、localStorage、網址、計時器、分析事件、",[119,678,679],{},"focus()"," 和捲動、第三方套件（地圖、圖表）。",[151,682,684],{"className":153,"code":683,"language":155,"meta":156,"style":156},"const keyword = ref('')\n\nwatch(keyword, async (val) => {\n  result.value = await fetchSearch(val)\n})\n",[119,685,686,704,708,729,746],{"__ignoreMap":156},[160,687,688,690,693,695,697,699,702],{"class":162,"line":163},[160,689,167],{"class":166},[160,691,692],{"class":170}," keyword",[160,694,174],{"class":166},[160,696,178],{"class":177},[160,698,182],{"class":181},[160,700,701],{"class":205},"''",[160,703,188],{"class":181},[160,705,706],{"class":162,"line":191},[160,707,234],{"emptyLinePlaceholder":233},[160,709,710,712,715,718,720,723,725,727],{"class":162,"line":211},[160,711,70],{"class":177},[160,713,714],{"class":181},"(keyword, ",[160,716,717],{"class":166},"async",[160,719,265],{"class":181},[160,721,722],{"class":401},"val",[160,724,411],{"class":181},[160,726,253],{"class":166},[160,728,256],{"class":181},[160,730,731,734,737,740,743],{"class":162,"line":230},[160,732,733],{"class":181},"  result.value ",[160,735,736],{"class":166},"=",[160,738,739],{"class":166}," await",[160,741,742],{"class":177}," fetchSearch",[160,744,745],{"class":181},"(val)\n",[160,747,748],{"class":162,"line":237},[160,749,302],{"class":181},[46,751,752],{},"這裡沒有比較前後值，還是要用 watch，因為打 API 是副作用。",[139,754,756],{"id":755},"很多時候連-watch-都不用","很多時候連 watch 都不用",[46,758,759,760,763],{},"如果值是",[49,761,762],{},"使用者操作改的","，在那個事件裡直接呼叫方法就好：",[151,765,769],{"className":766,"code":767,"language":768,"meta":156,"style":156},"language-html shiki shiki-themes github-dark","\u003Cselect v-model=\"productId\" @change=\"loadProduct\">\n","html",[119,770,771],{"__ignoreMap":156},[160,772,773,776,780,783,785,788,791,793,796],{"class":162,"line":163},[160,774,775],{"class":181},"\u003C",[160,777,779],{"class":778},"s4JwU","select",[160,781,782],{"class":177}," v-model",[160,784,736],{"class":181},[160,786,787],{"class":205},"\"productId\"",[160,789,790],{"class":177}," @change",[160,792,736],{"class":181},[160,794,795],{"class":205},"\"loadProduct\"",[160,797,798],{"class":181},">\n",[46,800,801,802,805],{},"watch 適合用在",[49,803,804],{},"不知道是誰改的、或有很多地方會改","的情況：",[807,808,809,824,827,830],"ul",{},[17,810,811,812,815,816,819,820,823],{},"路由參數變了（",[119,813,814],{},"\u002Fproduct\u002F1"," 連到 ",[119,817,818],{},"\u002Fproduct\u002F2","，Vue Router 會重用同一個元件，",[119,821,822],{},"onMounted"," 不會再跑）",[17,825,826],{},"props 從父元件傳進來",[17,828,829],{},"Pinia store 的狀態被其他元件改",[17,831,832],{},"同一個值有好幾個地方會改，不想每個地方都記得呼叫",[10,834,836],{"id":835},"_4-怎麼判斷要用哪一個","4. 怎麼判斷要用哪一個",[139,838,839],{"id":839},"判斷順序",[14,841,842,847,853,858],{},[17,843,844,845],{},"是算出一個值嗎？→ ",[49,846,67],{},[17,848,849,850],{},"是做一件事，而且觸發點很清楚（點擊、輸入、送出）？→ ",[49,851,852],{},"寫在事件處理函式",[17,854,855,856],{},"是做一件事，但值可能從各種地方被改？→ ",[49,857,70],{},[17,859,860,861],{},"要做的事需要知道「從什麼變成什麼」？→ 用 watch 的 ",[119,862,121],{},[139,864,865],{"id":865},"三個幫助判斷的問法",[46,867,868],{},[49,869,870],{},"換成「是」或「去」的句子",[807,872,873,880],{},[17,874,875,876,879],{},"「免運提示",[49,877,878],{},"是","：總金額大於 1000 時顯示」→ 描述狀態，computed",[17,881,882,883,886],{},"「總金額變了，",[49,884,885],{},"去","送分析事件」→ 動作，watch",[46,888,889],{},[49,890,891],{},"拿掉畫面，這件事還要發生嗎？",[807,893,894,897],{},[17,895,896],{},"免運提示：沒有畫面就沒意義，computed",[17,898,899],{},"分析事件：沒有畫面，伺服器還是要收到，watch",[46,901,902],{},[49,903,904],{},"這件事碰到外部世界了嗎？",[46,906,907],{},"和畫面有關的幾乎都是 computed，碰到外部世界的才是 watch。",[46,909,910,911,914,915,369],{},"在 jQuery 或原生 JS 裡，「顯示」真的是一件事，要自己找元素、改 ",[119,912,913],{},"display","。Vue 把這件事接手了，我們只描述狀態，畫面由 Vue 更新。所以在 Vue 裡，",[49,916,917],{},"畫面上的東西幾乎都不算「做事」",[10,919,921],{"id":920},"_5-寫法","5. 寫法",[46,923,924,925,369],{},"以下是 Vue 3 的 ",[119,926,927],{},"\u003Cscript setup>",[139,929,67],{"id":67},[151,931,933],{"className":153,"code":932,"language":155,"meta":156,"style":156},"import { computed } from 'vue'\n\nconst total = computed(() => price.value * qty.value)\n\nconsole.log(total.value)  \u002F\u002F script 裡要加 .value，template 裡不用\n",[119,934,935,949,953,975,979],{"__ignoreMap":156},[160,936,937,940,943,946],{"class":162,"line":163},[160,938,939],{"class":166},"import",[160,941,942],{"class":181}," { computed } ",[160,944,945],{"class":166},"from",[160,947,948],{"class":205}," 'vue'\n",[160,950,951],{"class":162,"line":191},[160,952,234],{"emptyLinePlaceholder":233},[160,954,955,957,959,961,963,965,967,970,972],{"class":162,"line":211},[160,956,167],{"class":166},[160,958,381],{"class":170},[160,960,174],{"class":166},[160,962,247],{"class":177},[160,964,250],{"class":181},[160,966,253],{"class":166},[160,968,969],{"class":181}," price.value ",[160,971,424],{"class":166},[160,973,974],{"class":181}," qty.value)\n",[160,976,977],{"class":162,"line":230},[160,978,234],{"emptyLinePlaceholder":233},[160,980,981,984,987,990],{"class":162,"line":237},[160,982,983],{"class":181},"console.",[160,985,986],{"class":177},"log",[160,988,989],{"class":181},"(total.value)  ",[160,991,992],{"class":295},"\u002F\u002F script 裡要加 .value，template 裡不用\n",[46,994,995,996,999],{},"可寫的 computed（常用在 ",[119,997,998],{},"v-model"," 綁定 props）：",[151,1001,1003],{"className":153,"code":1002,"language":155,"meta":156,"style":156},"const fullName = computed({\n  get: () => `${firstName.value} ${lastName.value}`,\n  set: (val) => {\n    [firstName.value, lastName.value] = val.split(' ')\n  },\n})\n",[119,1004,1005,1019,1054,1070,1090,1095],{"__ignoreMap":156},[160,1006,1007,1009,1012,1014,1016],{"class":162,"line":163},[160,1008,167],{"class":166},[160,1010,1011],{"class":170}," fullName",[160,1013,174],{"class":166},[160,1015,247],{"class":177},[160,1017,1018],{"class":181},"({\n",[160,1020,1021,1024,1027,1029,1032,1035,1037,1039,1042,1045,1047,1049,1051],{"class":162,"line":191},[160,1022,1023],{"class":177},"  get",[160,1025,1026],{"class":181},": () ",[160,1028,253],{"class":166},[160,1030,1031],{"class":205}," `${",[160,1033,1034],{"class":181},"firstName",[160,1036,490],{"class":205},[160,1038,493],{"class":181},[160,1040,1041],{"class":205},"} ${",[160,1043,1044],{"class":181},"lastName",[160,1046,490],{"class":205},[160,1048,493],{"class":181},[160,1050,511],{"class":205},[160,1052,1053],{"class":181},",\n",[160,1055,1056,1059,1062,1064,1066,1068],{"class":162,"line":211},[160,1057,1058],{"class":177},"  set",[160,1060,1061],{"class":181},": (",[160,1063,722],{"class":401},[160,1065,411],{"class":181},[160,1067,253],{"class":166},[160,1069,256],{"class":181},[160,1071,1072,1075,1077,1080,1083,1085,1088],{"class":162,"line":230},[160,1073,1074],{"class":181},"    [firstName.value, lastName.value] ",[160,1076,736],{"class":166},[160,1078,1079],{"class":181}," val.",[160,1081,1082],{"class":177},"split",[160,1084,182],{"class":181},[160,1086,1087],{"class":205},"' '",[160,1089,188],{"class":181},[160,1091,1092],{"class":162,"line":237},[160,1093,1094],{"class":181},"  },\n",[160,1096,1097],{"class":162,"line":259},[160,1098,302],{"class":181},[139,1100,70],{"id":70},[151,1102,1104],{"className":153,"code":1103,"language":155,"meta":156,"style":156},"import { watch } from 'vue'\n\nwatch(要看的東西, (新值, 舊值) => {\n  要做的事\n}, { 選項 })\n",[119,1105,1106,1117,1121,1141,1146],{"__ignoreMap":156},[160,1107,1108,1110,1113,1115],{"class":162,"line":163},[160,1109,939],{"class":166},[160,1111,1112],{"class":181}," { watch } ",[160,1114,945],{"class":166},[160,1116,948],{"class":205},[160,1118,1119],{"class":162,"line":191},[160,1120,234],{"emptyLinePlaceholder":233},[160,1122,1123,1125,1128,1131,1133,1135,1137,1139],{"class":162,"line":211},[160,1124,70],{"class":177},[160,1126,1127],{"class":181},"(要看的東西, (",[160,1129,1130],{"class":401},"新值",[160,1132,405],{"class":181},[160,1134,112],{"class":401},[160,1136,411],{"class":181},[160,1138,253],{"class":166},[160,1140,256],{"class":181},[160,1142,1143],{"class":162,"line":230},[160,1144,1145],{"class":181},"  要做的事\n",[160,1147,1148],{"class":162,"line":237},[160,1149,1150],{"class":181},"}, { 選項 })\n",[46,1152,1153,1154,1157],{},"第一個參數最容易寫錯。",[49,1155,1156],{},"傳「會變的那個東西」，不要傳「它現在的值」","：",[151,1159,1161],{"className":153,"code":1160,"language":155,"meta":156,"style":156},"const count = ref(0)\nconst user = reactive({ name: 'Jason', age: 30 })\n\nwatch(count, ...)                    \u002F\u002F ✅ ref 直接傳，不加 .value\nwatch(user, ...)                     \u002F\u002F ✅ reactive 物件直接傳，自動 deep\nwatch(() => user.age, ...)           \u002F\u002F ✅ 物件裡的屬性要包成函式\nwatch([count, () => user.age], ...)  \u002F\u002F ✅ 看好幾個用陣列\n\nwatch(count.value, ...)              \u002F\u002F ❌ 傳進去的是數字 0\nwatch(user.age, ...)                 \u002F\u002F ❌ 傳進去的是數字 30\n",[119,1162,1163,1180,1206,1210,1226,1241,1260,1280,1284,1300],{"__ignoreMap":156},[160,1164,1165,1167,1170,1172,1174,1176,1178],{"class":162,"line":163},[160,1166,167],{"class":166},[160,1168,1169],{"class":170}," count",[160,1171,174],{"class":166},[160,1173,178],{"class":177},[160,1175,182],{"class":181},[160,1177,430],{"class":170},[160,1179,188],{"class":181},[160,1181,1182,1184,1187,1189,1192,1195,1197,1200,1203],{"class":162,"line":191},[160,1183,167],{"class":166},[160,1185,1186],{"class":170}," user",[160,1188,174],{"class":166},[160,1190,1191],{"class":177}," reactive",[160,1193,1194],{"class":181},"({ name: ",[160,1196,206],{"class":205},[160,1198,1199],{"class":181},", age: ",[160,1201,1202],{"class":170},"30",[160,1204,1205],{"class":181}," })\n",[160,1207,1208],{"class":162,"line":211},[160,1209,234],{"emptyLinePlaceholder":233},[160,1211,1212,1214,1217,1220,1223],{"class":162,"line":230},[160,1213,70],{"class":177},[160,1215,1216],{"class":181},"(count, ",[160,1218,1219],{"class":166},"...",[160,1221,1222],{"class":181},")                    ",[160,1224,1225],{"class":295},"\u002F\u002F ✅ ref 直接傳，不加 .value\n",[160,1227,1228,1230,1233,1235,1238],{"class":162,"line":237},[160,1229,70],{"class":177},[160,1231,1232],{"class":181},"(user, ",[160,1234,1219],{"class":166},[160,1236,1237],{"class":181},")                     ",[160,1239,1240],{"class":295},"\u002F\u002F ✅ reactive 物件直接傳，自動 deep\n",[160,1242,1243,1245,1247,1249,1252,1254,1257],{"class":162,"line":259},[160,1244,70],{"class":177},[160,1246,250],{"class":181},[160,1248,253],{"class":166},[160,1250,1251],{"class":181}," user.age, ",[160,1253,1219],{"class":166},[160,1255,1256],{"class":181},")           ",[160,1258,1259],{"class":295},"\u002F\u002F ✅ 物件裡的屬性要包成函式\n",[160,1261,1262,1264,1267,1269,1272,1274,1277],{"class":162,"line":280},[160,1263,70],{"class":177},[160,1265,1266],{"class":181},"([count, () ",[160,1268,253],{"class":166},[160,1270,1271],{"class":181}," user.age], ",[160,1273,1219],{"class":166},[160,1275,1276],{"class":181},")  ",[160,1278,1279],{"class":295},"\u002F\u002F ✅ 看好幾個用陣列\n",[160,1281,1282],{"class":162,"line":299},[160,1283,234],{"emptyLinePlaceholder":233},[160,1285,1287,1289,1292,1294,1297],{"class":162,"line":1286},9,[160,1288,70],{"class":177},[160,1290,1291],{"class":181},"(count.value, ",[160,1293,1219],{"class":166},[160,1295,1296],{"class":181},")              ",[160,1298,1299],{"class":295},"\u002F\u002F ❌ 傳進去的是數字 0\n",[160,1301,1303,1305,1308,1310,1313],{"class":162,"line":1302},10,[160,1304,70],{"class":177},[160,1306,1307],{"class":181},"(user.age, ",[160,1309,1219],{"class":166},[160,1311,1312],{"class":181},")                 ",[160,1314,1315],{"class":295},"\u002F\u002F ❌ 傳進去的是數字 30\n",[46,1317,1318,1321,1322,1325],{},[119,1319,1320],{},"user.age"," 一讀出來就是固定的數字，包成 ",[119,1323,1324],{},"() => user.age"," 才能讓 Vue 每次重讀。",[46,1327,1328],{},"選項：",[53,1330,1331,1341],{},[56,1332,1333],{},[59,1334,1335,1338],{},[62,1336,1337],{},"選項",[62,1339,1340],{},"作用",[72,1342,1343,1356,1369],{},[59,1344,1345,1350],{},[77,1346,1347],{},[119,1348,1349],{},"immediate: true",[77,1351,1352,1353],{},"建立時先跑一次，這時舊值是 ",[119,1354,1355],{},"undefined",[59,1357,1358,1363],{},[77,1359,1360],{},[119,1361,1362],{},"deep: true",[77,1364,1365,1366,1368],{},"看物件內部的變化。直接 watch ",[119,1367,343],{}," 物件預設就是 deep，watch 一個回傳物件的 getter，只有整個物件被換掉才會觸發",[59,1370,1371,1376],{},[77,1372,1373],{},[119,1374,1375],{},"once: true",[77,1377,1378],{},"只觸發一次（Vue 3.4 起）",[139,1380,1382],{"id":1381},"watcheffect","watchEffect",[151,1384,1386],{"className":153,"code":1385,"language":155,"meta":156,"style":156},"import { watchEffect } from 'vue'\n\nwatchEffect(() => {\n  localStorage.setItem('dark', String(dark.value))\n})\n",[119,1387,1388,1399,1403,1413,1434],{"__ignoreMap":156},[160,1389,1390,1392,1395,1397],{"class":162,"line":163},[160,1391,939],{"class":166},[160,1393,1394],{"class":181}," { watchEffect } ",[160,1396,945],{"class":166},[160,1398,948],{"class":205},[160,1400,1401],{"class":162,"line":191},[160,1402,234],{"emptyLinePlaceholder":233},[160,1404,1405,1407,1409,1411],{"class":162,"line":211},[160,1406,1382],{"class":177},[160,1408,250],{"class":181},[160,1410,253],{"class":166},[160,1412,256],{"class":181},[160,1414,1415,1418,1421,1423,1426,1428,1431],{"class":162,"line":230},[160,1416,1417],{"class":181},"  localStorage.",[160,1419,1420],{"class":177},"setItem",[160,1422,182],{"class":181},[160,1424,1425],{"class":205},"'dark'",[160,1427,405],{"class":181},[160,1429,1430],{"class":177},"String",[160,1432,1433],{"class":181},"(dark.value))\n",[160,1435,1436],{"class":162,"line":237},[160,1437,302],{"class":181},[46,1439,1440],{},"像 computed 一樣自動追蹤讀到的資料，但用途像 watch，是做副作用的。建立時會先跑一次，拿不到舊值。",[10,1442,1444],{"id":1443},"_6-例子購物車的免運門檻","6. 例子：購物車的免運門檻",[46,1446,1447],{},"需求：顯示總金額，超過 1000 時顯示「已達免運門檻」，並送一次分析事件。",[151,1449,1451],{"className":153,"code":1450,"language":155,"meta":156,"style":156},"const total = computed(() =>\n  cart.value.reduce((sum, item) => sum + item.price * item.qty, 0)\n)\nconst freeShipping = computed(() => total.value > 1000)\n\nwatch(freeShipping, (reached) => {\n  if (reached) sendEvent('free_shipping_reached')\n})\n",[119,1452,1453,1468,1506,1510,1532,1536,1552,1569],{"__ignoreMap":156},[160,1454,1455,1457,1459,1461,1463,1465],{"class":162,"line":163},[160,1456,167],{"class":166},[160,1458,381],{"class":170},[160,1460,174],{"class":166},[160,1462,247],{"class":177},[160,1464,250],{"class":181},[160,1466,1467],{"class":166},"=>\n",[160,1469,1470,1473,1475,1477,1480,1482,1485,1487,1489,1492,1494,1497,1499,1502,1504],{"class":162,"line":191},[160,1471,1472],{"class":181},"  cart.value.",[160,1474,395],{"class":177},[160,1476,398],{"class":181},[160,1478,1479],{"class":401},"sum",[160,1481,405],{"class":181},[160,1483,1484],{"class":401},"item",[160,1486,411],{"class":181},[160,1488,253],{"class":166},[160,1490,1491],{"class":181}," sum ",[160,1493,289],{"class":166},[160,1495,1496],{"class":181}," item.price ",[160,1498,424],{"class":166},[160,1500,1501],{"class":181}," item.qty, ",[160,1503,430],{"class":170},[160,1505,188],{"class":181},[160,1507,1508],{"class":162,"line":211},[160,1509,188],{"class":181},[160,1511,1512,1514,1516,1518,1520,1522,1524,1526,1528,1530],{"class":162,"line":230},[160,1513,167],{"class":166},[160,1515,443],{"class":170},[160,1517,174],{"class":166},[160,1519,247],{"class":177},[160,1521,250],{"class":181},[160,1523,253],{"class":166},[160,1525,454],{"class":181},[160,1527,457],{"class":166},[160,1529,460],{"class":170},[160,1531,188],{"class":181},[160,1533,1534],{"class":162,"line":237},[160,1535,234],{"emptyLinePlaceholder":233},[160,1537,1538,1540,1543,1546,1548,1550],{"class":162,"line":259},[160,1539,70],{"class":177},[160,1541,1542],{"class":181},"(freeShipping, (",[160,1544,1545],{"class":401},"reached",[160,1547,411],{"class":181},[160,1549,253],{"class":166},[160,1551,256],{"class":181},[160,1553,1554,1556,1559,1562,1564,1567],{"class":162,"line":280},[160,1555,262],{"class":166},[160,1557,1558],{"class":181}," (reached) ",[160,1560,1561],{"class":177},"sendEvent",[160,1563,182],{"class":181},[160,1565,1566],{"class":205},"'free_shipping_reached'",[160,1568,188],{"class":181},[160,1570,1571],{"class":162,"line":299},[160,1572,302],{"class":181},[151,1574,1576],{"className":766,"code":1575,"language":768,"meta":156,"style":156},"\u003Cp v-if=\"freeShipping\">已達免運門檻\u003C\u002Fp>\n",[119,1577,1578],{"__ignoreMap":156},[160,1579,1580,1582,1584,1587,1589,1592,1595,1597],{"class":162,"line":163},[160,1581,775],{"class":181},[160,1583,46],{"class":778},[160,1585,1586],{"class":177}," v-if",[160,1588,736],{"class":181},[160,1590,1591],{"class":205},"\"freeShipping\"",[160,1593,1594],{"class":181},">已達免運門檻\u003C\u002F",[160,1596,46],{"class":778},[160,1598,798],{"class":181},[53,1600,1601,1614],{},[56,1602,1603],{},[59,1604,1605,1608,1611],{},[62,1606,1607],{},"需求",[62,1609,1610],{},"用什麼",[62,1612,1613],{},"原因",[72,1615,1616,1626,1640],{},[59,1617,1618,1621,1623],{},[77,1619,1620],{},"總金額",[77,1622,67],{},[77,1624,1625],{},"從購物車算出的值",[59,1627,1628,1631,1637],{},[77,1629,1630],{},"顯示免運提示",[77,1632,1633,1634],{},"computed 加 ",[119,1635,1636],{},"v-if",[77,1638,1639],{},"只是一個布林值，金額降回 1000 以下時會自動消失",[59,1641,1642,1645,1647],{},[77,1643,1644],{},"送分析事件",[77,1646,70],{},[77,1648,1649],{},"副作用",[46,1651,1652,1653,1656,1657,1659,1660,1663,1664,1667,1668,1670,1671,1673],{},"送事件要看的是",[49,1654,1655],{},"跨過門檻的那一刻","。如果直接 watch ",[119,1658,504],{}," 再判斷 ",[119,1661,1662],{},"val > 1000","，超過之後每改一次數量就會再送一次。watch ",[119,1665,1666],{},"freeShipping"," 只有在 ",[119,1669,325],{}," 和 ",[119,1672,185],{}," 之間切換時才觸發，就沒有這個問題。",[46,1675,1676,1677,369],{},"這也是 computed 和 watch 搭配的方式：",[49,1678,1679],{},"computed 把狀態整理好，watch 只負責在狀態改變時做事",[10,1681,1683],{"id":1682},"_7-我原本的誤解","7. 我原本的誤解",[53,1685,1686,1696],{},[56,1687,1688],{},[59,1689,1690,1693],{},[62,1691,1692],{},"我原本以為",[62,1694,1695],{},"實際上",[72,1697,1698,1706,1714,1722,1730],{},[59,1699,1700,1703],{},[77,1701,1702],{},"computed 會在「列在函數裡的變數」改了之後重算",[77,1704,1705],{},"追蹤的是執行時實際讀到的響應式資料，沒走到的分支不算",[59,1707,1708,1711],{},[77,1709,1710],{},"顯示「已達免運門檻」是一件事，要用 watch",[77,1712,1713],{},"它只是根據總金額算出的布林值，用 computed",[59,1715,1716,1719],{},[77,1717,1718],{},"有沒有要比較前後值，是選 watch 的依據",[77,1720,1721],{},"依據是有沒有副作用。不比較前後值也可能要用 watch（打 API），3.4 起 computed 也拿得到上一次的值",[59,1723,1724,1727],{},[77,1725,1726],{},"變數改了要呼叫方法，就只能用 watch",[77,1728,1729],{},"方法只是在算東西，放在 computed 裡沒問題。觸發點清楚的話，直接寫在事件裡",[59,1731,1732,1735],{},[77,1733,1734],{},"「算出一個值」是指數字、布林這類基本型別",[77,1736,1737],{},"跟型別無關，陣列、物件都可以。重點是有沒有改到別的東西",[10,1739,1741],{"id":1740},"_8-練習","8. 練習",[139,1743,1744],{"id":1744},"分類",[14,1746,1747,1750,1753,1756,1759,1762],{},[17,1748,1749],{},"輸入框為空時，送出按鈕變灰",[17,1751,1752],{},"使用者切換深色模式後，記住設定，下次打開還是深色",[17,1754,1755],{},"顯示「共 N 筆，已選 M 筆」",[17,1757,1758],{},"切換到另一個商品 ID 時，載入那個商品的資料",[17,1760,1761],{},"密碼長度不到 8 碼時，顯示紅色的提示",[17,1763,1764,1765],{},"搜尋關鍵字改變時，網址跟著變成 ",[119,1766,1767],{},"?q=關鍵字",[139,1769,1771],{"id":1770},"找錯下面有-3-個錯誤","找錯：下面有 3 個錯誤",[151,1773,1775],{"className":153,"code":1774,"language":155,"meta":156,"style":156},"const form = reactive({ email: '', password: '' })\nconst keyword = ref('')\n\nconst isValid = computed(() => {\n  form.password.length >= 8\n})\n\nwatch(form.email, (val) => {\n  console.log('email 改了', val)\n})\n\nwatch(keyword.value, (val) => {\n  fetchSearch(val)\n})\n",[119,1776,1777,1800,1816,1820,1837,1850,1854,1858,1873,1888,1892,1897,1913,1921],{"__ignoreMap":156},[160,1778,1779,1781,1784,1786,1788,1791,1793,1796,1798],{"class":162,"line":163},[160,1780,167],{"class":166},[160,1782,1783],{"class":170}," form",[160,1785,174],{"class":166},[160,1787,1191],{"class":177},[160,1789,1790],{"class":181},"({ email: ",[160,1792,701],{"class":205},[160,1794,1795],{"class":181},", password: ",[160,1797,701],{"class":205},[160,1799,1205],{"class":181},[160,1801,1802,1804,1806,1808,1810,1812,1814],{"class":162,"line":191},[160,1803,167],{"class":166},[160,1805,692],{"class":170},[160,1807,174],{"class":166},[160,1809,178],{"class":177},[160,1811,182],{"class":181},[160,1813,701],{"class":205},[160,1815,188],{"class":181},[160,1817,1818],{"class":162,"line":211},[160,1819,234],{"emptyLinePlaceholder":233},[160,1821,1822,1824,1827,1829,1831,1833,1835],{"class":162,"line":230},[160,1823,167],{"class":166},[160,1825,1826],{"class":170}," isValid",[160,1828,174],{"class":166},[160,1830,247],{"class":177},[160,1832,250],{"class":181},[160,1834,253],{"class":166},[160,1836,256],{"class":181},[160,1838,1839,1842,1844,1847],{"class":162,"line":237},[160,1840,1841],{"class":181},"  form.password.",[160,1843,498],{"class":170},[160,1845,1846],{"class":166}," >=",[160,1848,1849],{"class":170}," 8\n",[160,1851,1852],{"class":162,"line":259},[160,1853,302],{"class":181},[160,1855,1856],{"class":162,"line":280},[160,1857,234],{"emptyLinePlaceholder":233},[160,1859,1860,1862,1865,1867,1869,1871],{"class":162,"line":299},[160,1861,70],{"class":177},[160,1863,1864],{"class":181},"(form.email, (",[160,1866,722],{"class":401},[160,1868,411],{"class":181},[160,1870,253],{"class":166},[160,1872,256],{"class":181},[160,1874,1875,1878,1880,1882,1885],{"class":162,"line":1286},[160,1876,1877],{"class":181},"  console.",[160,1879,986],{"class":177},[160,1881,182],{"class":181},[160,1883,1884],{"class":205},"'email 改了'",[160,1886,1887],{"class":181},", val)\n",[160,1889,1890],{"class":162,"line":1302},[160,1891,302],{"class":181},[160,1893,1895],{"class":162,"line":1894},11,[160,1896,234],{"emptyLinePlaceholder":233},[160,1898,1900,1902,1905,1907,1909,1911],{"class":162,"line":1899},12,[160,1901,70],{"class":177},[160,1903,1904],{"class":181},"(keyword.value, (",[160,1906,722],{"class":401},[160,1908,411],{"class":181},[160,1910,253],{"class":166},[160,1912,256],{"class":181},[160,1914,1916,1919],{"class":162,"line":1915},13,[160,1917,1918],{"class":177},"  fetchSearch",[160,1920,745],{"class":181},[160,1922,1924],{"class":162,"line":1923},14,[160,1925,302],{"class":181},[139,1927,1928],{"id":1928},"答案",[46,1930,1931],{},[49,1932,1744],{},[14,1934,1935,1945,1955,1958,1972,1978],{},[17,1936,1937,1938,1941,1942],{},"computed。記得先 ",[119,1939,1940],{},"trim()","，只輸入空白也算空：",[119,1943,1944],{},"computed(() => text.value.trim().length > 0)",[17,1946,1947,1948,1951,1952],{},"watch（寫 localStorage）。也可以寫在切換鈕的 ",[119,1949,1950],{},"@click","，但設定可能從好幾個地方被改，用 watch 比較不會漏。另外，啟動時要把設定讀回來：",[119,1953,1954],{},"ref(localStorage.getItem('dark') === 'true')",[17,1956,1957],{},"computed（組字串）",[17,1959,1960,1961,1964,1965,1968,1969],{},"看是誰改的。下拉選單切換，用 ",[119,1962,1963],{},"@change","。網址 ",[119,1966,1967],{},"\u002Fproduct\u002F:id"," 切換，用 ",[119,1970,1971],{},"watch(() => route.params.id, loadProduct, { immediate: true })",[17,1973,1974,1975,1977],{},"computed（布林值，加上 ",[119,1976,1636],{}," 或 class）",[17,1979,1980,1981,1984],{},"watch（改網址）。實務上會加 debounce，並用 ",[119,1982,1983],{},"router.replace"," 避免塞滿瀏覽紀錄",[46,1986,1987],{},[49,1988,1989],{},"找錯",[14,1991,1992,2003,2012],{},[17,1993,1994,1997,1998,2000,2001],{},[119,1995,1996],{},"isValid"," 的函式用了大括號卻沒有 ",[119,1999,274],{},"，結果永遠是 ",[119,2002,1355],{},[17,2004,2005,2008,2009],{},[119,2006,2007],{},"watch(form.email, ...)"," 傳進去的是字串，要改成 ",[119,2010,2011],{},"() => form.email",[17,2013,2014,2008,2017],{},[119,2015,2016],{},"watch(keyword.value, ...)",[119,2018,2019],{},"keyword",[2021,2022,2023],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}",{"title":156,"searchDepth":191,"depth":191,"links":2025},[2026,2027,2028,2033,2036,2040,2045,2046,2047],{"id":12,"depth":191,"text":12},{"id":43,"depth":191,"text":44},{"id":136,"depth":191,"text":137,"children":2029},[2030,2031,2032],{"id":141,"depth":211,"text":142},{"id":351,"depth":211,"text":351},{"id":357,"depth":211,"text":358},{"id":668,"depth":191,"text":669,"children":2034},[2035],{"id":755,"depth":211,"text":756},{"id":835,"depth":191,"text":836,"children":2037},[2038,2039],{"id":839,"depth":211,"text":839},{"id":865,"depth":211,"text":865},{"id":920,"depth":191,"text":921,"children":2041},[2042,2043,2044],{"id":67,"depth":211,"text":67},{"id":70,"depth":211,"text":70},{"id":1381,"depth":211,"text":1382},{"id":1443,"depth":191,"text":1444},{"id":1682,"depth":191,"text":1683},{"id":1740,"depth":191,"text":1741,"children":2048},[2049,2050,2051],{"id":1744,"depth":211,"text":1744},{"id":1770,"depth":211,"text":1771},{"id":1928,"depth":211,"text":1928},"2026-10-27","computed 從現有資料推導出新的值，watch 在值改變後去做有副作用的事。分不清楚時，先問自己是在「描述狀態」還是在「執行動作」。",false,"md",{},"\u002Fnotes\u002Fvue3-computed-watch",{"title":5,"description":2053},"notes\u002Fvue3-computed-watch",[2061,2062],"Vue","面試準備","iAs6xshsf8MRmhPYTCZ-zAtPzuIKmUq3eyiwF4qA9lI",1793181520358]