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