[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fdesign":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fdesign":3254,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fdesign":3280},{"source":4,"connector":5,"page":6},"sqlite","better-sqlite3",{"path":7,"title":8,"description":9,"category":10,"tags":11,"date":14,"pinned":15,"draft":15,"body":16,"seo":3250,"stem":3251,"id":3252,"extension":3253},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fdesign","组合式 API 与逻辑复用设计模式","组合式 API 与 Composables 是 Vue 3 推荐的逻辑组织方式。概览见 Vue 基础。","技能小册",[12,13],"vue","design","2024-06-16",false,{"type":17,"value":18,"toc":3239},"minimark",[19,23,33,38,41,46,73,77,96,386,394,398,403,436,592,595,687,694,813,822,826,833,837,864,984,988,1001,1206,1210,1241,1365,1372,1460,1464,1480,1993,1999,2067,2071,2075,2101,2105,2131,2229,2233,2240,2367,2382,2388,2392,2395,2399,2409,2566,2570,2573,2827,2831,2841,2942,2946,2953,3086,3089,3170,3177,3181,3184,3235],[20,21,8],"h1",{"id":22},"组合式-api-与逻辑复用设计模式",[24,25,26,27,32],"p",{},"组合式 API 与 Composables 是 Vue 3 推荐的逻辑组织方式。概览见 ",[28,29,31],"a",{"href":30},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fbasics","Vue 基础","。",[34,35,37],"h3",{"id":36},"一核心设计理念","一、核心设计理念",[24,39,40],{},"组合式 API（Composition API）是 Vue 3 引入的一组全新 API，旨在解决选项式 API（Options API）在大型组件中逻辑分散、复用困难的问题。其核心设计理念包括：",[42,43,45],"h4",{"id":44},"_1-函数式编程思维","1. 函数式编程思维",[24,47,48,49,53,54,58,59,62,63,62,66,62,69,72],{},"将组件的逻辑视为",[50,51,52],"strong",{},"函数的组合","，而不是选项对象的堆积。响应式状态、计算属性、侦听器、生命周期钩子都可以在 ",[55,56,57],"code",{},"setup"," 函数内部通过显式调用（如 ",[55,60,61],{},"ref","、",[55,64,65],{},"computed",[55,67,68],{},"watch",[55,70,71],{},"onMounted","）来声明。这种风格更接近原生 JavaScript 的函数式编程，使得逻辑更加灵活、可测试。",[42,74,76],{"id":75},"_2-按功能聚合逻辑而非按选项类型拆分","2. 按功能聚合逻辑（而非按选项类型拆分）",[24,78,79,80,62,83,62,86,62,89,91,92,95],{},"在选项式 API 中，一个功能的代码（如数据、方法、生命周期、侦听器）被迫分散在 ",[55,81,82],{},"data",[55,84,85],{},"methods",[55,87,88],{},"mounted",[55,90,68],{}," 等不同选项中，导致“关注点分散”。组合式 API 允许将",[50,93,94],{},"同一个逻辑关注点","（如“用户信息管理”、“表单验证”）的所有相关代码放在一起，通过自定义组合函数（Composables）进行封装和复用。",[97,98,103],"pre",{"className":99,"code":100,"language":101,"meta":102,"style":102},"language-js shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F 选项式：同一功能散落各处\nexport default {\n  data() { return { user: null }; },\n  methods: { fetchUser() {...} },\n  mounted() { this.fetchUser(); }\n}\n\n\u002F\u002F 组合式：按功能聚合\nimport { ref, onMounted } from 'vue';\nexport default {\n  setup() {\n    const user = ref(null);\n    const fetchUser = async () => { ... };\n    onMounted(fetchUser);\n    return { user, fetchUser };\n  }\n}\n","js","",[55,104,105,114,128,163,189,217,223,230,236,273,282,292,318,345,359,375,381],{"__ignoreMap":102},[106,107,110],"span",{"class":108,"line":109},"line",1,[106,111,113],{"class":112},"sutJx","\u002F\u002F 选项式：同一功能散落各处\n",[106,115,117,121,124],{"class":108,"line":116},2,[106,118,120],{"class":119},"sVHd0","export",[106,122,123],{"class":119}," default",[106,125,127],{"class":126},"sP7_E"," {\n",[106,129,131,135,138,141,144,146,150,153,157,160],{"class":108,"line":130},3,[106,132,134],{"class":133},"sVXei","  data",[106,136,137],{"class":126},"()",[106,139,140],{"class":126}," {",[106,142,143],{"class":119}," return",[106,145,140],{"class":126},[106,147,149],{"class":148},"skxfh"," user",[106,151,152],{"class":126},":",[106,154,156],{"class":155},"s39Yj"," null",[106,158,159],{"class":126}," };",[106,161,162],{"class":126}," },\n",[106,164,166,169,171,173,176,178,180,184,187],{"class":108,"line":165},4,[106,167,168],{"class":148},"  methods",[106,170,152],{"class":126},[106,172,140],{"class":126},[106,174,175],{"class":133}," fetchUser",[106,177,137],{"class":126},[106,179,140],{"class":126},[106,181,183],{"class":182},"smGrS","...",[106,185,186],{"class":126},"}",[106,188,162],{"class":126},[106,190,192,195,197,199,202,205,209,211,214],{"class":108,"line":191},5,[106,193,194],{"class":133},"  mounted",[106,196,137],{"class":126},[106,198,140],{"class":126},[106,200,201],{"class":155}," this",[106,203,204],{"class":126},".",[106,206,208],{"class":207},"sGLFI","fetchUser",[106,210,137],{"class":148},[106,212,213],{"class":126},";",[106,215,216],{"class":126}," }\n",[106,218,220],{"class":108,"line":219},6,[106,221,222],{"class":126},"}\n",[106,224,226],{"class":108,"line":225},7,[106,227,229],{"emptyLinePlaceholder":228},true,"\n",[106,231,233],{"class":108,"line":232},8,[106,234,235],{"class":112},"\u002F\u002F 组合式：按功能聚合\n",[106,237,239,242,244,248,251,254,257,260,264,267,270],{"class":108,"line":238},9,[106,240,241],{"class":119},"import",[106,243,140],{"class":126},[106,245,247],{"class":246},"su5hD"," ref",[106,249,250],{"class":126},",",[106,252,253],{"class":246}," onMounted",[106,255,256],{"class":126}," }",[106,258,259],{"class":119}," from",[106,261,263],{"class":262},"sjJ54"," '",[106,265,12],{"class":266},"s_sjI",[106,268,269],{"class":262},"'",[106,271,272],{"class":126},";\n",[106,274,276,278,280],{"class":108,"line":275},10,[106,277,120],{"class":119},[106,279,123],{"class":119},[106,281,127],{"class":126},[106,283,285,288,290],{"class":108,"line":284},11,[106,286,287],{"class":133},"  setup",[106,289,137],{"class":126},[106,291,127],{"class":126},[106,293,295,299,302,305,307,310,313,316],{"class":108,"line":294},12,[106,296,298],{"class":297},"sbsja","    const",[106,300,149],{"class":301},"s_hVV",[106,303,304],{"class":182}," =",[106,306,247],{"class":207},[106,308,309],{"class":148},"(",[106,311,312],{"class":155},"null",[106,314,315],{"class":148},")",[106,317,272],{"class":126},[106,319,321,323,326,328,331,334,337,339,342],{"class":108,"line":320},13,[106,322,298],{"class":297},[106,324,175],{"class":325},"sfCm-",[106,327,304],{"class":182},[106,329,330],{"class":297}," async",[106,332,333],{"class":126}," ()",[106,335,336],{"class":297}," =>",[106,338,140],{"class":126},[106,340,341],{"class":182}," ...",[106,343,344],{"class":126}," };\n",[106,346,348,351,353,355,357],{"class":108,"line":347},14,[106,349,350],{"class":207},"    onMounted",[106,352,309],{"class":148},[106,354,208],{"class":246},[106,356,315],{"class":148},[106,358,272],{"class":126},[106,360,362,365,367,369,371,373],{"class":108,"line":361},15,[106,363,364],{"class":119},"    return",[106,366,140],{"class":126},[106,368,149],{"class":246},[106,370,250],{"class":126},[106,372,175],{"class":246},[106,374,344],{"class":126},[106,376,378],{"class":108,"line":377},16,[106,379,380],{"class":126},"  }\n",[106,382,384],{"class":108,"line":383},17,[106,385,222],{"class":126},[34,387,389,390,393],{"id":388},"二script-setup-语法糖","二、",[55,391,392],{},"\u003Cscript setup>"," 语法糖",[42,395,397],{"id":396},"_1-编译时转换机制","1. 编译时转换机制",[24,399,400,402],{},[55,401,392],{}," 是 Vue 3.2 引入的编译时语法糖，用于简化组合式 API 的书写。它的底层实现原理是：",[404,405,406,421],"ul",{},[407,408,409,410,412,413,416,417,420],"li",{},"编译器将 ",[55,411,392],{}," 中的代码",[50,414,415],{},"重写","为普通的 ",[55,418,419],{},"setup()"," 函数体。",[407,422,423,426,427,429,430,432,433,32],{},[50,424,425],{},"顶层绑定自动暴露","：所有在 ",[55,428,392],{}," 顶层声明的变量、函数、import 导入的内容，都会被编译器自动添加到 ",[55,431,419],{}," 的返回值中，从而可以直接在模板中使用，无需手动 ",[55,434,435],{},"return",[97,437,440],{"className":438,"code":439,"language":12,"meta":102,"style":102},"language-vue shiki shiki-themes material-theme-lighter github-light github-dark","\u003Cscript setup>\nimport { ref } from \"vue\";\nconst count = ref(0);\nfunction increment() {\n  count.value++;\n}\n\u003C\u002Fscript>\n\u003Ctemplate>\n  \u003Cbutton @click=\"increment\">{{ count }}\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[55,441,442,458,480,502,514,529,533,542,551,584],{"__ignoreMap":102},[106,443,444,447,451,455],{"class":108,"line":109},[106,445,446],{"class":126},"\u003C",[106,448,450],{"class":449},"sQzsp","script",[106,452,454],{"class":453},"s9AJx"," setup",[106,456,457],{"class":126},">\n",[106,459,460,462,464,466,468,470,473,475,478],{"class":108,"line":116},[106,461,241],{"class":119},[106,463,140],{"class":126},[106,465,247],{"class":246},[106,467,256],{"class":126},[106,469,259],{"class":119},[106,471,472],{"class":262}," \"",[106,474,12],{"class":266},[106,476,477],{"class":262},"\"",[106,479,272],{"class":126},[106,481,482,485,488,490,492,494,498,500],{"class":108,"line":130},[106,483,484],{"class":297},"const",[106,486,487],{"class":301}," count",[106,489,304],{"class":182},[106,491,247],{"class":207},[106,493,309],{"class":246},[106,495,497],{"class":496},"srdBf","0",[106,499,315],{"class":246},[106,501,272],{"class":126},[106,503,504,507,510,512],{"class":108,"line":165},[106,505,506],{"class":297},"function",[106,508,509],{"class":207}," increment",[106,511,137],{"class":126},[106,513,127],{"class":126},[106,515,516,519,521,524,527],{"class":108,"line":191},[106,517,518],{"class":246},"  count",[106,520,204],{"class":126},[106,522,523],{"class":246},"value",[106,525,526],{"class":182},"++",[106,528,272],{"class":126},[106,530,531],{"class":108,"line":219},[106,532,222],{"class":126},[106,534,535,538,540],{"class":108,"line":225},[106,536,537],{"class":126},"\u003C\u002F",[106,539,450],{"class":449},[106,541,457],{"class":126},[106,543,544,546,549],{"class":108,"line":232},[106,545,446],{"class":126},[106,547,548],{"class":449},"template",[106,550,457],{"class":126},[106,552,553,556,559,562,565,567,570,572,575,578,580,582],{"class":108,"line":238},[106,554,555],{"class":126},"  \u003C",[106,557,558],{"class":449},"button",[106,560,561],{"class":453}," @click",[106,563,564],{"class":126},"=",[106,566,477],{"class":262},[106,568,569],{"class":266},"increment",[106,571,477],{"class":262},[106,573,574],{"class":126},">",[106,576,577],{"class":246},"{{ count }}",[106,579,537],{"class":126},[106,581,558],{"class":449},[106,583,457],{"class":126},[106,585,586,588,590],{"class":108,"line":275},[106,587,537],{"class":126},[106,589,548],{"class":449},[106,591,457],{"class":126},[24,593,594],{},"编译后近似为：",[97,596,598],{"className":99,"code":597,"language":101,"meta":102,"style":102},"export default {\n  setup() {\n    const count = ref(0);\n    function increment() {\n      count.value++;\n    }\n    return { count, increment };\n  },\n};\n",[55,599,600,608,616,634,645,658,663,677,682],{"__ignoreMap":102},[106,601,602,604,606],{"class":108,"line":109},[106,603,120],{"class":119},[106,605,123],{"class":119},[106,607,127],{"class":126},[106,609,610,612,614],{"class":108,"line":116},[106,611,287],{"class":133},[106,613,137],{"class":126},[106,615,127],{"class":126},[106,617,618,620,622,624,626,628,630,632],{"class":108,"line":130},[106,619,298],{"class":297},[106,621,487],{"class":301},[106,623,304],{"class":182},[106,625,247],{"class":207},[106,627,309],{"class":148},[106,629,497],{"class":496},[106,631,315],{"class":148},[106,633,272],{"class":126},[106,635,636,639,641,643],{"class":108,"line":165},[106,637,638],{"class":297},"    function",[106,640,509],{"class":207},[106,642,137],{"class":126},[106,644,127],{"class":126},[106,646,647,650,652,654,656],{"class":108,"line":191},[106,648,649],{"class":246},"      count",[106,651,204],{"class":126},[106,653,523],{"class":246},[106,655,526],{"class":182},[106,657,272],{"class":126},[106,659,660],{"class":108,"line":219},[106,661,662],{"class":126},"    }\n",[106,664,665,667,669,671,673,675],{"class":108,"line":225},[106,666,364],{"class":119},[106,668,140],{"class":126},[106,670,487],{"class":246},[106,672,250],{"class":126},[106,674,509],{"class":246},[106,676,344],{"class":126},[106,678,679],{"class":108,"line":232},[106,680,681],{"class":126},"  },\n",[106,683,684],{"class":108,"line":238},[106,685,686],{"class":126},"};\n",[42,688,690,691,693],{"id":689},"_2-与普通-setup-的差异与优势","2. 与普通 ",[55,692,419],{}," 的差异与优势",[695,696,697,716],"table",{},[698,699,700],"thead",{},[701,702,703,707,711],"tr",{},[704,705,706],"th",{},"特性",[704,708,709],{},[55,710,392],{},[704,712,713,714],{},"普通 ",[55,715,419],{},[717,718,719,738,758,781,794],"tbody",{},[701,720,721,727,732],{},[722,723,724],"td",{},[50,725,726],{},"代码量",[722,728,729,730],{},"更简洁，无需 ",[55,731,435],{},[722,733,734,735,737],{},"需要手动 ",[55,736,435],{}," 模板使用的变量\u002F函数",[701,739,740,745,752],{},[722,741,742],{},[50,743,744],{},"组件注册",[722,746,747,748,751],{},"直接 import 组件即可使用，无需 ",[55,749,750],{},"components"," 选项",[722,753,754,755,757],{},"需要在 ",[55,756,750],{}," 中注册",[701,759,760,765,775],{},[722,761,762],{},[50,763,764],{},"Props \u002F Emits",[722,766,767,768,62,771,774],{},"通过 ",[55,769,770],{},"defineProps",[55,772,773],{},"defineEmits"," 宏定义，编译时处理",[722,776,767,777,780],{},[55,778,779],{},"setup(props, context)"," 参数定义",[701,782,783,788,791],{},[722,784,785],{},[50,786,787],{},"类型推导",[722,789,790],{},"对 TypeScript 支持更好，宏自动推导类型",[722,792,793],{},"需要手动标注类型",[701,795,796,804,810],{},[722,797,798],{},[50,799,800,801],{},"顶层 ",[55,802,803],{},"await",[722,805,806,807,809],{},"支持，编译为 ",[55,808,419],{}," 中的异步立即执行函数",[722,811,812],{},"需要包装为 IIFE 或手动处理",[24,814,815,818,819,821],{},[50,816,817],{},"优势总结","：更少的样板代码、更自然的组件定义、更好的 TypeScript 集成、支持顶层 ",[55,820,803],{},"（配合 Suspense）。",[34,823,825],{"id":824},"三自定义组合函数composables设计模式","三、自定义组合函数（Composables）设计模式",[24,827,828,829,832],{},"Composable 是一个利用组合式 API 封装",[50,830,831],{},"有状态逻辑","的函数，遵循特定的设计约定。",[42,834,836],{"id":835},"_1-命名规范与返回值设计","1. 命名规范与返回值设计",[404,838,839,858],{},[407,840,841,844,845,848,849,62,852,62,855,32],{},[50,842,843],{},"命名","：以 ",[55,846,847],{},"use"," 开头，如 ",[55,850,851],{},"useUser",[55,853,854],{},"useFetch",[55,856,857],{},"useCounter",[407,859,860,863],{},[50,861,862],{},"返回值","：通常返回一个包含响应式状态和操作函数的对象。如果只需要返回单个 ref，也可以直接返回该 ref，但推荐使用对象方便扩展。",[97,865,867],{"className":99,"code":866,"language":101,"meta":102,"style":102},"\u002F\u002F 好的返回值设计\nfunction useCounter(initialValue = 0) {\n  const count = ref(initialValue);\n  const increment = () => count.value++;\n  const decrement = () => count.value--;\n  return { count, increment, decrement };\n}\n",[55,868,869,874,896,915,937,961,980],{"__ignoreMap":102},[106,870,871],{"class":108,"line":109},[106,872,873],{"class":112},"\u002F\u002F 好的返回值设计\n",[106,875,876,878,881,883,887,889,892,894],{"class":108,"line":116},[106,877,506],{"class":297},[106,879,880],{"class":207}," useCounter",[106,882,309],{"class":126},[106,884,886],{"class":885},"s99_P","initialValue",[106,888,304],{"class":182},[106,890,891],{"class":496}," 0",[106,893,315],{"class":126},[106,895,127],{"class":126},[106,897,898,901,903,905,907,909,911,913],{"class":108,"line":130},[106,899,900],{"class":297},"  const",[106,902,487],{"class":301},[106,904,304],{"class":182},[106,906,247],{"class":207},[106,908,309],{"class":148},[106,910,886],{"class":246},[106,912,315],{"class":148},[106,914,272],{"class":126},[106,916,917,919,921,923,925,927,929,931,933,935],{"class":108,"line":165},[106,918,900],{"class":297},[106,920,509],{"class":325},[106,922,304],{"class":182},[106,924,333],{"class":126},[106,926,336],{"class":297},[106,928,487],{"class":246},[106,930,204],{"class":126},[106,932,523],{"class":246},[106,934,526],{"class":182},[106,936,272],{"class":126},[106,938,939,941,944,946,948,950,952,954,956,959],{"class":108,"line":191},[106,940,900],{"class":297},[106,942,943],{"class":325}," decrement",[106,945,304],{"class":182},[106,947,333],{"class":126},[106,949,336],{"class":297},[106,951,487],{"class":246},[106,953,204],{"class":126},[106,955,523],{"class":246},[106,957,958],{"class":182},"--",[106,960,272],{"class":126},[106,962,963,966,968,970,972,974,976,978],{"class":108,"line":219},[106,964,965],{"class":119},"  return",[106,967,140],{"class":126},[106,969,487],{"class":246},[106,971,250],{"class":126},[106,973,509],{"class":246},[106,975,250],{"class":126},[106,977,943],{"class":246},[106,979,344],{"class":126},[106,981,982],{"class":108,"line":225},[106,983,222],{"class":126},[42,985,987],{"id":986},"_2-响应式状态的生命周期管理","2. 响应式状态的生命周期管理",[24,989,990,991,62,993,996,997,1000],{},"Composable 内部可以使用生命周期钩子（",[55,992,71],{},[55,994,995],{},"onUnmounted"," 等），这些钩子会绑定到",[50,998,999],{},"调用该 composable 的组件实例","上。",[97,1002,1004],{"className":99,"code":1003,"language":101,"meta":102,"style":102},"function useMouseTracker() {\n  const x = ref(0);\n  const y = ref(0);\n  function update(e) {\n    x.value = e.clientX;\n    y.value = e.clientY;\n  }\n  onMounted(() => window.addEventListener(\"mousemove\", update));\n  onUnmounted(() => window.removeEventListener(\"mousemove\", update));\n  return { x, y };\n}\n",[55,1005,1006,1017,1036,1055,1072,1093,1113,1117,1154,1188,1202],{"__ignoreMap":102},[106,1007,1008,1010,1013,1015],{"class":108,"line":109},[106,1009,506],{"class":297},[106,1011,1012],{"class":207}," useMouseTracker",[106,1014,137],{"class":126},[106,1016,127],{"class":126},[106,1018,1019,1021,1024,1026,1028,1030,1032,1034],{"class":108,"line":116},[106,1020,900],{"class":297},[106,1022,1023],{"class":301}," x",[106,1025,304],{"class":182},[106,1027,247],{"class":207},[106,1029,309],{"class":148},[106,1031,497],{"class":496},[106,1033,315],{"class":148},[106,1035,272],{"class":126},[106,1037,1038,1040,1043,1045,1047,1049,1051,1053],{"class":108,"line":130},[106,1039,900],{"class":297},[106,1041,1042],{"class":301}," y",[106,1044,304],{"class":182},[106,1046,247],{"class":207},[106,1048,309],{"class":148},[106,1050,497],{"class":496},[106,1052,315],{"class":148},[106,1054,272],{"class":126},[106,1056,1057,1060,1063,1065,1068,1070],{"class":108,"line":165},[106,1058,1059],{"class":297},"  function",[106,1061,1062],{"class":207}," update",[106,1064,309],{"class":126},[106,1066,1067],{"class":885},"e",[106,1069,315],{"class":126},[106,1071,127],{"class":126},[106,1073,1074,1077,1079,1081,1083,1086,1088,1091],{"class":108,"line":191},[106,1075,1076],{"class":246},"    x",[106,1078,204],{"class":126},[106,1080,523],{"class":246},[106,1082,304],{"class":182},[106,1084,1085],{"class":246}," e",[106,1087,204],{"class":126},[106,1089,1090],{"class":246},"clientX",[106,1092,272],{"class":126},[106,1094,1095,1098,1100,1102,1104,1106,1108,1111],{"class":108,"line":219},[106,1096,1097],{"class":246},"    y",[106,1099,204],{"class":126},[106,1101,523],{"class":246},[106,1103,304],{"class":182},[106,1105,1085],{"class":246},[106,1107,204],{"class":126},[106,1109,1110],{"class":246},"clientY",[106,1112,272],{"class":126},[106,1114,1115],{"class":108,"line":225},[106,1116,380],{"class":126},[106,1118,1119,1122,1124,1126,1128,1131,1133,1136,1138,1140,1143,1145,1147,1149,1152],{"class":108,"line":232},[106,1120,1121],{"class":207},"  onMounted",[106,1123,309],{"class":148},[106,1125,137],{"class":126},[106,1127,336],{"class":297},[106,1129,1130],{"class":246}," window",[106,1132,204],{"class":126},[106,1134,1135],{"class":207},"addEventListener",[106,1137,309],{"class":148},[106,1139,477],{"class":262},[106,1141,1142],{"class":266},"mousemove",[106,1144,477],{"class":262},[106,1146,250],{"class":126},[106,1148,1062],{"class":246},[106,1150,1151],{"class":148},"))",[106,1153,272],{"class":126},[106,1155,1156,1159,1161,1163,1165,1167,1169,1172,1174,1176,1178,1180,1182,1184,1186],{"class":108,"line":238},[106,1157,1158],{"class":207},"  onUnmounted",[106,1160,309],{"class":148},[106,1162,137],{"class":126},[106,1164,336],{"class":297},[106,1166,1130],{"class":246},[106,1168,204],{"class":126},[106,1170,1171],{"class":207},"removeEventListener",[106,1173,309],{"class":148},[106,1175,477],{"class":262},[106,1177,1142],{"class":266},[106,1179,477],{"class":262},[106,1181,250],{"class":126},[106,1183,1062],{"class":246},[106,1185,1151],{"class":148},[106,1187,272],{"class":126},[106,1189,1190,1192,1194,1196,1198,1200],{"class":108,"line":275},[106,1191,965],{"class":119},[106,1193,140],{"class":126},[106,1195,1023],{"class":246},[106,1197,250],{"class":126},[106,1199,1042],{"class":246},[106,1201,344],{"class":126},[106,1203,1204],{"class":108,"line":284},[106,1205,222],{"class":126},[42,1207,1209],{"id":1208},"_3-参数设计支持-ref-输入返回值响应式包装","3. 参数设计：支持 ref 输入、返回值响应式包装",[404,1211,1212,1229],{},[407,1213,1214,1217,1218,1221,1222,1225,1226,1228],{},[50,1215,1216],{},"参数可以是 ref 或普通值","：使用 ",[55,1219,1220],{},"toRef"," \u002F ",[55,1223,1224],{},"toValue","（Vue 3.3+ 的 ",[55,1227,1224],{}," 工具函数）来统一处理。",[407,1230,1231,1234,1235,1237,1238,1240],{},[50,1232,1233],{},"返回值保持响应式","：如果内部状态是基于外部参数计算得出的，应使用 ",[55,1236,65],{}," 或 ",[55,1239,61],{}," 包装。",[97,1242,1244],{"className":99,"code":1243,"language":101,"meta":102,"style":102},"function useDouble(input) {\n  \u002F\u002F input 可能是 number 或 ref\u003Cnumber>\n  const double = computed(() => {\n    const val = typeof input === \"number\" ? input : input.value;\n    return val * 2;\n  });\n  return { double };\n}\n",[55,1245,1246,1262,1267,1287,1328,1342,1351,1361],{"__ignoreMap":102},[106,1247,1248,1250,1253,1255,1258,1260],{"class":108,"line":109},[106,1249,506],{"class":297},[106,1251,1252],{"class":207}," useDouble",[106,1254,309],{"class":126},[106,1256,1257],{"class":885},"input",[106,1259,315],{"class":126},[106,1261,127],{"class":126},[106,1263,1264],{"class":108,"line":116},[106,1265,1266],{"class":112},"  \u002F\u002F input 可能是 number 或 ref\u003Cnumber>\n",[106,1268,1269,1271,1274,1276,1279,1281,1283,1285],{"class":108,"line":130},[106,1270,900],{"class":297},[106,1272,1273],{"class":301}," double",[106,1275,304],{"class":182},[106,1277,1278],{"class":207}," computed",[106,1280,309],{"class":148},[106,1282,137],{"class":126},[106,1284,336],{"class":297},[106,1286,127],{"class":126},[106,1288,1289,1291,1294,1296,1299,1302,1305,1307,1310,1312,1315,1317,1320,1322,1324,1326],{"class":108,"line":165},[106,1290,298],{"class":297},[106,1292,1293],{"class":301}," val",[106,1295,304],{"class":182},[106,1297,1298],{"class":182}," typeof",[106,1300,1301],{"class":246}," input",[106,1303,1304],{"class":182}," ===",[106,1306,472],{"class":262},[106,1308,1309],{"class":266},"number",[106,1311,477],{"class":262},[106,1313,1314],{"class":182}," ?",[106,1316,1301],{"class":246},[106,1318,1319],{"class":182}," :",[106,1321,1301],{"class":246},[106,1323,204],{"class":126},[106,1325,523],{"class":246},[106,1327,272],{"class":126},[106,1329,1330,1332,1334,1337,1340],{"class":108,"line":191},[106,1331,364],{"class":119},[106,1333,1293],{"class":246},[106,1335,1336],{"class":182}," *",[106,1338,1339],{"class":496}," 2",[106,1341,272],{"class":126},[106,1343,1344,1347,1349],{"class":108,"line":219},[106,1345,1346],{"class":126},"  }",[106,1348,315],{"class":148},[106,1350,272],{"class":126},[106,1352,1353,1355,1357,1359],{"class":108,"line":225},[106,1354,965],{"class":119},[106,1356,140],{"class":126},[106,1358,1273],{"class":246},[106,1360,344],{"class":126},[106,1362,1363],{"class":108,"line":232},[106,1364,222],{"class":126},[24,1366,1367,1368,1371],{},"Vue 3.3 提供了 ",[55,1369,1370],{},"toValue()"," 帮助函数，简化这种模式：",[97,1373,1375],{"className":99,"code":1374,"language":101,"meta":102,"style":102},"import { toValue } from \"vue\";\nfunction useDouble(input) {\n  const double = computed(() => toValue(input) * 2);\n  return { double };\n}\n",[55,1376,1377,1398,1412,1446,1456],{"__ignoreMap":102},[106,1378,1379,1381,1383,1386,1388,1390,1392,1394,1396],{"class":108,"line":109},[106,1380,241],{"class":119},[106,1382,140],{"class":126},[106,1384,1385],{"class":246}," toValue",[106,1387,256],{"class":126},[106,1389,259],{"class":119},[106,1391,472],{"class":262},[106,1393,12],{"class":266},[106,1395,477],{"class":262},[106,1397,272],{"class":126},[106,1399,1400,1402,1404,1406,1408,1410],{"class":108,"line":116},[106,1401,506],{"class":297},[106,1403,1252],{"class":207},[106,1405,309],{"class":126},[106,1407,1257],{"class":885},[106,1409,315],{"class":126},[106,1411,127],{"class":126},[106,1413,1414,1416,1418,1420,1422,1424,1426,1428,1430,1432,1434,1437,1440,1442,1444],{"class":108,"line":130},[106,1415,900],{"class":297},[106,1417,1273],{"class":301},[106,1419,304],{"class":182},[106,1421,1278],{"class":207},[106,1423,309],{"class":148},[106,1425,137],{"class":126},[106,1427,336],{"class":297},[106,1429,1385],{"class":207},[106,1431,309],{"class":148},[106,1433,1257],{"class":246},[106,1435,1436],{"class":148},") ",[106,1438,1439],{"class":182},"*",[106,1441,1339],{"class":496},[106,1443,315],{"class":148},[106,1445,272],{"class":126},[106,1447,1448,1450,1452,1454],{"class":108,"line":165},[106,1449,965],{"class":119},[106,1451,140],{"class":126},[106,1453,1273],{"class":246},[106,1455,344],{"class":126},[106,1457,1458],{"class":108,"line":191},[106,1459,222],{"class":126},[42,1461,1463],{"id":1462},"_4-异步组合函数模式处理加载状态错误取消请求的标准化结构","4. 异步组合函数模式：处理加载状态、错误、取消请求的标准化结构",[24,1465,1466,1467,62,1469,62,1472,1475,1476,1479],{},"典型的异步 composable 返回一个对象，包含 ",[55,1468,82],{},[55,1470,1471],{},"error",[55,1473,1474],{},"loading"," 以及一个 ",[55,1477,1478],{},"execute"," 函数（用于手动触发或重试）。",[97,1481,1483],{"className":99,"code":1482,"language":101,"meta":102,"style":102},"import { ref, isRef, unref } from \"vue\";\n\nfunction useFetch(url, options = {}) {\n  const data = ref(null);\n  const error = ref(null);\n  const loading = ref(false);\n\n  const execute = async (overrideUrl) => {\n    const targetUrl = overrideUrl ?? unref(url);\n    loading.value = true;\n    error.value = null;\n    try {\n      const res = await fetch(targetUrl, options);\n      if (!res.ok) throw new Error(res.statusText);\n      data.value = await res.json();\n    } catch (e) {\n      error.value = e.message;\n    } finally {\n      loading.value = false;\n    }\n  };\n\n  \u002F\u002F 自动请求（可选通过 immediate 控制）\n  if (options.immediate !== false) execute();\n\n  \u002F\u002F 取消请求（使用 AbortController）\n  let abortController;\n  const abort = () => abortController?.abort();\n  \u002F\u002F 改写 execute 支持取消，此处省略完整实现\n\n  return { data, error, loading, execute, abort };\n}\n",[55,1484,1485,1515,1519,1543,1562,1581,1602,1606,1629,1654,1670,1685,1692,1721,1763,1787,1804,1824,1834,1851,1856,1862,1867,1873,1902,1907,1913,1924,1950,1956,1961,1988],{"__ignoreMap":102},[106,1486,1487,1489,1491,1493,1495,1498,1500,1503,1505,1507,1509,1511,1513],{"class":108,"line":109},[106,1488,241],{"class":119},[106,1490,140],{"class":126},[106,1492,247],{"class":246},[106,1494,250],{"class":126},[106,1496,1497],{"class":246}," isRef",[106,1499,250],{"class":126},[106,1501,1502],{"class":246}," unref",[106,1504,256],{"class":126},[106,1506,259],{"class":119},[106,1508,472],{"class":262},[106,1510,12],{"class":266},[106,1512,477],{"class":262},[106,1514,272],{"class":126},[106,1516,1517],{"class":108,"line":116},[106,1518,229],{"emptyLinePlaceholder":228},[106,1520,1521,1523,1526,1528,1531,1533,1536,1538,1541],{"class":108,"line":130},[106,1522,506],{"class":297},[106,1524,1525],{"class":207}," useFetch",[106,1527,309],{"class":126},[106,1529,1530],{"class":885},"url",[106,1532,250],{"class":126},[106,1534,1535],{"class":885}," options",[106,1537,304],{"class":182},[106,1539,1540],{"class":126}," {})",[106,1542,127],{"class":126},[106,1544,1545,1547,1550,1552,1554,1556,1558,1560],{"class":108,"line":165},[106,1546,900],{"class":297},[106,1548,1549],{"class":301}," data",[106,1551,304],{"class":182},[106,1553,247],{"class":207},[106,1555,309],{"class":148},[106,1557,312],{"class":155},[106,1559,315],{"class":148},[106,1561,272],{"class":126},[106,1563,1564,1566,1569,1571,1573,1575,1577,1579],{"class":108,"line":191},[106,1565,900],{"class":297},[106,1567,1568],{"class":301}," error",[106,1570,304],{"class":182},[106,1572,247],{"class":207},[106,1574,309],{"class":148},[106,1576,312],{"class":155},[106,1578,315],{"class":148},[106,1580,272],{"class":126},[106,1582,1583,1585,1588,1590,1592,1594,1598,1600],{"class":108,"line":219},[106,1584,900],{"class":297},[106,1586,1587],{"class":301}," loading",[106,1589,304],{"class":182},[106,1591,247],{"class":207},[106,1593,309],{"class":148},[106,1595,1597],{"class":1596},"syTEX","false",[106,1599,315],{"class":148},[106,1601,272],{"class":126},[106,1603,1604],{"class":108,"line":225},[106,1605,229],{"emptyLinePlaceholder":228},[106,1607,1608,1610,1613,1615,1617,1620,1623,1625,1627],{"class":108,"line":232},[106,1609,900],{"class":297},[106,1611,1612],{"class":325}," execute",[106,1614,304],{"class":182},[106,1616,330],{"class":297},[106,1618,1619],{"class":126}," (",[106,1621,1622],{"class":885},"overrideUrl",[106,1624,315],{"class":126},[106,1626,336],{"class":297},[106,1628,127],{"class":126},[106,1630,1631,1633,1636,1638,1641,1644,1646,1648,1650,1652],{"class":108,"line":238},[106,1632,298],{"class":297},[106,1634,1635],{"class":301}," targetUrl",[106,1637,304],{"class":182},[106,1639,1640],{"class":246}," overrideUrl",[106,1642,1643],{"class":182}," ??",[106,1645,1502],{"class":207},[106,1647,309],{"class":148},[106,1649,1530],{"class":246},[106,1651,315],{"class":148},[106,1653,272],{"class":126},[106,1655,1656,1659,1661,1663,1665,1668],{"class":108,"line":275},[106,1657,1658],{"class":246},"    loading",[106,1660,204],{"class":126},[106,1662,523],{"class":246},[106,1664,304],{"class":182},[106,1666,1667],{"class":1596}," true",[106,1669,272],{"class":126},[106,1671,1672,1675,1677,1679,1681,1683],{"class":108,"line":284},[106,1673,1674],{"class":246},"    error",[106,1676,204],{"class":126},[106,1678,523],{"class":246},[106,1680,304],{"class":182},[106,1682,156],{"class":155},[106,1684,272],{"class":126},[106,1686,1687,1690],{"class":108,"line":294},[106,1688,1689],{"class":119},"    try",[106,1691,127],{"class":126},[106,1693,1694,1697,1700,1702,1705,1708,1710,1713,1715,1717,1719],{"class":108,"line":320},[106,1695,1696],{"class":297},"      const",[106,1698,1699],{"class":301}," res",[106,1701,304],{"class":182},[106,1703,1704],{"class":119}," await",[106,1706,1707],{"class":207}," fetch",[106,1709,309],{"class":148},[106,1711,1712],{"class":246},"targetUrl",[106,1714,250],{"class":126},[106,1716,1535],{"class":246},[106,1718,315],{"class":148},[106,1720,272],{"class":126},[106,1722,1723,1726,1728,1731,1734,1736,1739,1741,1744,1747,1750,1752,1754,1756,1759,1761],{"class":108,"line":347},[106,1724,1725],{"class":119},"      if",[106,1727,1619],{"class":148},[106,1729,1730],{"class":182},"!",[106,1732,1733],{"class":246},"res",[106,1735,204],{"class":126},[106,1737,1738],{"class":246},"ok",[106,1740,1436],{"class":148},[106,1742,1743],{"class":119},"throw",[106,1745,1746],{"class":182}," new",[106,1748,1749],{"class":207}," Error",[106,1751,309],{"class":148},[106,1753,1733],{"class":246},[106,1755,204],{"class":126},[106,1757,1758],{"class":246},"statusText",[106,1760,315],{"class":148},[106,1762,272],{"class":126},[106,1764,1765,1768,1770,1772,1774,1776,1778,1780,1783,1785],{"class":108,"line":361},[106,1766,1767],{"class":246},"      data",[106,1769,204],{"class":126},[106,1771,523],{"class":246},[106,1773,304],{"class":182},[106,1775,1704],{"class":119},[106,1777,1699],{"class":246},[106,1779,204],{"class":126},[106,1781,1782],{"class":207},"json",[106,1784,137],{"class":148},[106,1786,272],{"class":126},[106,1788,1789,1792,1795,1797,1799,1801],{"class":108,"line":377},[106,1790,1791],{"class":126},"    }",[106,1793,1794],{"class":119}," catch",[106,1796,1619],{"class":148},[106,1798,1067],{"class":246},[106,1800,1436],{"class":148},[106,1802,1803],{"class":126},"{\n",[106,1805,1806,1809,1811,1813,1815,1817,1819,1822],{"class":108,"line":383},[106,1807,1808],{"class":246},"      error",[106,1810,204],{"class":126},[106,1812,523],{"class":246},[106,1814,304],{"class":182},[106,1816,1085],{"class":246},[106,1818,204],{"class":126},[106,1820,1821],{"class":246},"message",[106,1823,272],{"class":126},[106,1825,1827,1829,1832],{"class":108,"line":1826},18,[106,1828,1791],{"class":126},[106,1830,1831],{"class":119}," finally",[106,1833,127],{"class":126},[106,1835,1837,1840,1842,1844,1846,1849],{"class":108,"line":1836},19,[106,1838,1839],{"class":246},"      loading",[106,1841,204],{"class":126},[106,1843,523],{"class":246},[106,1845,304],{"class":182},[106,1847,1848],{"class":1596}," false",[106,1850,272],{"class":126},[106,1852,1854],{"class":108,"line":1853},20,[106,1855,662],{"class":126},[106,1857,1859],{"class":108,"line":1858},21,[106,1860,1861],{"class":126},"  };\n",[106,1863,1865],{"class":108,"line":1864},22,[106,1866,229],{"emptyLinePlaceholder":228},[106,1868,1870],{"class":108,"line":1869},23,[106,1871,1872],{"class":112},"  \u002F\u002F 自动请求（可选通过 immediate 控制）\n",[106,1874,1876,1879,1881,1884,1886,1889,1892,1894,1896,1898,1900],{"class":108,"line":1875},24,[106,1877,1878],{"class":119},"  if",[106,1880,1619],{"class":148},[106,1882,1883],{"class":246},"options",[106,1885,204],{"class":126},[106,1887,1888],{"class":246},"immediate",[106,1890,1891],{"class":182}," !==",[106,1893,1848],{"class":1596},[106,1895,1436],{"class":148},[106,1897,1478],{"class":207},[106,1899,137],{"class":148},[106,1901,272],{"class":126},[106,1903,1905],{"class":108,"line":1904},25,[106,1906,229],{"emptyLinePlaceholder":228},[106,1908,1910],{"class":108,"line":1909},26,[106,1911,1912],{"class":112},"  \u002F\u002F 取消请求（使用 AbortController）\n",[106,1914,1916,1919,1922],{"class":108,"line":1915},27,[106,1917,1918],{"class":297},"  let",[106,1920,1921],{"class":246}," abortController",[106,1923,272],{"class":126},[106,1925,1927,1929,1932,1934,1936,1938,1940,1943,1946,1948],{"class":108,"line":1926},28,[106,1928,900],{"class":297},[106,1930,1931],{"class":325}," abort",[106,1933,304],{"class":182},[106,1935,333],{"class":126},[106,1937,336],{"class":297},[106,1939,1921],{"class":246},[106,1941,1942],{"class":126},"?.",[106,1944,1945],{"class":207},"abort",[106,1947,137],{"class":148},[106,1949,272],{"class":126},[106,1951,1953],{"class":108,"line":1952},29,[106,1954,1955],{"class":112},"  \u002F\u002F 改写 execute 支持取消，此处省略完整实现\n",[106,1957,1959],{"class":108,"line":1958},30,[106,1960,229],{"emptyLinePlaceholder":228},[106,1962,1964,1966,1968,1970,1972,1974,1976,1978,1980,1982,1984,1986],{"class":108,"line":1963},31,[106,1965,965],{"class":119},[106,1967,140],{"class":126},[106,1969,1549],{"class":246},[106,1971,250],{"class":126},[106,1973,1568],{"class":246},[106,1975,250],{"class":126},[106,1977,1587],{"class":246},[106,1979,250],{"class":126},[106,1981,1612],{"class":246},[106,1983,250],{"class":126},[106,1985,1931],{"class":246},[106,1987,344],{"class":126},[106,1989,1991],{"class":108,"line":1990},32,[106,1992,222],{"class":126},[24,1994,1995,1998],{},[50,1996,1997],{},"使用示例","：",[97,2000,2002],{"className":99,"code":2001,"language":101,"meta":102,"style":102},"const { data, loading, error, execute } = useFetch(\"\u002Fapi\u002Fuser\", { immediate: false });\nexecute(); \u002F\u002F 手动触发\n",[55,2003,2004,2056],{"__ignoreMap":102},[106,2005,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028,2030,2032,2034,2037,2039,2041,2043,2046,2048,2050,2052,2054],{"class":108,"line":109},[106,2007,484],{"class":297},[106,2009,140],{"class":126},[106,2011,1549],{"class":301},[106,2013,250],{"class":126},[106,2015,1587],{"class":301},[106,2017,250],{"class":126},[106,2019,1568],{"class":301},[106,2021,250],{"class":126},[106,2023,1612],{"class":301},[106,2025,256],{"class":126},[106,2027,304],{"class":182},[106,2029,1525],{"class":207},[106,2031,309],{"class":246},[106,2033,477],{"class":262},[106,2035,2036],{"class":266},"\u002Fapi\u002Fuser",[106,2038,477],{"class":262},[106,2040,250],{"class":126},[106,2042,140],{"class":126},[106,2044,2045],{"class":148}," immediate",[106,2047,152],{"class":126},[106,2049,1848],{"class":1596},[106,2051,256],{"class":126},[106,2053,315],{"class":246},[106,2055,272],{"class":126},[106,2057,2058,2060,2062,2064],{"class":108,"line":116},[106,2059,1478],{"class":207},[106,2061,137],{"class":246},[106,2063,213],{"class":126},[106,2065,2066],{"class":112}," \u002F\u002F 手动触发\n",[34,2068,2070],{"id":2069},"四从-mixins-到-composables-的演进","四、从 Mixins 到 Composables 的演进",[42,2072,2074],{"id":2073},"mixins-的局限性vue-2","Mixins 的局限性（Vue 2）",[404,2076,2077,2083,2089,2095],{},[407,2078,2079,2082],{},[50,2080,2081],{},"命名冲突","：多个 mixin 可能定义同名的 data\u002Fmethods，发生静默覆盖。",[407,2084,2085,2088],{},[50,2086,2087],{},"来源不透明","：组件中使用的变量来自哪个 mixin 难以追踪。",[407,2090,2091,2094],{},[50,2092,2093],{},"逻辑分散","：一个 mixin 的代码可能分布在多个生命周期钩子中，难以理解。",[407,2096,2097,2100],{},[50,2098,2099],{},"隐式依赖","：mixin 可能依赖于组件中的某些属性或方法，没有显式声明，造成脆弱性。",[42,2102,2104],{"id":2103},"composables-如何解决上述问题","Composables 如何解决上述问题",[404,2106,2107,2113,2119,2125],{},[407,2108,2109,2112],{},[50,2110,2111],{},"显式引用","：Composable 的返回值通过解构赋值显式引入，所有使用到的变量都来自明确的函数调用，不会有命名冲突（用户可自行重命名）。",[407,2114,2115,2118],{},[50,2116,2117],{},"命名控制","：由于是普通函数，完全由调用者控制变量名。",[407,2120,2121,2124],{},[50,2122,2123],{},"逻辑边界清晰","：每个 composable 内部的逻辑高度内聚，通过函数调用顺序体现依赖关系。",[407,2126,2127,2130],{},[50,2128,2129],{},"无隐式依赖","：Composable 接收参数显式传入所需数据，依赖关系透明。",[97,2132,2134],{"className":99,"code":2133,"language":101,"meta":102,"style":102},"\u002F\u002F Mixins 的问题\nmixins: [userMixin, loggerMixin];\nthis.userName; \u002F\u002F 来自哪个 mixin？被覆盖了没？\n\n\u002F\u002F Composables 的解决方案\nconst { name: userName } = useUser();\nconst { log } = useLogger();\n",[55,2135,2136,2141,2159,2174,2178,2183,2209],{"__ignoreMap":102},[106,2137,2138],{"class":108,"line":109},[106,2139,2140],{"class":112},"\u002F\u002F Mixins 的问题\n",[106,2142,2143,2147,2149,2152,2154,2157],{"class":108,"line":116},[106,2144,2146],{"class":2145},"sbgvK","mixins",[106,2148,152],{"class":126},[106,2150,2151],{"class":246}," [userMixin",[106,2153,250],{"class":126},[106,2155,2156],{"class":246}," loggerMixin]",[106,2158,272],{"class":126},[106,2160,2161,2164,2166,2169,2171],{"class":108,"line":130},[106,2162,2163],{"class":155},"this",[106,2165,204],{"class":126},[106,2167,2168],{"class":246},"userName",[106,2170,213],{"class":126},[106,2172,2173],{"class":112}," \u002F\u002F 来自哪个 mixin？被覆盖了没？\n",[106,2175,2176],{"class":108,"line":165},[106,2177,229],{"emptyLinePlaceholder":228},[106,2179,2180],{"class":108,"line":191},[106,2181,2182],{"class":112},"\u002F\u002F Composables 的解决方案\n",[106,2184,2185,2187,2189,2193,2195,2198,2200,2202,2205,2207],{"class":108,"line":219},[106,2186,484],{"class":297},[106,2188,140],{"class":126},[106,2190,2192],{"class":2191},"sucvu"," name",[106,2194,152],{"class":126},[106,2196,2197],{"class":301}," userName",[106,2199,256],{"class":126},[106,2201,304],{"class":182},[106,2203,2204],{"class":207}," useUser",[106,2206,137],{"class":246},[106,2208,272],{"class":126},[106,2210,2211,2213,2215,2218,2220,2222,2225,2227],{"class":108,"line":225},[106,2212,484],{"class":297},[106,2214,140],{"class":126},[106,2216,2217],{"class":301}," log",[106,2219,256],{"class":126},[106,2221,304],{"class":182},[106,2223,2224],{"class":207}," useLogger",[106,2226,137],{"class":246},[106,2228,272],{"class":126},[34,2230,2232],{"id":2231},"五跨组件状态共享的-composables","五、跨组件状态共享的 Composables",[24,2234,2235,2236,2239],{},"在 Vue 中，如果需要多个组件共享同一份响应式状态，可以将响应式状态定义在",[50,2237,2238],{},"模块作用域","（而非 composable 函数内部），然后 composable 函数引用并暴露该状态。",[97,2241,2243],{"className":99,"code":2242,"language":101,"meta":102,"style":102},"\u002F\u002F stores\u002Fcounter.js\nimport { ref } from \"vue\";\nconst count = ref(0);\nexport function useCounter() {\n  const increment = () => count.value++;\n  const decrement = () => count.value--;\n  return { count, increment, decrement };\n}\n",[55,2244,2245,2250,2270,2288,2301,2323,2345,2363],{"__ignoreMap":102},[106,2246,2247],{"class":108,"line":109},[106,2248,2249],{"class":112},"\u002F\u002F stores\u002Fcounter.js\n",[106,2251,2252,2254,2256,2258,2260,2262,2264,2266,2268],{"class":108,"line":116},[106,2253,241],{"class":119},[106,2255,140],{"class":126},[106,2257,247],{"class":246},[106,2259,256],{"class":126},[106,2261,259],{"class":119},[106,2263,472],{"class":262},[106,2265,12],{"class":266},[106,2267,477],{"class":262},[106,2269,272],{"class":126},[106,2271,2272,2274,2276,2278,2280,2282,2284,2286],{"class":108,"line":130},[106,2273,484],{"class":297},[106,2275,487],{"class":301},[106,2277,304],{"class":182},[106,2279,247],{"class":207},[106,2281,309],{"class":246},[106,2283,497],{"class":496},[106,2285,315],{"class":246},[106,2287,272],{"class":126},[106,2289,2290,2292,2295,2297,2299],{"class":108,"line":165},[106,2291,120],{"class":119},[106,2293,2294],{"class":297}," function",[106,2296,880],{"class":207},[106,2298,137],{"class":126},[106,2300,127],{"class":126},[106,2302,2303,2305,2307,2309,2311,2313,2315,2317,2319,2321],{"class":108,"line":191},[106,2304,900],{"class":297},[106,2306,509],{"class":325},[106,2308,304],{"class":182},[106,2310,333],{"class":126},[106,2312,336],{"class":297},[106,2314,487],{"class":246},[106,2316,204],{"class":126},[106,2318,523],{"class":246},[106,2320,526],{"class":182},[106,2322,272],{"class":126},[106,2324,2325,2327,2329,2331,2333,2335,2337,2339,2341,2343],{"class":108,"line":219},[106,2326,900],{"class":297},[106,2328,943],{"class":325},[106,2330,304],{"class":182},[106,2332,333],{"class":126},[106,2334,336],{"class":297},[106,2336,487],{"class":246},[106,2338,204],{"class":126},[106,2340,523],{"class":246},[106,2342,958],{"class":182},[106,2344,272],{"class":126},[106,2346,2347,2349,2351,2353,2355,2357,2359,2361],{"class":108,"line":225},[106,2348,965],{"class":119},[106,2350,140],{"class":126},[106,2352,487],{"class":246},[106,2354,250],{"class":126},[106,2356,509],{"class":246},[106,2358,250],{"class":126},[106,2360,943],{"class":246},[106,2362,344],{"class":126},[106,2364,2365],{"class":108,"line":232},[106,2366,222],{"class":126},[24,2368,2369,2370,2373,2374,2377,2378,2381],{},"多个组件调用 ",[55,2371,2372],{},"useCounter()"," 将获得",[50,2375,2376],{},"同一个"," ",[55,2379,2380],{},"count"," ref 实例，从而实现跨组件状态共享。这与 Pinia 的实现原理类似（Pinia 在底层也是利用了这种模块单例模式）。",[24,2383,2384,2387],{},[50,2385,2386],{},"注意","：这种方式创建的是全局单例，适用于不需要组件生命周期隔离的场景。若需要每个组件独立的状态，则应将状态定义在 composable 函数内部（即每次调用都创建新的 ref）。",[34,2389,2391],{"id":2390},"六组合式-api-的-typescript-类型推导","六、组合式 API 的 TypeScript 类型推导",[24,2393,2394],{},"组合式 API 在设计之初就对 TypeScript 提供了一流支持。通过泛型可以创建类型安全的 composable。",[42,2396,2398],{"id":2397},"_1-基本类型推导","1. 基本类型推导",[24,2400,2401,62,2403,62,2406,2408],{},[55,2402,61],{},[55,2404,2405],{},"reactive",[55,2407,65],{}," 等 API 可以自动推导类型，也支持泛型显式指定。",[97,2410,2414],{"className":2411,"code":2412,"language":2413,"meta":102,"style":102},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","const count = ref(0); \u002F\u002F Ref\u003Cnumber>\nconst user = ref\u003CUser | null>(null); \u002F\u002F 显式包含 null，否则 TS 会报「null 不能赋值给 User」\n\nfunction useNumber(initial: number) {\n  const value = ref(initial);\n  const double = computed(() => value.value * 2);\n  return { value, double }; \u002F\u002F 类型自动推导\n}\n","ts",[55,2415,2416,2437,2471,2475,2496,2515,2545,2562],{"__ignoreMap":102},[106,2417,2418,2420,2422,2424,2426,2428,2430,2432,2434],{"class":108,"line":109},[106,2419,484],{"class":297},[106,2421,487],{"class":301},[106,2423,304],{"class":182},[106,2425,247],{"class":207},[106,2427,309],{"class":246},[106,2429,497],{"class":496},[106,2431,315],{"class":246},[106,2433,213],{"class":126},[106,2435,2436],{"class":112}," \u002F\u002F Ref\u003Cnumber>\n",[106,2438,2439,2441,2443,2445,2447,2449,2452,2455,2458,2460,2462,2464,2466,2468],{"class":108,"line":116},[106,2440,484],{"class":297},[106,2442,149],{"class":301},[106,2444,304],{"class":182},[106,2446,247],{"class":207},[106,2448,446],{"class":126},[106,2450,2451],{"class":2145},"User",[106,2453,2454],{"class":182}," |",[106,2456,156],{"class":2457},"sZMiF",[106,2459,574],{"class":126},[106,2461,309],{"class":246},[106,2463,312],{"class":155},[106,2465,315],{"class":246},[106,2467,213],{"class":126},[106,2469,2470],{"class":112}," \u002F\u002F 显式包含 null，否则 TS 会报「null 不能赋值给 User」\n",[106,2472,2473],{"class":108,"line":130},[106,2474,229],{"emptyLinePlaceholder":228},[106,2476,2477,2479,2482,2484,2487,2489,2492,2494],{"class":108,"line":165},[106,2478,506],{"class":297},[106,2480,2481],{"class":207}," useNumber",[106,2483,309],{"class":126},[106,2485,2486],{"class":885},"initial",[106,2488,152],{"class":182},[106,2490,2491],{"class":2457}," number",[106,2493,315],{"class":126},[106,2495,127],{"class":126},[106,2497,2498,2500,2503,2505,2507,2509,2511,2513],{"class":108,"line":191},[106,2499,900],{"class":297},[106,2501,2502],{"class":301}," value",[106,2504,304],{"class":182},[106,2506,247],{"class":207},[106,2508,309],{"class":148},[106,2510,2486],{"class":246},[106,2512,315],{"class":148},[106,2514,272],{"class":126},[106,2516,2517,2519,2521,2523,2525,2527,2529,2531,2533,2535,2537,2539,2541,2543],{"class":108,"line":219},[106,2518,900],{"class":297},[106,2520,1273],{"class":301},[106,2522,304],{"class":182},[106,2524,1278],{"class":207},[106,2526,309],{"class":148},[106,2528,137],{"class":126},[106,2530,336],{"class":297},[106,2532,2502],{"class":246},[106,2534,204],{"class":126},[106,2536,523],{"class":246},[106,2538,1336],{"class":182},[106,2540,1339],{"class":496},[106,2542,315],{"class":148},[106,2544,272],{"class":126},[106,2546,2547,2549,2551,2553,2555,2557,2559],{"class":108,"line":225},[106,2548,965],{"class":119},[106,2550,140],{"class":126},[106,2552,2502],{"class":246},[106,2554,250],{"class":126},[106,2556,1273],{"class":246},[106,2558,159],{"class":126},[106,2560,2561],{"class":112}," \u002F\u002F 类型自动推导\n",[106,2563,2564],{"class":108,"line":232},[106,2565,222],{"class":126},[42,2567,2569],{"id":2568},"_2-泛型组合函数设计","2. 泛型组合函数设计",[24,2571,2572],{},"自定义 composable 可以定义泛型参数，以支持灵活的类型输入输出。",[97,2574,2576],{"className":2411,"code":2575,"language":2413,"meta":102,"style":102},"function useFetch\u003CT>(url: string | Ref\u003Cstring>): {\n  data: Ref\u003CT | null>;\n  error: Ref\u003Cstring | null>;\n  loading: Ref\u003Cboolean>;\n} {\n  const data = ref\u003CT | null>(null);\n  const error = ref\u003Cstring | null>(null);\n  const loading = ref(false);\n  \u002F\u002F ... 实现\n  return { data, error, loading };\n}\n\n\u002F\u002F 使用时指定类型\nconst { data } = useFetch\u003CUser[]>(\"\u002Fapi\u002Fusers\");\n\u002F\u002F data 的类型为 Ref\u003CUser[] | null>\n",[55,2577,2578,2616,2635,2654,2670,2676,2704,2732,2750,2755,2773,2777,2781,2786,2822],{"__ignoreMap":102},[106,2579,2580,2582,2584,2586,2589,2592,2594,2596,2599,2601,2604,2606,2609,2612,2614],{"class":108,"line":109},[106,2581,506],{"class":297},[106,2583,1525],{"class":207},[106,2585,446],{"class":126},[106,2587,2588],{"class":2145},"T",[106,2590,2591],{"class":126},">(",[106,2593,1530],{"class":885},[106,2595,152],{"class":182},[106,2597,2598],{"class":2457}," string",[106,2600,2454],{"class":182},[106,2602,2603],{"class":2145}," Ref",[106,2605,446],{"class":126},[106,2607,2608],{"class":2457},"string",[106,2610,2611],{"class":126},">)",[106,2613,152],{"class":182},[106,2615,127],{"class":126},[106,2617,2618,2620,2622,2624,2626,2628,2630,2632],{"class":108,"line":116},[106,2619,134],{"class":2191},[106,2621,152],{"class":182},[106,2623,2603],{"class":2145},[106,2625,446],{"class":126},[106,2627,2588],{"class":2145},[106,2629,2454],{"class":182},[106,2631,156],{"class":2457},[106,2633,2634],{"class":126},">;\n",[106,2636,2637,2640,2642,2644,2646,2648,2650,2652],{"class":108,"line":130},[106,2638,2639],{"class":2191},"  error",[106,2641,152],{"class":182},[106,2643,2603],{"class":2145},[106,2645,446],{"class":126},[106,2647,2608],{"class":2457},[106,2649,2454],{"class":182},[106,2651,156],{"class":2457},[106,2653,2634],{"class":126},[106,2655,2656,2659,2661,2663,2665,2668],{"class":108,"line":165},[106,2657,2658],{"class":2191},"  loading",[106,2660,152],{"class":182},[106,2662,2603],{"class":2145},[106,2664,446],{"class":126},[106,2666,2667],{"class":2457},"boolean",[106,2669,2634],{"class":126},[106,2671,2672,2674],{"class":108,"line":191},[106,2673,186],{"class":126},[106,2675,127],{"class":126},[106,2677,2678,2680,2682,2684,2686,2688,2690,2692,2694,2696,2698,2700,2702],{"class":108,"line":219},[106,2679,900],{"class":297},[106,2681,1549],{"class":301},[106,2683,304],{"class":182},[106,2685,247],{"class":207},[106,2687,446],{"class":126},[106,2689,2588],{"class":2145},[106,2691,2454],{"class":182},[106,2693,156],{"class":2457},[106,2695,574],{"class":126},[106,2697,309],{"class":148},[106,2699,312],{"class":155},[106,2701,315],{"class":148},[106,2703,272],{"class":126},[106,2705,2706,2708,2710,2712,2714,2716,2718,2720,2722,2724,2726,2728,2730],{"class":108,"line":225},[106,2707,900],{"class":297},[106,2709,1568],{"class":301},[106,2711,304],{"class":182},[106,2713,247],{"class":207},[106,2715,446],{"class":126},[106,2717,2608],{"class":2457},[106,2719,2454],{"class":182},[106,2721,156],{"class":2457},[106,2723,574],{"class":126},[106,2725,309],{"class":148},[106,2727,312],{"class":155},[106,2729,315],{"class":148},[106,2731,272],{"class":126},[106,2733,2734,2736,2738,2740,2742,2744,2746,2748],{"class":108,"line":232},[106,2735,900],{"class":297},[106,2737,1587],{"class":301},[106,2739,304],{"class":182},[106,2741,247],{"class":207},[106,2743,309],{"class":148},[106,2745,1597],{"class":1596},[106,2747,315],{"class":148},[106,2749,272],{"class":126},[106,2751,2752],{"class":108,"line":238},[106,2753,2754],{"class":112},"  \u002F\u002F ... 实现\n",[106,2756,2757,2759,2761,2763,2765,2767,2769,2771],{"class":108,"line":275},[106,2758,965],{"class":119},[106,2760,140],{"class":126},[106,2762,1549],{"class":246},[106,2764,250],{"class":126},[106,2766,1568],{"class":246},[106,2768,250],{"class":126},[106,2770,1587],{"class":246},[106,2772,344],{"class":126},[106,2774,2775],{"class":108,"line":284},[106,2776,222],{"class":126},[106,2778,2779],{"class":108,"line":294},[106,2780,229],{"emptyLinePlaceholder":228},[106,2782,2783],{"class":108,"line":320},[106,2784,2785],{"class":112},"\u002F\u002F 使用时指定类型\n",[106,2787,2788,2790,2792,2794,2796,2798,2800,2802,2804,2807,2809,2811,2813,2816,2818,2820],{"class":108,"line":347},[106,2789,484],{"class":297},[106,2791,140],{"class":126},[106,2793,1549],{"class":301},[106,2795,256],{"class":126},[106,2797,304],{"class":182},[106,2799,1525],{"class":207},[106,2801,446],{"class":126},[106,2803,2451],{"class":2145},[106,2805,2806],{"class":246},"[]",[106,2808,574],{"class":126},[106,2810,309],{"class":246},[106,2812,477],{"class":262},[106,2814,2815],{"class":266},"\u002Fapi\u002Fusers",[106,2817,477],{"class":262},[106,2819,315],{"class":246},[106,2821,272],{"class":126},[106,2823,2824],{"class":108,"line":361},[106,2825,2826],{"class":112},"\u002F\u002F data 的类型为 Ref\u003CUser[] | null>\n",[42,2828,2830],{"id":2829},"_3-处理-ref-作为参数时的类型","3. 处理 ref 作为参数时的类型",[24,2832,2833,2834,1237,2837,2840],{},"使用 ",[55,2835,2836],{},"MaybeRef",[55,2838,2839],{},"MaybeRefOrGetter"," 工具类型（Vue 3.3+ 提供的类型实用程序）。",[97,2842,2844],{"className":2411,"code":2843,"language":2413,"meta":102,"style":102},"import type { MaybeRef } from \"vue\";\n\nfunction useDouble(input: MaybeRef\u003Cnumber>) {\n  const double = computed(() => unref(input) * 2);\n  return { double };\n}\n",[55,2845,2846,2870,2874,2896,2928,2938],{"__ignoreMap":102},[106,2847,2848,2850,2853,2855,2858,2860,2862,2864,2866,2868],{"class":108,"line":109},[106,2849,241],{"class":119},[106,2851,2852],{"class":119}," type",[106,2854,140],{"class":126},[106,2856,2857],{"class":246}," MaybeRef",[106,2859,256],{"class":126},[106,2861,259],{"class":119},[106,2863,472],{"class":262},[106,2865,12],{"class":266},[106,2867,477],{"class":262},[106,2869,272],{"class":126},[106,2871,2872],{"class":108,"line":116},[106,2873,229],{"emptyLinePlaceholder":228},[106,2875,2876,2878,2880,2882,2884,2886,2888,2890,2892,2894],{"class":108,"line":130},[106,2877,506],{"class":297},[106,2879,1252],{"class":207},[106,2881,309],{"class":126},[106,2883,1257],{"class":885},[106,2885,152],{"class":182},[106,2887,2857],{"class":2145},[106,2889,446],{"class":126},[106,2891,1309],{"class":2457},[106,2893,2611],{"class":126},[106,2895,127],{"class":126},[106,2897,2898,2900,2902,2904,2906,2908,2910,2912,2914,2916,2918,2920,2922,2924,2926],{"class":108,"line":165},[106,2899,900],{"class":297},[106,2901,1273],{"class":301},[106,2903,304],{"class":182},[106,2905,1278],{"class":207},[106,2907,309],{"class":148},[106,2909,137],{"class":126},[106,2911,336],{"class":297},[106,2913,1502],{"class":207},[106,2915,309],{"class":148},[106,2917,1257],{"class":246},[106,2919,1436],{"class":148},[106,2921,1439],{"class":182},[106,2923,1339],{"class":496},[106,2925,315],{"class":148},[106,2927,272],{"class":126},[106,2929,2930,2932,2934,2936],{"class":108,"line":191},[106,2931,965],{"class":119},[106,2933,140],{"class":126},[106,2935,1273],{"class":246},[106,2937,344],{"class":126},[106,2939,2940],{"class":108,"line":219},[106,2941,222],{"class":126},[42,2943,2945],{"id":2944},"_4-返回值类型简化","4. 返回值类型简化",[24,2947,2948,2949,2952],{},"可以使用 ",[55,2950,2951],{},"ReturnType"," 或显式定义返回类型，提高可读性。",[97,2954,2956],{"className":2411,"code":2955,"language":2413,"meta":102,"style":102},"interface UseCounterReturn {\n  count: Ref\u003Cnumber>;\n  increment: () => void;\n}\nfunction useCounter(initial: number = 0): UseCounterReturn {\n  const count = ref(initial);\n  const increment = () => count.value++;\n  return { count, increment };\n}\n",[55,2957,2958,2968,2982,2998,3002,3028,3046,3068,3082],{"__ignoreMap":102},[106,2959,2960,2963,2966],{"class":108,"line":109},[106,2961,2962],{"class":297},"interface",[106,2964,2965],{"class":2145}," UseCounterReturn",[106,2967,127],{"class":126},[106,2969,2970,2972,2974,2976,2978,2980],{"class":108,"line":116},[106,2971,518],{"class":2191},[106,2973,152],{"class":182},[106,2975,2603],{"class":2145},[106,2977,446],{"class":126},[106,2979,1309],{"class":2457},[106,2981,2634],{"class":126},[106,2983,2984,2987,2989,2991,2993,2996],{"class":108,"line":130},[106,2985,2986],{"class":133},"  increment",[106,2988,152],{"class":182},[106,2990,333],{"class":126},[106,2992,336],{"class":297},[106,2994,2995],{"class":2457}," void",[106,2997,272],{"class":126},[106,2999,3000],{"class":108,"line":165},[106,3001,222],{"class":126},[106,3003,3004,3006,3008,3010,3012,3014,3016,3018,3020,3022,3024,3026],{"class":108,"line":191},[106,3005,506],{"class":297},[106,3007,880],{"class":207},[106,3009,309],{"class":126},[106,3011,2486],{"class":885},[106,3013,152],{"class":182},[106,3015,2491],{"class":2457},[106,3017,304],{"class":182},[106,3019,891],{"class":496},[106,3021,315],{"class":126},[106,3023,152],{"class":182},[106,3025,2965],{"class":2145},[106,3027,127],{"class":126},[106,3029,3030,3032,3034,3036,3038,3040,3042,3044],{"class":108,"line":219},[106,3031,900],{"class":297},[106,3033,487],{"class":301},[106,3035,304],{"class":182},[106,3037,247],{"class":207},[106,3039,309],{"class":148},[106,3041,2486],{"class":246},[106,3043,315],{"class":148},[106,3045,272],{"class":126},[106,3047,3048,3050,3052,3054,3056,3058,3060,3062,3064,3066],{"class":108,"line":225},[106,3049,900],{"class":297},[106,3051,509],{"class":325},[106,3053,304],{"class":182},[106,3055,333],{"class":126},[106,3057,336],{"class":297},[106,3059,487],{"class":246},[106,3061,204],{"class":126},[106,3063,523],{"class":246},[106,3065,526],{"class":182},[106,3067,272],{"class":126},[106,3069,3070,3072,3074,3076,3078,3080],{"class":108,"line":232},[106,3071,965],{"class":119},[106,3073,140],{"class":126},[106,3075,487],{"class":246},[106,3077,250],{"class":126},[106,3079,509],{"class":246},[106,3081,344],{"class":126},[106,3083,3084],{"class":108,"line":238},[106,3085,222],{"class":126},[34,3087,3088],{"id":3088},"总结",[695,3090,3091,3101],{},[698,3092,3093],{},[701,3094,3095,3098],{},[704,3096,3097],{},"方面",[704,3099,3100],{},"关键点",[717,3102,3103,3113,3124,3137,3147,3157],{},[701,3104,3105,3110],{},[722,3106,3107],{},[50,3108,3109],{},"核心设计理念",[722,3111,3112],{},"函数式编程、按功能聚合逻辑",[701,3114,3115,3121],{},[722,3116,3117],{},[50,3118,3119],{},[55,3120,392],{},[722,3122,3123],{},"编译转换、自动暴露、更简洁的语法",[701,3125,3126,3131],{},[722,3127,3128],{},[50,3129,3130],{},"Composables 设计",[722,3132,3133,3136],{},[55,3134,3135],{},"useXxx"," 命名、返回对象、生命周期钩子、支持 ref 参数、异步标准化",[701,3138,3139,3144],{},[722,3140,3141],{},[50,3142,3143],{},"Mixins → Composables",[722,3145,3146],{},"解决命名冲突、来源不透明、逻辑分散、隐式依赖",[701,3148,3149,3154],{},[722,3150,3151],{},[50,3152,3153],{},"跨组件状态共享",[722,3155,3156],{},"模块级 ref + composable 返回同一实例",[701,3158,3159,3164],{},[722,3160,3161],{},[50,3162,3163],{},"TypeScript 支持",[722,3165,3166,3167,3169],{},"泛型组合函数、",[55,3168,2836],{}," 类型、自动类型推导",[24,3171,3172,3173,3176],{},"组合式 API 带来的不仅是语法上的改进，更是一种",[50,3174,3175],{},"可组合、可预测的逻辑复用模式","，是现代 Vue 应用的推荐实践。",[3178,3179,3180],"h2",{"id":3180},"参考文献",[24,3182,3183],{},"以下链接在编写时均可正常访问：",[695,3185,3186,3196],{},[698,3187,3188],{},[701,3189,3190,3193],{},[704,3191,3192],{},"资料",[704,3194,3195],{},"说明",[717,3197,3198,3211,3223],{},[701,3199,3200,3208],{},[722,3201,3202],{},[28,3203,3207],{"href":3204,"rel":3205},"https:\u002F\u002Fcn.vuejs.org\u002Fguide\u002Fextras\u002Fcomposition-api-faq.html",[3206],"nofollow","Vue：组合式 API",[722,3209,3210],{},"官方说明",[701,3212,3213,3220],{},[722,3214,3215],{},[28,3216,3219],{"href":3217,"rel":3218},"https:\u002F\u002Fcn.vuejs.org\u002Fapi\u002Fsfc-script-setup.html",[3206],"Vue：\u003Cscript setup>",[722,3221,3222],{},"语法糖",[701,3224,3225,3232],{},[722,3226,3227],{},[28,3228,3231],{"href":3229,"rel":3230},"https:\u002F\u002Fcn.vuejs.org\u002Fguide\u002Freusability\u002Fcomposables.html",[3206],"Vue：Composables",[722,3233,3234],{},"组合式函数",[3236,3237,3238],"style",{},"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 .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 .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--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 .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--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 .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 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 .sfCm-, html code.shiki .sfCm-{--shiki-light:#90A4AE;--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 .sQzsp, html code.shiki .sQzsp{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s9AJx, html code.shiki .s9AJx{--shiki-light:#9C3EDA;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sucvu, html code.shiki .sucvu{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":102,"searchDepth":116,"depth":116,"links":3240},[3241,3242,3244,3245,3246,3247,3248,3249],{"id":36,"depth":130,"text":37},{"id":388,"depth":130,"text":3243},"二、\u003Cscript setup> 语法糖",{"id":824,"depth":130,"text":825},{"id":2069,"depth":130,"text":2070},{"id":2231,"depth":130,"text":2232},{"id":2390,"depth":130,"text":2391},{"id":3088,"depth":130,"text":3088},{"id":3180,"depth":116,"text":3180},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fframe\u002FVue\u002Fdesign","knowledge\u002Fknowledge\u002Fbooklet\u002Fframe\u002FVue\u002Fdesign.md","md",{"items":3255},[3256,3260,3264,3268,3272,3276],{"path":3257,"title":3258,"description":3259,"category":10,"score":219},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fperformance\u002Fassets","资源优化","图片懒加载、按需引入与 Web Vitals 监控。见 HTML 图片、CSS 字体优化。",{"path":3261,"title":3262,"description":3263,"category":10,"score":219},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fstatus","状态管理：Pinia 与 Vuex","Pinia 是 Vue 官方推荐的状态管理库（Vue 3 新项目默认选型）。Vuex 4 仍可与 Vue 3 配合，用于维护旧项目。见 Vue 基础。",{"path":3265,"title":3266,"description":3267,"category":10,"score":219},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fecology","Vue 生态关键库深度集成","VueUse、Vite 与 Nuxt 是 Vue 3 生态中常用的效率与工程化方案。概览见 Vue 基础。",{"path":3269,"title":3270,"description":3271,"category":10,"score":219},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Freactivity\u002Ftrack","依赖收集与派发更新机制","说明 Vue 3 中 track \u002F trigger 与 targetMap 的工作方式。前置：v2-v3 差异。",{"path":3273,"title":3274,"description":3275,"category":10,"score":219},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Ftest","测试策略","Vue 应用常用 Vitest + @vue\u002Ftest-utils 做单元\u002F组件测试，Playwright 或 Cypress 做 E2E。见 Vue 基础。",{"path":3277,"title":3278,"description":3279,"category":10,"score":219},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Ftypescript","TypeScript 深度集成","Vue 3 源码使用 TypeScript 编写， 可获得良好的类型推导。见 Vue 基础。",{"series":3281,"title":12,"items":3282,"index":130,"prev":3330,"next":3331},"booklet\u002Fframe\u002Fvue",[3283,3285,3287,3291,3292,3294,3298,3302,3306,3310,3314,3318,3320,3322,3324,3326],{"path":3257,"title":3258,"date":3284,"pinned":15},"2025-11-06",{"path":30,"title":31,"date":3286,"pinned":15},"2021-07-29",{"path":3288,"title":3289,"date":3290,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fperformance\u002Fbuild","编译与打包优化","2021-08-02",{"path":7,"title":8,"date":14,"pinned":15},{"path":3265,"title":3266,"date":3293,"pinned":15},"2025-08-26",{"path":3295,"title":3296,"date":3297,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fproject","工程化实践","2021-07-20",{"path":3299,"title":3300,"date":3301,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fprops","组件通信与跨层级设计模式","2023-03-23",{"path":3303,"title":3304,"date":3305,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Freactivity\u002Fproxy","响应式进阶","2023-03-21",{"path":3307,"title":3308,"date":3309,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Frender","渲染机制与编译原理","2021-09-23",{"path":3311,"title":3312,"date":3313,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Frouter","路由与权限控制","2021-09-12",{"path":3315,"title":3316,"date":3317,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fperformance\u002Fruntime","运行时优化","2021-06-10",{"path":3261,"title":3262,"date":3319,"pinned":15},"2025-10-11",{"path":3273,"title":3274,"date":3321,"pinned":15},"2024-10-23",{"path":3269,"title":3270,"date":3323,"pinned":15},"2025-01-13",{"path":3277,"title":3278,"date":3325,"pinned":15},"2023-11-29",{"path":3327,"title":3328,"date":3329,"pinned":15},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Freactivity\u002Fv2-v3","Vue 2 与 Vue 3 响应式差异","2022-07-06",{"path":3288,"title":3289,"date":3290,"pinned":15},{"path":3265,"title":3266,"date":3293,"pinned":15}]