[{"data":1,"prerenderedAt":539},["ShallowReactive",2],{"note-\u002Fnotes\u002Fsync\u002Fvue":3},{"id":4,"title":5,"body":6,"category":526,"date":527,"description":528,"draft":529,"extension":530,"meta":531,"navigation":50,"path":532,"seo":533,"sourcePath":534,"stem":535,"tags":536,"__hash__":538},"notes\u002Fnotes\u002Fsync\u002Fvue-實務開發.md","實務開發",{"type":7,"value":8,"toc":521},"minimark",[9,13,34,259,306,311,316,325,343,347,366,369,373,387,395,402,411,414,421,436,439,454,457,463,469,473,482,491,498,507,511,517],[10,11,12],"h2",{"id":12},"vue的渲染順序",[14,15,16,17,21,22,25,26,29,30,33],"p",{},"在一個標準的",[18,19,20],"code",{},".vue","檔案中，通常會有三個重要的標籤",[18,23,24],{},"\u003Ctemplate>"," 、",[18,27,28],{},"\u003Cscript>"," 跟 ",[18,31,32],{},"\u003Cstyle>","。那這三者是如何渲染的？",[35,36,41],"pre",{"className":37,"code":38,"language":39,"meta":40,"style":40},"language-javascript shiki shiki-themes github-light github-dark","\n\u003Cscript setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\n\nconsole.log('1. script setup 開始')\n\nconst message = 'hello'  \nconst count = ref(0)\n\nconst double = computed(() => {  \nconsole.log('computed double 計算')  \nreturn count.value * 2  \n})\n\nwatch(count, () => {  \nconsole.log('count changed')  \n})\n\nfunction onClick() {  \nconsole.log('使用者點擊後才執行 onClick')  \ncount.value++  \n}\n\nconsole.log('2. script setup 結束')  \n\u003C\u002Fscript>\n\n\u003Ctemplate> \n    \u003Cdiv> \n        \u003Cp>{{ message }}\u003C\u002Fp> \u003Cp>{{ count }}\u003C\u002Fp>\n        \u003Cp>{{ double }}\u003C\u002Fp>\n        \u003Cbutton @click=\"onClick\">click\u003C\u002Fbutton>\n    \u003C\u002Fdiv> \n\u003C\u002Ftemplate>\n\n\u003Cstyle lang=\"scss\" scoped> \n    \u002F** style *\u002F \n\u003C\u002Fstyle>\n","javascript","",[18,42,43,52,58,64,69,75,80,86,92,97,103,109,115,121,126,132,138,143,148,154,160,166,172,177,183,189,194,200,206,212,218,224,230,236,241,247,253],{"__ignoreMap":40},[44,45,48],"span",{"class":46,"line":47},"line",1,[44,49,51],{"emptyLinePlaceholder":50},true,"\n",[44,53,55],{"class":46,"line":54},2,[44,56,57],{},"\u003Cscript setup lang=\"ts\">\n",[44,59,61],{"class":46,"line":60},3,[44,62,63],{},"import { computed, ref, watch } from 'vue'\n",[44,65,67],{"class":46,"line":66},4,[44,68,51],{"emptyLinePlaceholder":50},[44,70,72],{"class":46,"line":71},5,[44,73,74],{},"console.log('1. script setup 開始')\n",[44,76,78],{"class":46,"line":77},6,[44,79,51],{"emptyLinePlaceholder":50},[44,81,83],{"class":46,"line":82},7,[44,84,85],{},"const message = 'hello'  \n",[44,87,89],{"class":46,"line":88},8,[44,90,91],{},"const count = ref(0)\n",[44,93,95],{"class":46,"line":94},9,[44,96,51],{"emptyLinePlaceholder":50},[44,98,100],{"class":46,"line":99},10,[44,101,102],{},"const double = computed(() => {  \n",[44,104,106],{"class":46,"line":105},11,[44,107,108],{},"console.log('computed double 計算')  \n",[44,110,112],{"class":46,"line":111},12,[44,113,114],{},"return count.value * 2  \n",[44,116,118],{"class":46,"line":117},13,[44,119,120],{},"})\n",[44,122,124],{"class":46,"line":123},14,[44,125,51],{"emptyLinePlaceholder":50},[44,127,129],{"class":46,"line":128},15,[44,130,131],{},"watch(count, () => {  \n",[44,133,135],{"class":46,"line":134},16,[44,136,137],{},"console.log('count changed')  \n",[44,139,141],{"class":46,"line":140},17,[44,142,120],{},[44,144,146],{"class":46,"line":145},18,[44,147,51],{"emptyLinePlaceholder":50},[44,149,151],{"class":46,"line":150},19,[44,152,153],{},"function onClick() {  \n",[44,155,157],{"class":46,"line":156},20,[44,158,159],{},"console.log('使用者點擊後才執行 onClick')  \n",[44,161,163],{"class":46,"line":162},21,[44,164,165],{},"count.value++  \n",[44,167,169],{"class":46,"line":168},22,[44,170,171],{},"}\n",[44,173,175],{"class":46,"line":174},23,[44,176,51],{"emptyLinePlaceholder":50},[44,178,180],{"class":46,"line":179},24,[44,181,182],{},"console.log('2. script setup 結束')  \n",[44,184,186],{"class":46,"line":185},25,[44,187,188],{},"\u003C\u002Fscript>\n",[44,190,192],{"class":46,"line":191},26,[44,193,51],{"emptyLinePlaceholder":50},[44,195,197],{"class":46,"line":196},27,[44,198,199],{},"\u003Ctemplate> \n",[44,201,203],{"class":46,"line":202},28,[44,204,205],{},"    \u003Cdiv> \n",[44,207,209],{"class":46,"line":208},29,[44,210,211],{},"        \u003Cp>{{ message }}\u003C\u002Fp> \u003Cp>{{ count }}\u003C\u002Fp>\n",[44,213,215],{"class":46,"line":214},30,[44,216,217],{},"        \u003Cp>{{ double }}\u003C\u002Fp>\n",[44,219,221],{"class":46,"line":220},31,[44,222,223],{},"        \u003Cbutton @click=\"onClick\">click\u003C\u002Fbutton>\n",[44,225,227],{"class":46,"line":226},32,[44,228,229],{},"    \u003C\u002Fdiv> \n",[44,231,233],{"class":46,"line":232},33,[44,234,235],{},"\u003C\u002Ftemplate>\n",[44,237,239],{"class":46,"line":238},34,[44,240,51],{"emptyLinePlaceholder":50},[44,242,244],{"class":46,"line":243},35,[44,245,246],{},"\u003Cstyle lang=\"scss\" scoped> \n",[44,248,250],{"class":46,"line":249},36,[44,251,252],{},"    \u002F** style *\u002F \n",[44,254,256],{"class":46,"line":255},37,[44,257,258],{},"\u003C\u002Fstyle>\n",[260,261,262,269],"ul",{},[263,264,265,268],"li",{},[18,266,267],{},"\u003Cscript setup>"," 會早於 template render 執行，不是建立template後才將資料塞進去",[263,270,271,272],{},"整個流程會像是：",[273,274,275,280,289,294,297,300,303],"ol",{},[263,276,277,279],{},[18,278,20],{}," 建立 component instance",[263,281,282,283,286,287],{},"執行 ",[18,284,285],{},"setup()","，也就是 ",[18,288,267],{},[263,290,291,293],{},[18,292,285],{}," 回傳或暴露 template 可用的資料、函式",[263,295,296],{},"執行 render function，也就是由 template 編譯出來的函式",[263,298,299],{},"render function 讀取 message \u002F count \u002F onClick",[263,301,302],{},"產生 Virtual DOM",[263,304,305],{},"Virtual DOM patch 到真實 DOM",[307,308,310],"h3",{"id":309},"其他會影響渲染的","其他會影響渲染的：",[312,313,315],"h4",{"id":314},"_1-computed-會影響-render但它是-lazy-的","1. computed 會影響 render，但它是 lazy 的",[35,317,319],{"className":37,"code":318,"language":39,"meta":40,"style":40},"const double = computed(() => count.value * 2)\n",[18,320,321],{"__ignoreMap":40},[44,322,323],{"class":46,"line":47},[44,324,318],{},[14,326,327,328,331,332,335,338,339,342],{},"只有 template 或其他地方讀到 ",[18,329,330],{},"double.value"," 時，它才會計算。",[333,334],"br",{},[18,336,337],{},"count"," 改變後，",[18,340,341],{},"double"," 會被標記為需要更新，下一次 render 讀取時才重新計算。",[312,344,346],{"id":345},"_2-watch-watcheffect-可以在資料變動後執行副作用","2. watch \u002F watchEffect 可以在資料變動後執行副作用",[35,348,350],{"className":37,"code":349,"language":39,"meta":40,"style":40},"watch(count, () => {\n  console.log('count changed')\n})\n",[18,351,352,357,362],{"__ignoreMap":40},[44,353,354],{"class":46,"line":47},[44,355,356],{},"watch(count, () => {\n",[44,358,359],{"class":46,"line":54},[44,360,361],{},"  console.log('count changed')\n",[44,363,364],{"class":46,"line":60},[44,365,120],{},[14,367,368],{},"這種不直接產生畫面，但可能會改其他 state，進而造成下一次 render。",[312,370,372],{"id":371},"_3-async-api-會造成至少兩次畫面狀態","3. async API 會造成至少兩次畫面狀態",[14,374,375,376,379,380,379,383,386],{},"像 ",[18,377,378],{},"useQuery()"," \u002F ",[18,381,382],{},"useAsyncData()",[18,384,385],{},"useFetch()"," 這類查詢通常是：",[35,388,393],{"className":389,"code":391,"language":392},[390],"language-text","執行 \u003Cscript setup>\n↓\n建立 query \u002F async data 狀態\n↓\n第一次 render：API 尚未回來，data 還是初始值\n↓\n畫面先使用初始狀態，例如 list = []、total = 0\n↓\nAPI 回來\n↓\ndata.value 改變\n↓\n依賴 data 的 computed，例如 list \u002F total，被標記為需要更新\n↓\n使用到 list \u002F total 的 render 範圍重新執行\n↓\n產生新的 Virtual DOM\n↓\nVue diff 新舊 Virtual DOM\n↓\n只 patch 真實 DOM 中有差異的部分\n\n","text",[18,394,391],{"__ignoreMap":40},[14,396,397,398,401],{},"所以在 ",[18,399,400],{},"vendor-payment-list"," 裡：",[35,403,405],{"className":37,"code":404,"language":39,"meta":40,"style":40},"const { page, pageSize, list, total, applyFilters, clearFilters } = useVendorPaymentList()\n",[18,406,407],{"__ignoreMap":40},[44,408,409],{"class":46,"line":47},[44,410,404],{},[14,412,413],{},"這行會先建立列表查詢。",[14,415,416,417,420],{},"第一次 render 時，API 可能還沒有回來，因此 ",[18,418,419],{},"useListQuery"," 裡的預設值會先被使用：",[35,422,424],{"className":37,"code":423,"language":39,"meta":40,"style":40},"const list = computed(() => data.value?.data ?? [])\nconst total = computed(() => data.value?.total ?? 0)\n",[18,425,426,431],{"__ignoreMap":40},[44,427,428],{"class":46,"line":47},[44,429,430],{},"const list = computed(() => data.value?.data ?? [])\n",[44,432,433],{"class":46,"line":54},[44,434,435],{},"const total = computed(() => data.value?.total ?? 0)\n",[14,437,438],{},"所以 table 第一次可能收到：",[35,440,442],{"className":37,"code":441,"language":39,"meta":40,"style":40},"list = []\ntotal = 0\n",[18,443,444,449],{"__ignoreMap":40},[44,445,446],{"class":46,"line":47},[44,447,448],{},"list = []\n",[44,450,451],{"class":46,"line":54},[44,452,453],{},"total = 0\n",[14,455,456],{},"等 API 回來後：",[35,458,461],{"className":459,"code":460,"language":392},[390],"data.value 更新\n↓\nlist \u002F total 重新計算\n↓\nvendor-payment-list-table 收到新的 data \u002F total\n↓\n相關 render function 重新執行\n↓\nVue 只更新真實 DOM 中有差異的 table 區域\n",[18,462,460],{"__ignoreMap":40},[464,465,466],"blockquote",{},[14,467,468],{},"要注意的是，這裡的「重新 render」不是整個頁面的 DOM 全部砍掉重建。",[312,470,472],{"id":471},"_4-v-if-v-for-key-會影響-component-是否建立或重建","4. v-if \u002F v-for \u002F key 會影響 component 是否建立或重建",[35,474,476],{"className":37,"code":475,"language":39,"meta":40,"style":40},"\u003Cchild-component v-if=\"visible\" \u002F>\n",[18,477,478],{"__ignoreMap":40},[44,479,480],{"class":46,"line":47},[44,481,475],{},[14,483,484,487,488,490],{},[18,485,486],{},"visible = false"," 時，子元件根本不會建立，也不會執行子元件的 ",[18,489,267],{},"。",[14,492,493,494,497],{},"如果 ",[18,495,496],{},"key"," 改變：",[35,499,501],{"className":37,"code":500,"language":39,"meta":40,"style":40},"\u003Cchild-component :key=\"id\" \u002F>\n",[18,502,503],{"__ignoreMap":40},[44,504,505],{"class":46,"line":47},[44,506,500],{},[312,508,510],{"id":509},"_5-父子元件順序","5. 父子元件順序",[35,512,515],{"className":513,"code":514,"language":392},[390],"父元件 setup\n↓\n父元件 render\n↓\n遇到子元件\n↓\n子元件 setup\n↓\n子元件 render\n",[18,516,514],{"__ignoreMap":40},[518,519,520],"style",{},"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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":40,"searchDepth":54,"depth":54,"links":522},[523],{"id":12,"depth":54,"text":12,"children":524},[525],{"id":309,"depth":60,"text":310},"frontend","2026-08-05","在一個標準的.vue檔案中，通常會有三個重要的標籤\u003Ctemplate> 、\u003Cscript> 跟 \u003Cstyle>。那這三者是如何渲染的？",false,"md",{},"\u002Fnotes\u002Fsync\u002Fvue",{"title":5,"description":528},"Vue\u002F實務開發.md","notes\u002Fsync\u002Fvue-實務開發",[537],"Vue","oCBttA3Pdb5jdxlGEeIzDo1eDffTimr0DFH8qpUwZYY",1788767747613]