[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fproject":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fproject":1728,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fproject":1753},{"source":4,"connector":5,"page":6},"sqlite","better-sqlite3",{"path":7,"title":8,"description":9,"category":10,"tags":11,"date":13,"pinned":14,"draft":14,"body":15,"seo":1724,"stem":1725,"id":1726,"extension":1727},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fproject","工程化实践","SFC 拆分、自定义指令、插件与调试等工程化话题。构建工具见 生态 · Vite。","技能小册",[12],"vue","2021-07-20",false,{"type":16,"value":17,"toc":1714},"minimark",[18,21,31,34,39,47,52,111,133,137,343,347,350,354,357,404,411,518,522,711,715,850,854,861,864,1083,1086,1146,1150,1219,1223,1226,1234,1327,1333,1339,1353,1357,1360,1452,1457,1465,1469,1472,1475,1531,1536,1544,1547,1639,1642,1646,1649,1710],[19,20,8],"h1",{"id":8},[22,23,24,25,30],"p",{},"SFC 拆分、自定义指令、插件与调试等工程化话题。构建工具见 ",[26,27,29],"a",{"href":28},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fecology","生态 · Vite","。",[22,32,33],{},"Vue 的工程化实践涵盖组件设计、自定义指令、插件开发、跨框架复用、全局 API 变更以及调试工具，帮助团队构建可维护、可扩展的大型应用。",[35,36,38],"h3",{"id":37},"一vue-单文件组件sfc的设计模式","一、Vue 单文件组件（SFC）的设计模式",[22,40,41,42,46],{},"SFC 将模板、逻辑、样式聚合在一个 ",[43,44,45],"code",{},".vue"," 文件中，但当组件变得庞大时，需要合理的拆分策略。",[48,49,51],"h4",{"id":50},"_1-大型组件拆分策略","1. 大型组件拆分策略",[53,54,55,74],"table",{},[56,57,58],"thead",{},[59,60,61,65,68,71],"tr",{},[62,63,64],"th",{},"策略",[62,66,67],{},"实现方式",[62,69,70],{},"适用场景",[62,72,73],{},"优缺点",[75,76,77,95],"tbody",{},[59,78,79,86,89,92],{},[80,81,82],"td",{},[83,84,85],"strong",{},"拆分为多个子组件",[80,87,88],{},"将可复用的 UI 片段（表格、弹窗、表单域）抽离为独立组件",[80,90,91],{},"UI 结构复杂、存在明显边界",[80,93,94],{},"✅ 组件职责单一，易于复用；❌ 组件间 props\u002Femit 通信增多",[59,96,97,102,105,108],{},[80,98,99],{},[83,100,101],{},"使用 composable 组织逻辑",[80,103,104],{},"将业务逻辑封装到组合式函数中，SFC 中仅保留模板和简单数据流",[80,106,107],{},"逻辑复杂、UI 相对简单",[80,109,110],{},"✅ 逻辑复用方便，模板轻量；❌ 需理解 composable 模式",[22,112,113,116,117,120,121,124,125,128,129,132],{},[83,114,115],{},"实际建议","：两者常结合使用。例如：",[43,118,119],{},"UserProfile.vue"," 内部拆分为 ",[43,122,123],{},"UserHeader","、",[43,126,127],{},"UserStats"," 子组件，数据获取逻辑则放入 ",[43,130,131],{},"useUserData"," composable。",[48,134,136],{"id":135},"_2-模板限制与解决方案","2. 模板限制与解决方案",[138,139,140,337],"ul",{},[141,142,143,146,147,150,151],"li",{},[83,144,145],{},"避免在模板中写过重逻辑","：如复杂的三元表达式、深层属性访问、数组过滤等应移到",[83,148,149],{},"计算属性","中。",[152,153,157],"pre",{"className":154,"code":155,"language":12,"meta":156,"style":156},"language-vue shiki shiki-themes material-theme-lighter github-light github-dark","\u003Ctemplate>\n  \u003C!-- ❌ 坏 -->\n  \u003Cdiv>{{ user?.address?.city?.toUpperCase() || \"Unknown\" }}\u003C\u002Fdiv>\n  \u003C!-- ✅ 好 -->\n  \u003Cdiv>{{ displayCity }}\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript setup>\nconst displayCity = computed(() => user.value?.address?.city?.toUpperCase() ?? \"Unknown\");\n\u003C\u002Fscript>\n","",[43,158,159,175,182,205,211,229,238,252,328],{"__ignoreMap":156},[160,161,164,168,172],"span",{"class":162,"line":163},"line",1,[160,165,167],{"class":166},"sP7_E","\u003C",[160,169,171],{"class":170},"sQzsp","template",[160,173,174],{"class":166},">\n",[160,176,178],{"class":162,"line":177},2,[160,179,181],{"class":180},"sutJx","  \u003C!-- ❌ 坏 -->\n",[160,183,185,188,191,194,198,201,203],{"class":162,"line":184},3,[160,186,187],{"class":166},"  \u003C",[160,189,190],{"class":170},"div",[160,192,193],{"class":166},">",[160,195,197],{"class":196},"su5hD","{{ user?.address?.city?.toUpperCase() || \"Unknown\" }}",[160,199,200],{"class":166},"\u003C\u002F",[160,202,190],{"class":170},[160,204,174],{"class":166},[160,206,208],{"class":162,"line":207},4,[160,209,210],{"class":180},"  \u003C!-- ✅ 好 -->\n",[160,212,214,216,218,220,223,225,227],{"class":162,"line":213},5,[160,215,187],{"class":166},[160,217,190],{"class":170},[160,219,193],{"class":166},[160,221,222],{"class":196},"{{ displayCity }}",[160,224,200],{"class":166},[160,226,190],{"class":170},[160,228,174],{"class":166},[160,230,232,234,236],{"class":162,"line":231},6,[160,233,200],{"class":166},[160,235,171],{"class":170},[160,237,174],{"class":166},[160,239,241,243,246,250],{"class":162,"line":240},7,[160,242,167],{"class":166},[160,244,245],{"class":170},"script",[160,247,249],{"class":248},"s9AJx"," setup",[160,251,174],{"class":166},[160,253,255,259,263,267,271,274,277,280,283,286,289,292,295,297,300,302,305,308,311,315,319,322,325],{"class":162,"line":254},8,[160,256,258],{"class":257},"sbsja","const",[160,260,262],{"class":261},"s_hVV"," displayCity",[160,264,266],{"class":265},"smGrS"," =",[160,268,270],{"class":269},"sGLFI"," computed",[160,272,273],{"class":196},"(",[160,275,276],{"class":166},"()",[160,278,279],{"class":257}," =>",[160,281,282],{"class":196}," user",[160,284,285],{"class":166},".",[160,287,288],{"class":196},"value",[160,290,291],{"class":166},"?.",[160,293,294],{"class":196},"address",[160,296,291],{"class":166},[160,298,299],{"class":196},"city",[160,301,291],{"class":166},[160,303,304],{"class":269},"toUpperCase",[160,306,307],{"class":196},"() ",[160,309,310],{"class":265},"??",[160,312,314],{"class":313},"sjJ54"," \"",[160,316,318],{"class":317},"s_sjI","Unknown",[160,320,321],{"class":313},"\"",[160,323,324],{"class":196},")",[160,326,327],{"class":166},";\n",[160,329,331,333,335],{"class":162,"line":330},9,[160,332,200],{"class":166},[160,334,245],{"class":170},[160,336,174],{"class":166},[141,338,339,342],{},[83,340,341],{},"模板中避免副作用","：模板内不应调用修改数据的方法，只做展示。",[35,344,346],{"id":345},"二自定义指令","二、自定义指令",[22,348,349],{},"自定义指令允许对 DOM 元素进行底层操作，封装通用 DOM 行为。",[48,351,353],{"id":352},"_1-指令的生命周期钩子","1. 指令的生命周期钩子",[22,355,356],{},"Vue 3 指令钩子（与组件生命周期类似）：",[138,358,359,365,375,380,386,392,398],{},[141,360,361,364],{},[43,362,363],{},"created","：在绑定属性前调用（很少用）。",[141,366,367,370,371,374],{},[43,368,369],{},"beforeMount","：第一次调用 ",[43,372,373],{},"mounted"," 前。",[141,376,377,379],{},[43,378,373],{},"：元素挂载后，可用于操作 DOM。",[141,381,382,385],{},[43,383,384],{},"beforeUpdate","：更新前。",[141,387,388,391],{},[43,389,390],{},"updated","：更新后。",[141,393,394,397],{},[43,395,396],{},"beforeUnmount","：卸载前。",[141,399,400,403],{},[43,401,402],{},"unmounted","：卸载后。",[22,405,406,407,410],{},"常用示例（",[43,408,409],{},"v-focus","）：",[152,412,416],{"className":413,"code":414,"language":415,"meta":156,"style":156},"language-js shiki shiki-themes material-theme-lighter github-light github-dark","const vFocus = {\n  mounted: (el) => el.focus(),\n  updated: (el) => {\n    if (el.shouldFocus) el.focus();\n  },\n};\n","js",[43,417,418,430,462,479,508,513],{"__ignoreMap":156},[160,419,420,422,425,427],{"class":162,"line":163},[160,421,258],{"class":257},[160,423,424],{"class":261}," vFocus",[160,426,266],{"class":265},[160,428,429],{"class":166}," {\n",[160,431,432,435,438,441,445,447,449,452,454,457,459],{"class":162,"line":177},[160,433,434],{"class":269},"  mounted",[160,436,437],{"class":166},":",[160,439,440],{"class":166}," (",[160,442,444],{"class":443},"s99_P","el",[160,446,324],{"class":166},[160,448,279],{"class":257},[160,450,451],{"class":196}," el",[160,453,285],{"class":166},[160,455,456],{"class":269},"focus",[160,458,276],{"class":196},[160,460,461],{"class":166},",\n",[160,463,464,467,469,471,473,475,477],{"class":162,"line":184},[160,465,466],{"class":269},"  updated",[160,468,437],{"class":166},[160,470,440],{"class":166},[160,472,444],{"class":443},[160,474,324],{"class":166},[160,476,279],{"class":257},[160,478,429],{"class":166},[160,480,481,485,488,490,492,495,498,500,502,504,506],{"class":162,"line":207},[160,482,484],{"class":483},"sVHd0","    if",[160,486,440],{"class":487},"skxfh",[160,489,444],{"class":196},[160,491,285],{"class":166},[160,493,494],{"class":196},"shouldFocus",[160,496,497],{"class":487},") ",[160,499,444],{"class":196},[160,501,285],{"class":166},[160,503,456],{"class":269},[160,505,276],{"class":487},[160,507,327],{"class":166},[160,509,510],{"class":162,"line":213},[160,511,512],{"class":166},"  },\n",[160,514,515],{"class":162,"line":231},[160,516,517],{"class":166},"};\n",[48,519,521],{"id":520},"_2-全局指令-vs-局部注册","2. 全局指令 vs 局部注册",[138,523,524,630],{},[141,525,526,529,530,533,534,537,538,541,542],{},[83,527,528],{},"局部注册","：在组件内通过 ",[43,531,532],{},"directives"," 选项（或 ",[43,535,536],{},"\u003Cscript setup>"," 中以 ",[43,539,540],{},"v"," 开头的变量）注册，仅限当前组件。",[152,543,545],{"className":154,"code":544,"language":12,"meta":156,"style":156},"\u003Cscript setup>\nconst vMyDirective = { mounted: (el) => {...} };\n\u003C\u002Fscript>\n\u003Ctemplate>\n  \u003Cinput v-my-directive \u002F>\n\u003C\u002Ftemplate>\n",[43,546,547,557,593,601,609,622],{"__ignoreMap":156},[160,548,549,551,553,555],{"class":162,"line":163},[160,550,167],{"class":166},[160,552,245],{"class":170},[160,554,249],{"class":248},[160,556,174],{"class":166},[160,558,559,561,564,566,569,572,574,576,578,580,582,584,587,590],{"class":162,"line":177},[160,560,258],{"class":257},[160,562,563],{"class":261}," vMyDirective",[160,565,266],{"class":265},[160,567,568],{"class":166}," {",[160,570,571],{"class":269}," mounted",[160,573,437],{"class":166},[160,575,440],{"class":166},[160,577,444],{"class":443},[160,579,324],{"class":166},[160,581,279],{"class":257},[160,583,568],{"class":166},[160,585,586],{"class":265},"...",[160,588,589],{"class":166},"}",[160,591,592],{"class":166}," };\n",[160,594,595,597,599],{"class":162,"line":184},[160,596,200],{"class":166},[160,598,245],{"class":170},[160,600,174],{"class":166},[160,602,603,605,607],{"class":162,"line":207},[160,604,167],{"class":166},[160,606,171],{"class":170},[160,608,174],{"class":166},[160,610,611,613,616,619],{"class":162,"line":213},[160,612,187],{"class":166},[160,614,615],{"class":170},"input",[160,617,618],{"class":248}," v-my-directive",[160,620,621],{"class":166}," \u002F>\n",[160,623,624,626,628],{"class":162,"line":231},[160,625,200],{"class":166},[160,627,171],{"class":170},[160,629,174],{"class":166},[141,631,632,635,636,639,640],{},[83,633,634],{},"全局注册","：通过 ",[43,637,638],{},"app.directive(name, definition)"," 在整个应用中可用。",[152,641,643],{"className":413,"code":642,"language":415,"meta":156,"style":156},"const app = createApp(App);\napp.directive(\"focus\", { mounted: (el) => el.focus() });\n",[43,644,645,662],{"__ignoreMap":156},[160,646,647,649,652,654,657,660],{"class":162,"line":163},[160,648,258],{"class":257},[160,650,651],{"class":261}," app",[160,653,266],{"class":265},[160,655,656],{"class":269}," createApp",[160,658,659],{"class":196},"(App)",[160,661,327],{"class":166},[160,663,664,667,669,672,674,676,678,680,683,685,687,689,691,693,695,697,699,701,703,705,707,709],{"class":162,"line":177},[160,665,666],{"class":196},"app",[160,668,285],{"class":166},[160,670,671],{"class":269},"directive",[160,673,273],{"class":196},[160,675,321],{"class":313},[160,677,456],{"class":317},[160,679,321],{"class":313},[160,681,682],{"class":166},",",[160,684,568],{"class":166},[160,686,571],{"class":269},[160,688,437],{"class":166},[160,690,440],{"class":166},[160,692,444],{"class":443},[160,694,324],{"class":166},[160,696,279],{"class":257},[160,698,451],{"class":196},[160,700,285],{"class":166},[160,702,456],{"class":269},[160,704,307],{"class":196},[160,706,589],{"class":166},[160,708,324],{"class":196},[160,710,327],{"class":166},[48,712,714],{"id":713},"_3-实际应用场景","3. 实际应用场景",[138,716,717,832,841],{},[141,718,719,726,727],{},[83,720,721,722,725],{},"权限控制指令（",[43,723,724],{},"v-permission","）","：根据用户角色显示\u002F隐藏元素。",[152,728,730],{"className":413,"code":729,"language":415,"meta":156,"style":156},"app.directive(\"permission\", {\n  mounted(el, binding) {\n    const requiredRoles = binding.value;\n    if (!hasAnyRole(requiredRoles)) el.remove();\n  },\n});\n",[43,731,732,753,771,789,820,824],{"__ignoreMap":156},[160,733,734,736,738,740,742,744,747,749,751],{"class":162,"line":163},[160,735,666],{"class":196},[160,737,285],{"class":166},[160,739,671],{"class":269},[160,741,273],{"class":196},[160,743,321],{"class":313},[160,745,746],{"class":317},"permission",[160,748,321],{"class":313},[160,750,682],{"class":166},[160,752,429],{"class":166},[160,754,755,758,760,762,764,767,769],{"class":162,"line":177},[160,756,434],{"class":757},"sVXei",[160,759,273],{"class":166},[160,761,444],{"class":443},[160,763,682],{"class":166},[160,765,766],{"class":443}," binding",[160,768,324],{"class":166},[160,770,429],{"class":166},[160,772,773,776,779,781,783,785,787],{"class":162,"line":184},[160,774,775],{"class":257},"    const",[160,777,778],{"class":261}," requiredRoles",[160,780,266],{"class":265},[160,782,766],{"class":196},[160,784,285],{"class":166},[160,786,288],{"class":196},[160,788,327],{"class":166},[160,790,791,793,795,798,801,803,806,809,811,813,816,818],{"class":162,"line":207},[160,792,484],{"class":483},[160,794,440],{"class":487},[160,796,797],{"class":265},"!",[160,799,800],{"class":269},"hasAnyRole",[160,802,273],{"class":487},[160,804,805],{"class":196},"requiredRoles",[160,807,808],{"class":487},")) ",[160,810,444],{"class":196},[160,812,285],{"class":166},[160,814,815],{"class":269},"remove",[160,817,276],{"class":487},[160,819,327],{"class":166},[160,821,822],{"class":162,"line":213},[160,823,512],{"class":166},[160,825,826,828,830],{"class":162,"line":231},[160,827,589],{"class":166},[160,829,324],{"class":196},[160,831,327],{"class":166},[141,833,834,840],{},[83,835,836,837,725],{},"防抖指令（",[43,838,839],{},"v-debounce","：包装点击或输入事件。",[141,842,843,849],{},[83,844,845,846,725],{},"懒加载指令（",[43,847,848],{},"v-lazy","：使用 IntersectionObserver 加载图片。",[35,851,853],{"id":852},"三自定义插件","三、自定义插件",[22,855,856,857,860],{},"插件通过 ",[43,858,859],{},"app.use()"," 安装，用于向应用注入全局功能。",[48,862,863],{"id":863},"实现一个插件",[152,865,867],{"className":413,"code":866,"language":415,"meta":156,"style":156},"\u002F\u002F plugins\u002Fi18n.js\nexport default {\n  install(app, options) {\n    \u002F\u002F 注入全局属性\n    app.config.globalProperties.$t = (key) => translations[key] || key;\n    \u002F\u002F 注册全局组件\n    app.component(\"MyButton\", MyButton);\n    \u002F\u002F 注册全局指令\n    app.directive(\"focus\", { mounted: (el) => el.focus() });\n    \u002F\u002F 提供依赖注入\n    app.provide(\"i18n\", options);\n  },\n};\n",[43,868,869,874,884,902,907,957,962,989,994,1040,1046,1073,1078],{"__ignoreMap":156},[160,870,871],{"class":162,"line":163},[160,872,873],{"class":180},"\u002F\u002F plugins\u002Fi18n.js\n",[160,875,876,879,882],{"class":162,"line":177},[160,877,878],{"class":483},"export",[160,880,881],{"class":483}," default",[160,883,429],{"class":166},[160,885,886,889,891,893,895,898,900],{"class":162,"line":184},[160,887,888],{"class":757},"  install",[160,890,273],{"class":166},[160,892,666],{"class":443},[160,894,682],{"class":166},[160,896,897],{"class":443}," options",[160,899,324],{"class":166},[160,901,429],{"class":166},[160,903,904],{"class":162,"line":207},[160,905,906],{"class":180},"    \u002F\u002F 注入全局属性\n",[160,908,909,912,914,917,919,922,924,927,929,931,934,936,938,941,944,946,949,952,955],{"class":162,"line":213},[160,910,911],{"class":196},"    app",[160,913,285],{"class":166},[160,915,916],{"class":196},"config",[160,918,285],{"class":166},[160,920,921],{"class":196},"globalProperties",[160,923,285],{"class":166},[160,925,926],{"class":269},"$t",[160,928,266],{"class":265},[160,930,440],{"class":166},[160,932,933],{"class":443},"key",[160,935,324],{"class":166},[160,937,279],{"class":257},[160,939,940],{"class":196}," translations",[160,942,943],{"class":487},"[",[160,945,933],{"class":196},[160,947,948],{"class":487},"] ",[160,950,951],{"class":265},"||",[160,953,954],{"class":196}," key",[160,956,327],{"class":166},[160,958,959],{"class":162,"line":231},[160,960,961],{"class":180},"    \u002F\u002F 注册全局组件\n",[160,963,964,966,968,971,973,975,978,980,982,985,987],{"class":162,"line":240},[160,965,911],{"class":196},[160,967,285],{"class":166},[160,969,970],{"class":269},"component",[160,972,273],{"class":487},[160,974,321],{"class":313},[160,976,977],{"class":317},"MyButton",[160,979,321],{"class":313},[160,981,682],{"class":166},[160,983,984],{"class":196}," MyButton",[160,986,324],{"class":487},[160,988,327],{"class":166},[160,990,991],{"class":162,"line":254},[160,992,993],{"class":180},"    \u002F\u002F 注册全局指令\n",[160,995,996,998,1000,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026,1028,1030,1032,1034,1036,1038],{"class":162,"line":330},[160,997,911],{"class":196},[160,999,285],{"class":166},[160,1001,671],{"class":269},[160,1003,273],{"class":487},[160,1005,321],{"class":313},[160,1007,456],{"class":317},[160,1009,321],{"class":313},[160,1011,682],{"class":166},[160,1013,568],{"class":166},[160,1015,571],{"class":269},[160,1017,437],{"class":166},[160,1019,440],{"class":166},[160,1021,444],{"class":443},[160,1023,324],{"class":166},[160,1025,279],{"class":257},[160,1027,451],{"class":196},[160,1029,285],{"class":166},[160,1031,456],{"class":269},[160,1033,307],{"class":487},[160,1035,589],{"class":166},[160,1037,324],{"class":487},[160,1039,327],{"class":166},[160,1041,1043],{"class":162,"line":1042},10,[160,1044,1045],{"class":180},"    \u002F\u002F 提供依赖注入\n",[160,1047,1049,1051,1053,1056,1058,1060,1063,1065,1067,1069,1071],{"class":162,"line":1048},11,[160,1050,911],{"class":196},[160,1052,285],{"class":166},[160,1054,1055],{"class":269},"provide",[160,1057,273],{"class":487},[160,1059,321],{"class":313},[160,1061,1062],{"class":317},"i18n",[160,1064,321],{"class":313},[160,1066,682],{"class":166},[160,1068,897],{"class":196},[160,1070,324],{"class":487},[160,1072,327],{"class":166},[160,1074,1076],{"class":162,"line":1075},12,[160,1077,512],{"class":166},[160,1079,1081],{"class":162,"line":1080},13,[160,1082,517],{"class":166},[22,1084,1085],{},"使用：",[152,1087,1089],{"className":413,"code":1088,"language":415,"meta":156,"style":156},"import i18nPlugin from \".\u002Fplugins\u002Fi18n\";\napp.use(i18nPlugin, { locale: \"zh-CN\" });\n",[43,1090,1091,1111],{"__ignoreMap":156},[160,1092,1093,1096,1099,1102,1104,1107,1109],{"class":162,"line":163},[160,1094,1095],{"class":483},"import",[160,1097,1098],{"class":196}," i18nPlugin ",[160,1100,1101],{"class":483},"from",[160,1103,314],{"class":313},[160,1105,1106],{"class":317},".\u002Fplugins\u002Fi18n",[160,1108,321],{"class":313},[160,1110,327],{"class":166},[160,1112,1113,1115,1117,1120,1123,1125,1127,1130,1132,1134,1137,1139,1142,1144],{"class":162,"line":177},[160,1114,666],{"class":196},[160,1116,285],{"class":166},[160,1118,1119],{"class":269},"use",[160,1121,1122],{"class":196},"(i18nPlugin",[160,1124,682],{"class":166},[160,1126,568],{"class":166},[160,1128,1129],{"class":487}," locale",[160,1131,437],{"class":166},[160,1133,314],{"class":313},[160,1135,1136],{"class":317},"zh-CN",[160,1138,321],{"class":313},[160,1140,1141],{"class":166}," }",[160,1143,324],{"class":196},[160,1145,327],{"class":166},[48,1147,1149],{"id":1148},"插件中注入全局属性的类型扩展typescript","插件中注入全局属性的类型扩展（TypeScript）",[152,1151,1155],{"className":1152,"code":1153,"language":1154,"meta":156,"style":156},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","declare module \"vue\" {\n  interface ComponentCustomProperties {\n    $t: (key: string) => string;\n  }\n}\n","ts",[43,1156,1157,1173,1184,1209,1214],{"__ignoreMap":156},[160,1158,1159,1162,1165,1167,1169,1171],{"class":162,"line":163},[160,1160,1161],{"class":257},"declare",[160,1163,1164],{"class":257}," module",[160,1166,314],{"class":313},[160,1168,12],{"class":317},[160,1170,321],{"class":313},[160,1172,429],{"class":166},[160,1174,1175,1178,1182],{"class":162,"line":177},[160,1176,1177],{"class":257},"  interface",[160,1179,1181],{"class":1180},"sbgvK"," ComponentCustomProperties",[160,1183,429],{"class":166},[160,1185,1186,1189,1191,1193,1195,1197,1201,1203,1205,1207],{"class":162,"line":184},[160,1187,1188],{"class":757},"    $t",[160,1190,437],{"class":265},[160,1192,440],{"class":166},[160,1194,933],{"class":443},[160,1196,437],{"class":265},[160,1198,1200],{"class":1199},"sZMiF"," string",[160,1202,324],{"class":166},[160,1204,279],{"class":257},[160,1206,1200],{"class":1199},[160,1208,327],{"class":166},[160,1210,1211],{"class":162,"line":207},[160,1212,1213],{"class":166},"  }\n",[160,1215,1216],{"class":162,"line":213},[160,1217,1218],{"class":166},"}\n",[35,1220,1222],{"id":1221},"四vue-与-web-components-互操作","四、Vue 与 Web Components 互操作",[22,1224,1225],{},"Vue 3 内置了对 Web Components 的支持，可以将 Vue 组件编译为原生自定义元素，可在任何框架（或原生 HTML）中使用。",[48,1227,1229,1230,1233],{"id":1228},"使用-definecustomelement-打包-vue-组件","使用 ",[43,1231,1232],{},"defineCustomElement"," 打包 Vue 组件",[152,1235,1237],{"className":413,"code":1236,"language":415,"meta":156,"style":156},"import { defineCustomElement } from \"vue\";\nimport MyComponent from \".\u002FMyComponent.vue\";\n\nconst MyElement = defineCustomElement(MyComponent);\ncustomElements.define(\"my-component\", MyElement);\n",[43,1238,1239,1261,1279,1285,1301],{"__ignoreMap":156},[160,1240,1241,1243,1245,1248,1250,1253,1255,1257,1259],{"class":162,"line":163},[160,1242,1095],{"class":483},[160,1244,568],{"class":166},[160,1246,1247],{"class":196}," defineCustomElement",[160,1249,1141],{"class":166},[160,1251,1252],{"class":483}," from",[160,1254,314],{"class":313},[160,1256,12],{"class":317},[160,1258,321],{"class":313},[160,1260,327],{"class":166},[160,1262,1263,1265,1268,1270,1272,1275,1277],{"class":162,"line":177},[160,1264,1095],{"class":483},[160,1266,1267],{"class":196}," MyComponent ",[160,1269,1101],{"class":483},[160,1271,314],{"class":313},[160,1273,1274],{"class":317},".\u002FMyComponent.vue",[160,1276,321],{"class":313},[160,1278,327],{"class":166},[160,1280,1281],{"class":162,"line":184},[160,1282,1284],{"emptyLinePlaceholder":1283},true,"\n",[160,1286,1287,1289,1292,1294,1296,1299],{"class":162,"line":207},[160,1288,258],{"class":257},[160,1290,1291],{"class":261}," MyElement",[160,1293,266],{"class":265},[160,1295,1247],{"class":269},[160,1297,1298],{"class":196},"(MyComponent)",[160,1300,327],{"class":166},[160,1302,1303,1306,1308,1311,1313,1315,1318,1320,1322,1325],{"class":162,"line":213},[160,1304,1305],{"class":196},"customElements",[160,1307,285],{"class":166},[160,1309,1310],{"class":269},"define",[160,1312,273],{"class":196},[160,1314,321],{"class":313},[160,1316,1317],{"class":317},"my-component",[160,1319,321],{"class":313},[160,1321,682],{"class":166},[160,1323,1324],{"class":196}," MyElement)",[160,1326,327],{"class":166},[22,1328,1329,1330,30],{},"现在可以在 HTML 中直接使用 ",[43,1331,1332],{},"\u003Cmy-component>\u003C\u002Fmy-component>",[22,1334,1335,1338],{},[83,1336,1337],{},"注意事项","：",[138,1340,1341,1344,1347,1350],{},[141,1342,1343],{},"样式默认会封装在 Shadow DOM 中，支持样式隔离。",[141,1345,1346],{},"Props 通过 HTML 属性传递（非字符串需手动转换）。",[141,1348,1349],{},"插槽支持，但不支持作用域插槽。",[141,1351,1352],{},"适合跨框架复用（如 React、Angular 中使用 Vue 组件）。",[35,1354,1356],{"id":1355},"五全局-api-变更vue-2-vue-3","五、全局 API 变更（Vue 2 → Vue 3）",[22,1358,1359],{},"Vue 3 改变了全局 API 的设计，以支持多应用实例隔离。",[53,1361,1362,1375],{},[56,1363,1364],{},[59,1365,1366,1369,1372],{},[62,1367,1368],{},"Vue 2",[62,1370,1371],{},"Vue 3",[62,1373,1374],{},"说明",[75,1376,1377,1392,1407,1422,1437],{},[59,1378,1379,1384,1389],{},[80,1380,1381],{},[43,1382,1383],{},"Vue.prototype.$http = ...",[80,1385,1386],{},[43,1387,1388],{},"app.config.globalProperties.$http = ...",[80,1390,1391],{},"全局属性挂载到 app 实例，避免污染 Vue 构造函数",[59,1393,1394,1399,1404],{},[80,1395,1396],{},[43,1397,1398],{},"Vue.component('my-comp', {...})",[80,1400,1401],{},[43,1402,1403],{},"app.component('my-comp', {...})",[80,1405,1406],{},"组件注册限定在特定 app 内",[59,1408,1409,1414,1419],{},[80,1410,1411],{},[43,1412,1413],{},"Vue.directive('focus', {...})",[80,1415,1416],{},[43,1417,1418],{},"app.directive('focus', {...})",[80,1420,1421],{},"指令注册同理",[59,1423,1424,1429,1434],{},[80,1425,1426],{},[43,1427,1428],{},"Vue.use(plugin, options)",[80,1430,1431],{},[43,1432,1433],{},"app.use(plugin, options)",[80,1435,1436],{},"插件作用域为 app",[59,1438,1439,1444,1449],{},[80,1440,1441],{},[43,1442,1443],{},"new Vue({...})",[80,1445,1446],{},[43,1447,1448],{},"createApp({...})",[80,1450,1451],{},"返回 app 实例，支持多应用",[22,1453,1454,1338],{},[83,1455,1456],{},"优势",[138,1458,1459,1462],{},[141,1460,1461],{},"支持在同一个页面中运行多个 Vue 应用（微前端场景），它们之间不会相互干扰。",[141,1463,1464],{},"便于测试（每个测试可以创建独立的 app）。",[35,1466,1468],{"id":1467},"六开发调试工具vue-devtools","六、开发调试工具：Vue DevTools",[22,1470,1471],{},"Vue DevTools 是浏览器扩展，提供强大的调试能力。",[48,1473,1474],{"id":1474},"进阶功能",[138,1476,1477,1493,1506,1519,1525],{},[141,1478,1479,1338,1482],{},[83,1480,1481],{},"组件树分析",[138,1483,1484,1487,1490],{},[141,1485,1486],{},"查看组件层级、props、data、computed。",[141,1488,1489],{},"高亮渲染轨迹（哪些组件重新渲染了）。",[141,1491,1492],{},"时间旅行调试（修改 data 并回溯）。",[141,1494,1495,1338,1498],{},[83,1496,1497],{},"性能时间轴",[138,1499,1500,1503],{},[141,1501,1502],{},"记录组件渲染、更新耗时。",[141,1504,1505],{},"识别性能瓶颈（慢组件）。",[141,1507,1508,1338,1511],{},[83,1509,1510],{},"Pinia 状态快照",[138,1512,1513,1516],{},[141,1514,1515],{},"实时查看 Pinia store 的状态。",[141,1517,1518],{},"时间旅行修改 store 值，观察 UI 变化。",[141,1520,1521,1524],{},[83,1522,1523],{},"路由追踪","：显示当前路由、导航历史。",[141,1526,1527,1530],{},[83,1528,1529],{},"自定义 Inspector","：插件开发者可注入自定义面板。",[22,1532,1533,1338],{},[83,1534,1535],{},"使用技巧",[138,1537,1538,1541],{},[141,1539,1540],{},"开启“组件高亮”方便定位 DOM 对应的组件。",[141,1542,1543],{},"使用“性能分析”录制交互过程，分析不必要的重渲染。",[35,1545,1546],{"id":1546},"总结",[53,1548,1549,1559],{},[56,1550,1551],{},[59,1552,1553,1556],{},[62,1554,1555],{},"工程化主题",[62,1557,1558],{},"核心实践",[75,1560,1561,1571,1587,1600,1612,1629],{},[59,1562,1563,1568],{},[80,1564,1565],{},[83,1566,1567],{},"SFC 设计模式",[80,1569,1570],{},"大型组件拆分（子组件 + composable） + 避免模板重逻辑",[59,1572,1573,1578],{},[80,1574,1575],{},[83,1576,1577],{},"自定义指令",[80,1579,1580,1581,1583,1584,1586],{},"生命周期钩子（",[43,1582,373],{},"\u002F",[43,1585,390],{},"），全局\u002F局部注册，典型场景（权限、防抖、懒加载）",[59,1588,1589,1594],{},[80,1590,1591],{},[83,1592,1593],{},"自定义插件",[80,1595,1596,1599],{},[43,1597,1598],{},"install"," 方法，注入全局属性\u002F指令\u002F组件，配合 TypeScript 类型扩展",[59,1601,1602,1607],{},[80,1603,1604],{},[83,1605,1606],{},"Web Components",[80,1608,1609,1611],{},[43,1610,1232],{}," 导出 Vue 组件，实现跨框架复用",[59,1613,1614,1619],{},[80,1615,1616],{},[83,1617,1618],{},"全局 API 变更",[80,1620,1621,1622,1625,1626,1628],{},"从 ",[43,1623,1624],{},"Vue"," 构造函数改为 ",[43,1627,666],{}," 实例，支持多应用隔离",[59,1630,1631,1636],{},[80,1632,1633],{},[83,1634,1635],{},"Vue DevTools",[80,1637,1638],{},"组件树、性能时间轴、Pinia 快照、路由调试",[22,1640,1641],{},"掌握这些工程化实践，能让 Vue 项目在架构清晰度、代码复用、调试效率及跨平台能力上迈上新台阶。",[1643,1644,1645],"h2",{"id":1645},"参考文献",[22,1647,1648],{},"以下链接在编写时均可正常访问：",[53,1650,1651,1660],{},[56,1652,1653],{},[59,1654,1655,1658],{},[62,1656,1657],{},"资料",[62,1659,1374],{},[75,1661,1662,1675,1687,1699],{},[59,1663,1664,1672],{},[80,1665,1666],{},[26,1667,1671],{"href":1668,"rel":1669},"https:\u002F\u002Fcn.vuejs.org\u002Fguide\u002Fscaling-up\u002Fsfc.html",[1670],"nofollow","Vue：单文件组件",[80,1673,1674],{},"SFC",[59,1676,1677,1684],{},[80,1678,1679],{},[26,1680,1683],{"href":1681,"rel":1682},"https:\u002F\u002Fcn.vuejs.org\u002Fguide\u002Freusability\u002Fcustom-directives.html",[1670],"Vue：自定义指令",[80,1685,1686],{},"指令",[59,1688,1689,1696],{},[80,1690,1691],{},[26,1692,1695],{"href":1693,"rel":1694},"https:\u002F\u002Fcn.vuejs.org\u002Fguide\u002Freusability\u002Fplugins.html",[1670],"Vue：插件",[80,1697,1698],{},"插件",[59,1700,1701,1707],{},[80,1702,1703],{},[26,1704,1635],{"href":1705,"rel":1706},"https:\u002F\u002Fdevtools.vuejs.org\u002F",[1670],[80,1708,1709],{},"调试工具",[1711,1712,1713],"style",{},"html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sQzsp, html code.shiki .sQzsp{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9AJx, html code.shiki .s9AJx{--shiki-light:#9C3EDA;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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);}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sVXei, html code.shiki .sVXei{--shiki-light:#E53935;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":156,"searchDepth":177,"depth":177,"links":1715},[1716,1717,1718,1719,1720,1721,1722,1723],{"id":37,"depth":184,"text":38},{"id":345,"depth":184,"text":346},{"id":852,"depth":184,"text":853},{"id":1221,"depth":184,"text":1222},{"id":1355,"depth":184,"text":1356},{"id":1467,"depth":184,"text":1468},{"id":1546,"depth":184,"text":1546},{"id":1645,"depth":177,"text":1645},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fframe\u002FVue\u002Fproject","knowledge\u002Fknowledge\u002Fbooklet\u002Fframe\u002FVue\u002Fproject.md","md",{"items":1729},[1730,1734,1738,1741,1745,1749],{"path":1731,"title":1732,"description":1733,"category":10,"score":231},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fperformance\u002Fassets","资源优化","图片懒加载、按需引入与 Web Vitals 监控。见 HTML 图片、CSS 字体优化。",{"path":1735,"title":1736,"description":1737,"category":10,"score":231},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fstatus","状态管理：Pinia 与 Vuex","Pinia 是 Vue 官方推荐的状态管理库（Vue 3 新项目默认选型）。Vuex 4 仍可与 Vue 3 配合，用于维护旧项目。见 Vue 基础。",{"path":28,"title":1739,"description":1740,"category":10,"score":231},"Vue 生态关键库深度集成","VueUse、Vite 与 Nuxt 是 Vue 3 生态中常用的效率与工程化方案。概览见 Vue 基础。",{"path":1742,"title":1743,"description":1744,"category":10,"score":231},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Freactivity\u002Ftrack","依赖收集与派发更新机制","说明 Vue 3 中 track \u002F trigger 与 targetMap 的工作方式。前置：v2-v3 差异。",{"path":1746,"title":1747,"description":1748,"category":10,"score":231},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Ftest","测试策略","Vue 应用常用 Vitest + @vue\u002Ftest-utils 做单元\u002F组件测试，Playwright 或 Cypress 做 E2E。见 Vue 基础。",{"path":1750,"title":1751,"description":1752,"category":10,"score":231},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fdesign","组合式 API 与逻辑复用设计模式","组合式 API 与 Composables 是 Vue 3 推荐的逻辑组织方式。概览见 Vue 基础。",{"series":1754,"title":12,"items":1755,"index":213,"prev":1805,"next":1806},"booklet\u002Fframe\u002Fvue",[1756,1758,1762,1766,1768,1770,1771,1775,1779,1783,1787,1791,1793,1795,1797,1801],{"path":1731,"title":1732,"date":1757,"pinned":14},"2025-11-06",{"path":1759,"title":1760,"date":1761,"pinned":14},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fbasics","Vue 基础","2021-07-29",{"path":1763,"title":1764,"date":1765,"pinned":14},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fperformance\u002Fbuild","编译与打包优化","2021-08-02",{"path":1750,"title":1751,"date":1767,"pinned":14},"2024-06-16",{"path":28,"title":1739,"date":1769,"pinned":14},"2025-08-26",{"path":7,"title":8,"date":13,"pinned":14},{"path":1772,"title":1773,"date":1774,"pinned":14},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fprops","组件通信与跨层级设计模式","2023-03-23",{"path":1776,"title":1777,"date":1778,"pinned":14},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Freactivity\u002Fproxy","响应式进阶","2023-03-21",{"path":1780,"title":1781,"date":1782,"pinned":14},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Frender","渲染机制与编译原理","2021-09-23",{"path":1784,"title":1785,"date":1786,"pinned":14},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Frouter","路由与权限控制","2021-09-12",{"path":1788,"title":1789,"date":1790,"pinned":14},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fperformance\u002Fruntime","运行时优化","2021-06-10",{"path":1735,"title":1736,"date":1792,"pinned":14},"2025-10-11",{"path":1746,"title":1747,"date":1794,"pinned":14},"2024-10-23",{"path":1742,"title":1743,"date":1796,"pinned":14},"2025-01-13",{"path":1798,"title":1799,"date":1800,"pinned":14},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Ftypescript","TypeScript 深度集成","2023-11-29",{"path":1802,"title":1803,"date":1804,"pinned":14},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Freactivity\u002Fv2-v3","Vue 2 与 Vue 3 响应式差异","2022-07-06",{"path":28,"title":1739,"date":1769,"pinned":14},{"path":1772,"title":1773,"date":1774,"pinned":14}]