[{"data":1,"prerenderedAt":645},["ShallowReactive",2],{"note-\u002Fnotes\u002Fsync\u002Fvue-02-template-syntax":3},{"id":4,"title":5,"body":6,"category":632,"date":633,"description":634,"draft":635,"extension":636,"meta":637,"navigation":197,"path":638,"seo":639,"sourcePath":640,"stem":641,"tags":642,"__hash__":644},"notes\u002Fnotes\u002Fsync\u002Fvue-02-template-syntax.md","Template Syntax",{"type":7,"value":8,"toc":612},"minimark",[9,14,19,32,36,43,46,50,66,68,72,76,101,104,108,114,123,126,130,164,167,176,180,183,224,231,234,238,243,247,273,310,341,345,354,397,401,415,420,460,463,478,501,505,520,549,552,596,599,608],[10,11,13],"h2",{"id":12},"vue-運作的核心機制","Vue 運作的核心機制",[15,16,18],"h3",{"id":17},"編譯為-js-compile","編譯為 JS (Compile)：",[20,21,22,23,27,28],"p",{},"Vue 在建置階段（或執行初期），會在底層把你寫的 ",[24,25,26],"code",{},"\u003Ctemplate>"," 全部轉換成高效率的 ",[29,30,31],"strong",{},"JavaScript 渲染函式 (Render Function)",[15,33,35],{"id":34},"精準打擊-minimal-dom-manipulations","精準打擊 (Minimal DOM manipulations)：",[20,37,38,39,42],{},"搭配 Vue 的虛擬 DOM (Virtual DOM) ，當你的變數改變時，會在記憶體中計算出新舊畫面的「最小差異」，然後",[29,40,41],{},"只去更新真實網頁上那一個微小的節點","。",[20,44,45],{},"=====",[10,47,49],{"id":48},"text-interpolation-文字插植","Text Interpolation (文字插植)",[51,52,57],"pre",{"className":53,"code":54,"language":55,"meta":56,"style":56},"language-javascript shiki shiki-themes github-light github-dark","\u003Cspan>Message: {{ msg }}\u003C\u002Fspan>\n","javascript","",[24,58,59],{"__ignoreMap":56},[60,61,64],"span",{"class":62,"line":63},"line",1,[60,65,54],{},[20,67,45],{},[10,69,71],{"id":70},"raw-html","Raw HTML",[15,73,75],{"id":74},"text-interpolation-文字插值-vs-raw-html-原始-html","Text Interpolation (文字插值) vs Raw HTML (原始 HTML)",[77,78,79,87],"ul",{},[80,81,82,83,86],"li",{},"用文字差值的方式",[24,84,85],{},"{{}}","，可以杜絕===XSS 攻擊===",[80,88,89,90,93,94,97,98,100],{},"永遠只在「你完全信任且能控制」的內容上使用 ",[24,91,92],{},"v-html","（例如你自己公司 CMS 後台編輯器產生的文章）。",[29,95,96],{},"絕對不要","把使用者輸入的內容，直接用 ",[24,99,92],{}," 渲染在畫面上。",[20,102,103],{},"======",[10,105,107],{"id":106},"attribute-bindings-屬性綁定","Attribute Bindings (屬性綁定)",[109,110,111],"blockquote",{},[20,112,113],{},"不能在 HTML 屬性中使用花括號。請改用 v-bind 指令",[51,115,117],{"className":53,"code":116,"language":55,"meta":56,"style":56},"\u003Cdiv v-bind:id=\"dynamicId\">\u003C\u002Fdiv>\n",[24,118,119],{"__ignoreMap":56},[60,120,121],{"class":62,"line":63},[60,122,116],{},[15,124,125],{"id":125},"簡寫",[127,128],"div",{":id":129},"dynamicId",[51,131,133],{"className":53,"code":132,"language":55,"meta":56,"style":56},"const objectOfAttrs = {\n  id: 'container',\n  class: 'wrapper',\n  style: 'background-color:green'\n}\n",[24,134,135,140,146,152,158],{"__ignoreMap":56},[60,136,137],{"class":62,"line":63},[60,138,139],{},"const objectOfAttrs = {\n",[60,141,143],{"class":62,"line":142},2,[60,144,145],{},"  id: 'container',\n",[60,147,149],{"class":62,"line":148},3,[60,150,151],{},"  class: 'wrapper',\n",[60,153,155],{"class":62,"line":154},4,[60,156,157],{},"  style: 'background-color:green'\n",[60,159,161],{"class":62,"line":160},5,[60,162,163],{},"}\n",[20,165,166],{},"搭配",[51,168,170],{"className":53,"code":169,"language":55,"meta":56,"style":56},"\u003Cdiv v-bind=\"objectOfAttrs\">\u003C\u002Fdiv>\n",[24,171,172],{"__ignoreMap":56},[60,173,174],{"class":62,"line":63},[60,175,169],{},[15,177,179],{"id":178},"javascript-表達式-expression","JavaScript 表達式 (Expression)",[20,181,182],{},"文件差值內部可以接受JS的表達式",[51,184,186],{"className":53,"code":185,"language":55,"meta":56,"style":56},"{{ number + 1 }}\n\n{{ ok ? 'YES' : 'NO' }}\n\n{{ message.split('').reverse().join('') }}\n\n\u003Cdiv :id=\"`list-${id}`\">\u003C\u002Fdiv>\n",[24,187,188,193,199,204,208,213,218],{"__ignoreMap":56},[60,189,190],{"class":62,"line":63},[60,191,192],{},"{{ number + 1 }}\n",[60,194,195],{"class":62,"line":142},[60,196,198],{"emptyLinePlaceholder":197},true,"\n",[60,200,201],{"class":62,"line":148},[60,202,203],{},"{{ ok ? 'YES' : 'NO' }}\n",[60,205,206],{"class":62,"line":154},[60,207,198],{"emptyLinePlaceholder":197},[60,209,210],{"class":62,"line":160},[60,211,212],{},"{{ message.split('').reverse().join('') }}\n",[60,214,216],{"class":62,"line":215},6,[60,217,198],{"emptyLinePlaceholder":197},[60,219,221],{"class":62,"line":220},7,[60,222,223],{},"\u003Cdiv :id=\"`list-${id}`\">\u003C\u002Fdiv>\n",[20,225,226,227,230],{},"但是",[29,228,229],{},"不能","放用來「宣告」或「控制流程」的陳述式",[232,233],"hr",{},[10,235,237],{"id":236},"directives","Directives",[109,239,240],{},[20,241,242],{},"帶有==v-==前綴的特殊屬性。",[15,244,246],{"id":245},"arguments","Arguments",[77,248,249,267],{},[80,250,251,252,255,256,259,260,263,264,42],{},"指令 (Directive) 就是 ",[24,253,254],{},"v-"," 開頭的字（例如 ",[24,257,258],{},"v-bind","、",[24,261,262],{},"v-on","）。但是，光說指令是不夠的，Vue 需要知道",[29,265,266],{},"這個指令要作用在誰身上",[80,268,269,272],{},[29,270,271],{},"公式","：指令:參數=\"表達式\"",[51,274,276],{"className":53,"code":275,"language":55,"meta":56,"style":56},"\u003Ca v-bind:href=\"url\">\n\n\u003Ca :href=\"url\">               \u002F\u002F 縮寫\n\n\u003Ca v-on:click=\"doSomething\">\n\n\u003Ca @click=\"doSomething\">      \u002F\u002F 縮寫\n",[24,277,278,283,287,292,296,301,305],{"__ignoreMap":56},[60,279,280],{"class":62,"line":63},[60,281,282],{},"\u003Ca v-bind:href=\"url\">\n",[60,284,285],{"class":62,"line":142},[60,286,198],{"emptyLinePlaceholder":197},[60,288,289],{"class":62,"line":148},[60,290,291],{},"\u003Ca :href=\"url\">               \u002F\u002F 縮寫\n",[60,293,294],{"class":62,"line":154},[60,295,198],{"emptyLinePlaceholder":197},[60,297,298],{"class":62,"line":160},[60,299,300],{},"\u003Ca v-on:click=\"doSomething\">\n",[60,302,303],{"class":62,"line":215},[60,304,198],{"emptyLinePlaceholder":197},[60,306,307],{"class":62,"line":220},[60,308,309],{},"\u003Ca @click=\"doSomething\">      \u002F\u002F 縮寫\n",[109,311,312],{},[20,313,314,318,319,324,325,328,329,331,332,337,338,340],{},[29,315,316],{},[24,317,258],{}," 是指令（告訴 Vue：幫我動態綁定一個屬性）\n",[29,320,321],{},[24,322,323],{},"href"," 就是這段文件所說的 ",[29,326,327],{},"「參數 (Argument)」","（告訴 Vue：我要綁定的是 ",[24,330,323],{}," 這個屬性）\n",[29,333,334],{},[24,335,336],{},"url"," 是表達式（告訴 Vue：這個屬性的值來自於 ",[24,339,336],{}," 這個變數）",[15,342,344],{"id":343},"dynamic-arguments","Dynamic Arguments",[51,346,348],{"className":53,"code":347,"language":55,"meta":56,"style":56},"\u003Ca :[attributeName]=\"url\"> ... \u003C\u002Fa>\n",[24,349,350],{"__ignoreMap":56},[60,351,352],{"class":62,"line":63},[60,353,347],{},[77,355,356,371,374,388],{},[80,357,358,359,362,363,366,367,370],{},"消滅因 ",[29,360,361],{},"『屬性名稱不同"," 或 ",[29,364,365],{},"『事件名稱不同』"," 而產生的 ",[24,368,369],{},"v-if"," 複製貼上地獄。",[80,372,373],{},"有時候要綁定 A 屬性\u002F事件，有時候要綁定 B 屬性\u002F事件",[80,375,376,377,380,381,384,385,42],{},"日常開發 95% 的業務邏輯中，",[29,378,379],{},"真的用不到它","。我們通常都很明確知道一個按鈕要綁定 ",[24,382,383],{},"click","，一個輸入框要綁定 ",[24,386,387],{},"disabled",[80,389,390,391,362,394,42],{},"主要服務於 ",[29,392,393],{},"「開發底層共用元件庫（如 shadcn、ElementUI）」",[29,395,396],{},"「極度複雜的資料驅動架構（Schema-Driven）」",[15,398,400],{"id":399},"情境一開發一個萬用的下拉選單-dropdown","情境一：開發一個萬用的「下拉選單 (Dropdown)」",[20,402,403,404,407,408,411,412,414],{},"假設你要幫公司寫一個共用的 ",[24,405,406],{},"\u003CAppDropdown>"," 元件。 設計師要求：在「電腦版」時，滑鼠移過去（",[24,409,410],{},"mouseenter","）就要展開；在「手機版」時，必須點擊（",[24,413,383],{},"）才會展開。",[416,417,419],"h4",{"id":418},"如果沒有動態參數","如果沒有動態參數：",[51,421,423],{"className":53,"code":422,"language":55,"meta":56,"style":56},"\u003C!-- 逼不得已的寫法：把所有可能的事件都綁定，然後在邏輯裡判斷 -->\n\u003Cdiv \n  @mouseenter=\"isDesktop ? open() : null\"\n  @click=\"isMobile ? open() : null\"\n>\n  點我或滑我\n\u003C\u002Fdiv>\n",[24,424,425,430,435,440,445,450,455],{"__ignoreMap":56},[60,426,427],{"class":62,"line":63},[60,428,429],{},"\u003C!-- 逼不得已的寫法：把所有可能的事件都綁定，然後在邏輯裡判斷 -->\n",[60,431,432],{"class":62,"line":142},[60,433,434],{},"\u003Cdiv \n",[60,436,437],{"class":62,"line":148},[60,438,439],{},"  @mouseenter=\"isDesktop ? open() : null\"\n",[60,441,442],{"class":62,"line":154},[60,443,444],{},"  @click=\"isMobile ? open() : null\"\n",[60,446,447],{"class":62,"line":160},[60,448,449],{},">\n",[60,451,452],{"class":62,"line":215},[60,453,454],{},"  點我或滑我\n",[60,456,457],{"class":62,"line":220},[60,458,459],{},"\u003C\u002Fdiv>\n",[416,461,462],{"id":462},"使用動態參數",[51,464,466],{"className":53,"code":465,"language":55,"meta":56,"style":56},"\u002F\u002F JS 裡判斷：如果是手機就回傳 'click'，否則回傳 'mouseenter'\nconst triggerEvent = computed(() => isMobile.value ? 'click' : 'mouseenter');\n",[24,467,468,473],{"__ignoreMap":56},[60,469,470],{"class":62,"line":63},[60,471,472],{},"\u002F\u002F JS 裡判斷：如果是手機就回傳 'click'，否則回傳 'mouseenter'\n",[60,474,475],{"class":62,"line":142},[60,476,477],{},"const triggerEvent = computed(() => isMobile.value ? 'click' : 'mouseenter');\n",[51,479,481],{"className":53,"code":480,"language":55,"meta":56,"style":56},"\u003C!-- 乾淨俐落！ -->\n\u003Cdiv @[triggerEvent]=\"open()\">\n  點我或滑我\n\u003C\u002Fdiv>\n",[24,482,483,488,493,497],{"__ignoreMap":56},[60,484,485],{"class":62,"line":63},[60,486,487],{},"\u003C!-- 乾淨俐落！ -->\n",[60,489,490],{"class":62,"line":142},[60,491,492],{},"\u003Cdiv @[triggerEvent]=\"open()\">\n",[60,494,495],{"class":62,"line":148},[60,496,454],{},[60,498,499],{"class":62,"line":154},[60,500,459],{},[15,502,504],{"id":503},"情境二後端決定前端的驗證屬性schema-driven","情境二：後端決定前端的驗證屬性（Schema-Driven）",[20,506,507,508,511,512,515,516,519],{},"假設後端傳來一包 JSON，告訴你這個 ",[24,509,510],{},"\u003Cinput>"," 有一個長度或大小的限制。 有時候是限制「字數」（屬性叫 ",[24,513,514],{},"maxlength","），有時候是限制「數字大小」（屬性叫 ",[24,517,518],{},"max","）。",[51,521,523],{"className":53,"code":522,"language":55,"meta":56,"style":56},"\u002F\u002F 後端傳來的設定\nconst fieldRule = {\n  limitType: 'maxlength', \u002F\u002F 下次可能會變成 'max'\n  limitValue: 20\n}\n",[24,524,525,530,535,540,545],{"__ignoreMap":56},[60,526,527],{"class":62,"line":63},[60,528,529],{},"\u002F\u002F 後端傳來的設定\n",[60,531,532],{"class":62,"line":142},[60,533,534],{},"const fieldRule = {\n",[60,536,537],{"class":62,"line":148},[60,538,539],{},"  limitType: 'maxlength', \u002F\u002F 下次可能會變成 'max'\n",[60,541,542],{"class":62,"line":154},[60,543,544],{},"  limitValue: 20\n",[60,546,547],{"class":62,"line":160},[60,548,163],{},[416,550,419],{"id":551},"如果沒有動態參數-1",[51,553,555],{"className":53,"code":554,"language":55,"meta":56,"style":56},"\u003Cinput \n  v-if=\"fieldRule.limitType === 'maxlength'\" \n  :maxlength=\"fieldRule.limitValue\" \n\u002F>\n\u003Cinput \n  v-if=\"fieldRule.limitType === 'max'\" \n  :max=\"fieldRule.limitValue\" \n\u002F>\n",[24,556,557,562,567,572,577,581,586,591],{"__ignoreMap":56},[60,558,559],{"class":62,"line":63},[60,560,561],{},"\u003Cinput \n",[60,563,564],{"class":62,"line":142},[60,565,566],{},"  v-if=\"fieldRule.limitType === 'maxlength'\" \n",[60,568,569],{"class":62,"line":148},[60,570,571],{},"  :maxlength=\"fieldRule.limitValue\" \n",[60,573,574],{"class":62,"line":154},[60,575,576],{},"\u002F>\n",[60,578,579],{"class":62,"line":160},[60,580,561],{},[60,582,583],{"class":62,"line":215},[60,584,585],{},"  v-if=\"fieldRule.limitType === 'max'\" \n",[60,587,588],{"class":62,"line":220},[60,589,590],{},"  :max=\"fieldRule.limitValue\" \n",[60,592,594],{"class":62,"line":593},8,[60,595,576],{},[416,597,462],{"id":598},"使用動態參數-1",[51,600,602],{"className":53,"code":601,"language":55,"meta":56,"style":56},"\u003Cinput :[fieldRule.limitType]=\"fieldRule.limitValue\" \u002F>\n",[24,603,604],{"__ignoreMap":56},[60,605,606],{"class":62,"line":63},[60,607,601],{},[609,610,611],"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":56,"searchDepth":142,"depth":142,"links":613},[614,618,619,622,626],{"id":12,"depth":142,"text":13,"children":615},[616,617],{"id":17,"depth":148,"text":18},{"id":34,"depth":148,"text":35},{"id":48,"depth":142,"text":49},{"id":70,"depth":142,"text":71,"children":620},[621],{"id":74,"depth":148,"text":75},{"id":106,"depth":142,"text":107,"children":623},[624,625],{"id":125,"depth":148,"text":125},{"id":178,"depth":148,"text":179},{"id":236,"depth":142,"text":237,"children":627},[628,629,630,631],{"id":245,"depth":148,"text":246},{"id":343,"depth":148,"text":344},{"id":399,"depth":148,"text":400},{"id":503,"depth":148,"text":504},"frontend","2026-08-05","Vue 在建置階段（或執行初期），會在底層把你寫的 \u003Ctemplate> 全部轉換成高效率的 JavaScript 渲染函式 (Render Function)",false,"md",{},"\u002Fnotes\u002Fsync\u002Fvue-02-template-syntax",{"title":5,"description":634},"Vue\u002F02-Template Syntax.md","notes\u002Fsync\u002Fvue-02-template-syntax",[643],"Vue","iGRmf4BFEQ_HwfCBPLDs8oiPyji4fyKu8_wVbzg1a98",1788767747578]