[{"data":1,"prerenderedAt":1786},["ShallowReactive",2],{"post-document:\u002Ffrontend\u002Fvue\u002F2026\u002F08\u002F03\u002Fpinia\u002F":3},{"id":4,"title":5,"body":6,"categories":1723,"date":1726,"description":1727,"extension":1728,"image":1729,"key_concepts":1730,"last_modified_at":1729,"legacyPath":1734,"meta":1735,"navigation":224,"part":1729,"path":1772,"published":224,"robots":1729,"seo":1773,"series":1729,"stem":1774,"strengths":1775,"summary":1778,"tags":1779,"tradeoffs":1783,"__hash__":1785},"posts\u002Fposts\u002FFrontend\u002FVue\u002F2026-08-03-pinia.md","05. Pinia 상태 관리: store 설계와 사용법",{"type":7,"value":8,"toc":1708},"minimark",[9,14,27,30,38,48,56,59,65,69,72,134,137,141,164,170,284,288,302,305,367,381,853,857,860,1004,1010,1013,1046,1053,1270,1277,1281,1284,1310,1317,1367,1374,1388,1392,1397,1406,1412,1416,1421,1424,1428,1431,1434,1632,1635,1644,1665,1669,1701,1704],[10,11,13],"h2",{"id":12},"pinia가-필요한-이유","Pinia가 필요한 이유",[15,16,17,18,22,23,26],"p",{},"한 컴포넌트 안에서만 사용하는 값은 ",[19,20,21],"code",{},"ref()","나 ",[19,24,25],{},"reactive()","면 충분하다.",[15,28,29],{},"하지만 로그인 사용자와 장바구니처럼 서로 멀리 떨어진 여러 화면이 같은 상태를 읽고 변경하면 전달 과정이 길어진다.",[15,31,32,33,37],{},"(계속 써야하는 정보를 토스",[34,35,36],"del",{},"토스","토스해서 전달하는 것은 비효율적이지 않은가!)",[39,40,46],"pre",{"className":41,"code":43,"language":44,"meta":45},[42],"language-text","App\n├── AppHeader ── 사용자 이름, 장바구니 개수\n├── ProductList ── 장바구니에 상품 추가\n└── ShoppingCart ── 수량 변경, 상품 제거\n","text","",[19,47,43],{"__ignoreMap":45},[15,49,50,51,55],{},"모든 중간 컴포넌트에 props를 계속 전달하는 방식을 ",[52,53,54],"strong",{},"props drilling"," 이라고 한다. Pinia는 공유 상태와 상태를 다루는 동작을 store에 모아 필요한 컴포넌트가 직접 사용하게 한다.",[15,57,58],{},"(굉장히 비효율적인 방식)",[39,60,63],{"className":61,"code":62,"language":44,"meta":45},[42],"AppHeader  ─┐\nProductList ├──> Cart Store\nCartPage  ──┘     state \u002F getters \u002F actions\n",[19,64,62],{"__ignoreMap":45},[10,66,68],{"id":67},"먼저-판단할-것","먼저 판단할 것",[15,70,71],{},"Pinia가 모든 상태의 기본 위치는 아니다.",[73,74,75,88],"table",{},[76,77,78],"thead",{},[79,80,81,85],"tr",{},[82,83,84],"th",{},"상태",[82,86,87],{},"적합한 위치",[89,90,91,103,118,126],"tbody",{},[79,92,93,97],{},[94,95,96],"td",{},"모달 열림, 입력 중인 값",[94,98,99,100],{},"사용하는 컴포넌트의 ",[19,101,102],{},"ref",[79,104,105,108],{},[94,106,107],{},"부모와 자식만 공유",[94,109,110,111,114,115],{},"부모 상태 + ",[19,112,113],{},"props"," \u002F ",[19,116,117],{},"emit",[79,119,120,123],{},[94,121,122],{},"같은 로직을 여러 곳에서 재사용",[94,124,125],{},"composable",[79,127,128,131],{},[94,129,130],{},"로그인 사용자, 장바구니, 앱 전역 설정",[94,132,133],{},"Pinia store",[15,135,136],{},"상태가 여러 페이지에서 필요하고, 페이지가 바뀌어도 유지되어야 하며, 변경 규칙을 한곳에 모을 필요가 있을 때 store를 고려한다.",[10,138,140],{"id":139},"설치하고-앱에-연결하기","설치하고 앱에 연결하기",[39,142,146],{"className":143,"code":144,"language":145,"meta":45,"style":45},"language-bash shiki shiki-themes github-light github-dark","npm install pinia\n","bash",[19,147,148],{"__ignoreMap":45},[149,150,153,157,161],"span",{"class":151,"line":152},"line",1,[149,154,156],{"class":155},"sScJk","npm",[149,158,160],{"class":159},"sZZnC"," install",[149,162,163],{"class":159}," pinia\n",[15,165,166,169],{},[19,167,168],{},"main.js","에서 Pinia 인스턴스를 Vue 앱에 등록한다.",[39,171,175],{"className":172,"code":173,"language":174,"meta":45,"style":45},"language-js shiki shiki-themes github-light github-dark","import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from '.\u002FApp.vue'\n\nconst app = createApp(App)\n\napp.use(createPinia())\napp.mount('#app')\n","js",[19,176,177,193,206,219,226,245,250,268],{"__ignoreMap":45},[149,178,179,183,187,190],{"class":151,"line":152},[149,180,182],{"class":181},"szBVR","import",[149,184,186],{"class":185},"sVt8B"," { createApp } ",[149,188,189],{"class":181},"from",[149,191,192],{"class":159}," 'vue'\n",[149,194,196,198,201,203],{"class":151,"line":195},2,[149,197,182],{"class":181},[149,199,200],{"class":185}," { createPinia } ",[149,202,189],{"class":181},[149,204,205],{"class":159}," 'pinia'\n",[149,207,209,211,214,216],{"class":151,"line":208},3,[149,210,182],{"class":181},[149,212,213],{"class":185}," App ",[149,215,189],{"class":181},[149,217,218],{"class":159}," '.\u002FApp.vue'\n",[149,220,222],{"class":151,"line":221},4,[149,223,225],{"emptyLinePlaceholder":224},true,"\n",[149,227,229,232,236,239,242],{"class":151,"line":228},5,[149,230,231],{"class":181},"const",[149,233,235],{"class":234},"sj4cs"," app",[149,237,238],{"class":181}," =",[149,240,241],{"class":155}," createApp",[149,243,244],{"class":185},"(App)\n",[149,246,248],{"class":151,"line":247},6,[149,249,225],{"emptyLinePlaceholder":224},[149,251,253,256,259,262,265],{"class":151,"line":252},7,[149,254,255],{"class":185},"app.",[149,257,258],{"class":155},"use",[149,260,261],{"class":185},"(",[149,263,264],{"class":155},"createPinia",[149,266,267],{"class":185},"())\n",[149,269,271,273,276,278,281],{"class":151,"line":270},8,[149,272,255],{"class":185},[149,274,275],{"class":155},"mount",[149,277,261],{"class":185},[149,279,280],{"class":159},"'#app'",[149,282,283],{"class":185},")\n",[10,285,287],{"id":286},"store의-기본-구조","store의 기본 구조",[15,289,290,291,294,295,294,298,301],{},"Pinia의 Option Store는 ",[19,292,293],{},"state",", ",[19,296,297],{},"getters",[19,299,300],{},"actions"," 세 부분으로 이해하면 쉽다.",[15,303,304],{},"아주 쉽다!",[73,306,307,320],{},[76,308,309],{},[79,310,311,314,317],{},[82,312,313],{},"구분",[82,315,316],{},"역할",[82,318,319],{},"컴포넌트에 비유",[89,321,322,341,355],{},[79,323,324,328,331],{},[94,325,326],{},[19,327,293],{},[94,329,330],{},"store가 보관하는 원본 데이터",[94,332,333,294,336,294,338],{},[19,334,335],{},"data",[19,337,102],{},[19,339,340],{},"reactive",[79,342,343,347,350],{},[94,344,345],{},[19,346,297],{},[94,348,349],{},"state에서 계산한 파생 값",[94,351,352],{},[19,353,354],{},"computed",[79,356,357,361,364],{},[94,358,359],{},[19,360,300],{},[94,362,363],{},"상태를 읽고 변경하는 동작",[94,365,366],{},"함수, methods",[15,368,369,370,373,374,376,377,380],{},"store의 첫 번째 인자인 id는 앱 안에서 고유해야 한다. 반환 함수는 ",[19,371,372],{},"useCartStore","처럼 ",[19,375,258],{},"와 ",[19,378,379],{},"Store","를 붙이는 관례를 따른다.",[39,382,384],{"className":172,"code":383,"language":174,"meta":45,"style":45},"\u002F\u002F src\u002Fstores\u002Fcart.js\nimport { defineStore } from 'pinia'\n\nexport const useCartStore = defineStore('cart', {\n  state: () => ({\n    items: [],\n    lastUpdatedAt: null\n  }),\n\n  getters: {\n    itemCount: (state) => {\n      return state.items.reduce((sum, item) => sum + item.quantity, 0)\n    },\n\n    totalPrice: (state) => {\n      return state.items.reduce(\n        (sum, item) => sum + item.price * item.quantity,\n        0\n      )\n    }\n  },\n\n  actions: {\n    addItem(product) {\n      const item = this.items.find((item) => item.id === product.id)\n\n      if (item) {\n        item.quantity += 1\n        return\n      }\n\n      this.items.push({\n        id: product.id,\n        name: product.name,\n        price: product.price,\n        quantity: 1\n      })\n    },\n\n    removeItem(productId) {\n      this.items = this.items.filter((item) => item.id !== productId)\n    }\n  }\n})\n",[19,385,386,392,403,407,431,445,450,458,463,468,474,494,535,541,546,562,574,603,609,615,621,627,632,638,652,689,694,703,715,721,727,732,746,752,758,764,773,779,784,789,802,836,841,847],{"__ignoreMap":45},[149,387,388],{"class":151,"line":152},[149,389,391],{"class":390},"sJ8bj","\u002F\u002F src\u002Fstores\u002Fcart.js\n",[149,393,394,396,399,401],{"class":151,"line":195},[149,395,182],{"class":181},[149,397,398],{"class":185}," { defineStore } ",[149,400,189],{"class":181},[149,402,205],{"class":159},[149,404,405],{"class":151,"line":208},[149,406,225],{"emptyLinePlaceholder":224},[149,408,409,412,415,418,420,423,425,428],{"class":151,"line":221},[149,410,411],{"class":181},"export",[149,413,414],{"class":181}," const",[149,416,417],{"class":234}," useCartStore",[149,419,238],{"class":181},[149,421,422],{"class":155}," defineStore",[149,424,261],{"class":185},[149,426,427],{"class":159},"'cart'",[149,429,430],{"class":185},", {\n",[149,432,433,436,439,442],{"class":151,"line":228},[149,434,435],{"class":155},"  state",[149,437,438],{"class":185},": () ",[149,440,441],{"class":181},"=>",[149,443,444],{"class":185}," ({\n",[149,446,447],{"class":151,"line":247},[149,448,449],{"class":185},"    items: [],\n",[149,451,452,455],{"class":151,"line":252},[149,453,454],{"class":185},"    lastUpdatedAt: ",[149,456,457],{"class":234},"null\n",[149,459,460],{"class":151,"line":270},[149,461,462],{"class":185},"  }),\n",[149,464,466],{"class":151,"line":465},9,[149,467,225],{"emptyLinePlaceholder":224},[149,469,471],{"class":151,"line":470},10,[149,472,473],{"class":185},"  getters: {\n",[149,475,477,480,483,486,489,491],{"class":151,"line":476},11,[149,478,479],{"class":155},"    itemCount",[149,481,482],{"class":185},": (",[149,484,293],{"class":485},"s4XuR",[149,487,488],{"class":185},") ",[149,490,441],{"class":181},[149,492,493],{"class":185}," {\n",[149,495,497,500,503,506,509,512,514,517,519,521,524,527,530,533],{"class":151,"line":496},12,[149,498,499],{"class":181},"      return",[149,501,502],{"class":185}," state.items.",[149,504,505],{"class":155},"reduce",[149,507,508],{"class":185},"((",[149,510,511],{"class":485},"sum",[149,513,294],{"class":185},[149,515,516],{"class":485},"item",[149,518,488],{"class":185},[149,520,441],{"class":181},[149,522,523],{"class":185}," sum ",[149,525,526],{"class":181},"+",[149,528,529],{"class":185}," item.quantity, ",[149,531,532],{"class":234},"0",[149,534,283],{"class":185},[149,536,538],{"class":151,"line":537},13,[149,539,540],{"class":185},"    },\n",[149,542,544],{"class":151,"line":543},14,[149,545,225],{"emptyLinePlaceholder":224},[149,547,549,552,554,556,558,560],{"class":151,"line":548},15,[149,550,551],{"class":155},"    totalPrice",[149,553,482],{"class":185},[149,555,293],{"class":485},[149,557,488],{"class":185},[149,559,441],{"class":181},[149,561,493],{"class":185},[149,563,565,567,569,571],{"class":151,"line":564},16,[149,566,499],{"class":181},[149,568,502],{"class":185},[149,570,505],{"class":155},[149,572,573],{"class":185},"(\n",[149,575,577,580,582,584,586,588,590,592,594,597,600],{"class":151,"line":576},17,[149,578,579],{"class":185},"        (",[149,581,511],{"class":485},[149,583,294],{"class":185},[149,585,516],{"class":485},[149,587,488],{"class":185},[149,589,441],{"class":181},[149,591,523],{"class":185},[149,593,526],{"class":181},[149,595,596],{"class":185}," item.price ",[149,598,599],{"class":181},"*",[149,601,602],{"class":185}," item.quantity,\n",[149,604,606],{"class":151,"line":605},18,[149,607,608],{"class":234},"        0\n",[149,610,612],{"class":151,"line":611},19,[149,613,614],{"class":185},"      )\n",[149,616,618],{"class":151,"line":617},20,[149,619,620],{"class":185},"    }\n",[149,622,624],{"class":151,"line":623},21,[149,625,626],{"class":185},"  },\n",[149,628,630],{"class":151,"line":629},22,[149,631,225],{"emptyLinePlaceholder":224},[149,633,635],{"class":151,"line":634},23,[149,636,637],{"class":185},"  actions: {\n",[149,639,641,644,646,649],{"class":151,"line":640},24,[149,642,643],{"class":155},"    addItem",[149,645,261],{"class":185},[149,647,648],{"class":485},"product",[149,650,651],{"class":185},") {\n",[149,653,655,658,661,663,666,669,672,674,676,678,680,683,686],{"class":151,"line":654},25,[149,656,657],{"class":181},"      const",[149,659,660],{"class":234}," item",[149,662,238],{"class":181},[149,664,665],{"class":234}," this",[149,667,668],{"class":185},".items.",[149,670,671],{"class":155},"find",[149,673,508],{"class":185},[149,675,516],{"class":485},[149,677,488],{"class":185},[149,679,441],{"class":181},[149,681,682],{"class":185}," item.id ",[149,684,685],{"class":181},"===",[149,687,688],{"class":185}," product.id)\n",[149,690,692],{"class":151,"line":691},26,[149,693,225],{"emptyLinePlaceholder":224},[149,695,697,700],{"class":151,"line":696},27,[149,698,699],{"class":181},"      if",[149,701,702],{"class":185}," (item) {\n",[149,704,706,709,712],{"class":151,"line":705},28,[149,707,708],{"class":185},"        item.quantity ",[149,710,711],{"class":181},"+=",[149,713,714],{"class":234}," 1\n",[149,716,718],{"class":151,"line":717},29,[149,719,720],{"class":181},"        return\n",[149,722,724],{"class":151,"line":723},30,[149,725,726],{"class":185},"      }\n",[149,728,730],{"class":151,"line":729},31,[149,731,225],{"emptyLinePlaceholder":224},[149,733,735,738,740,743],{"class":151,"line":734},32,[149,736,737],{"class":234},"      this",[149,739,668],{"class":185},[149,741,742],{"class":155},"push",[149,744,745],{"class":185},"({\n",[149,747,749],{"class":151,"line":748},33,[149,750,751],{"class":185},"        id: product.id,\n",[149,753,755],{"class":151,"line":754},34,[149,756,757],{"class":185},"        name: product.name,\n",[149,759,761],{"class":151,"line":760},35,[149,762,763],{"class":185},"        price: product.price,\n",[149,765,767,770],{"class":151,"line":766},36,[149,768,769],{"class":185},"        quantity: ",[149,771,772],{"class":234},"1\n",[149,774,776],{"class":151,"line":775},37,[149,777,778],{"class":185},"      })\n",[149,780,782],{"class":151,"line":781},38,[149,783,540],{"class":185},[149,785,787],{"class":151,"line":786},39,[149,788,225],{"emptyLinePlaceholder":224},[149,790,792,795,797,800],{"class":151,"line":791},40,[149,793,794],{"class":155},"    removeItem",[149,796,261],{"class":185},[149,798,799],{"class":485},"productId",[149,801,651],{"class":185},[149,803,805,807,810,813,815,817,820,822,824,826,828,830,833],{"class":151,"line":804},41,[149,806,737],{"class":234},[149,808,809],{"class":185},".items ",[149,811,812],{"class":181},"=",[149,814,665],{"class":234},[149,816,668],{"class":185},[149,818,819],{"class":155},"filter",[149,821,508],{"class":185},[149,823,516],{"class":485},[149,825,488],{"class":185},[149,827,441],{"class":181},[149,829,682],{"class":185},[149,831,832],{"class":181},"!==",[149,834,835],{"class":185}," productId)\n",[149,837,839],{"class":151,"line":838},42,[149,840,620],{"class":185},[149,842,844],{"class":151,"line":843},43,[149,845,846],{"class":185},"  }\n",[149,848,850],{"class":151,"line":849},44,[149,851,852],{"class":185},"})\n",[10,854,856],{"id":855},"컴포넌트에서-사용하기","컴포넌트에서 사용하기",[15,858,859],{},"store 인스턴스의 state, getters, actions는 바로 접근할 수 있다.",[39,861,865],{"className":862,"code":863,"language":864,"meta":45,"style":45},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup>\nimport { useCartStore } from '@\u002Fstores\u002Fcart'\n\nconst cartStore = useCartStore()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>상품 {{ cartStore.itemCount }}개\u003C\u002Fp>\n  \u003Cp>합계 {{ cartStore.totalPrice.toLocaleString() }}원\u003C\u002Fp>\n\n  \u003Cbutton @click=\"cartStore.addItem(product)\">\n    장바구니에 담기\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n","vue",[19,866,867,882,894,898,912,921,925,934,948,961,965,982,987,996],{"__ignoreMap":45},[149,868,869,872,876,879],{"class":151,"line":152},[149,870,871],{"class":185},"\u003C",[149,873,875],{"class":874},"s9eBZ","script",[149,877,878],{"class":155}," setup",[149,880,881],{"class":185},">\n",[149,883,884,886,889,891],{"class":151,"line":195},[149,885,182],{"class":181},[149,887,888],{"class":185}," { useCartStore } ",[149,890,189],{"class":181},[149,892,893],{"class":159}," '@\u002Fstores\u002Fcart'\n",[149,895,896],{"class":151,"line":208},[149,897,225],{"emptyLinePlaceholder":224},[149,899,900,902,905,907,909],{"class":151,"line":221},[149,901,231],{"class":181},[149,903,904],{"class":234}," cartStore",[149,906,238],{"class":181},[149,908,417],{"class":155},[149,910,911],{"class":185},"()\n",[149,913,914,917,919],{"class":151,"line":228},[149,915,916],{"class":185},"\u003C\u002F",[149,918,875],{"class":874},[149,920,881],{"class":185},[149,922,923],{"class":151,"line":247},[149,924,225],{"emptyLinePlaceholder":224},[149,926,927,929,932],{"class":151,"line":252},[149,928,871],{"class":185},[149,930,931],{"class":874},"template",[149,933,881],{"class":185},[149,935,936,939,941,944,946],{"class":151,"line":270},[149,937,938],{"class":185},"  \u003C",[149,940,15],{"class":874},[149,942,943],{"class":185},">상품 {{ cartStore.itemCount }}개\u003C\u002F",[149,945,15],{"class":874},[149,947,881],{"class":185},[149,949,950,952,954,957,959],{"class":151,"line":465},[149,951,938],{"class":185},[149,953,15],{"class":874},[149,955,956],{"class":185},">합계 {{ cartStore.totalPrice.toLocaleString() }}원\u003C\u002F",[149,958,15],{"class":874},[149,960,881],{"class":185},[149,962,963],{"class":151,"line":470},[149,964,225],{"emptyLinePlaceholder":224},[149,966,967,969,972,975,977,980],{"class":151,"line":476},[149,968,938],{"class":185},[149,970,971],{"class":874},"button",[149,973,974],{"class":155}," @click",[149,976,812],{"class":185},[149,978,979],{"class":159},"\"cartStore.addItem(product)\"",[149,981,881],{"class":185},[149,983,984],{"class":151,"line":496},[149,985,986],{"class":185},"    장바구니에 담기\n",[149,988,989,992,994],{"class":151,"line":537},[149,990,991],{"class":185},"  \u003C\u002F",[149,993,971],{"class":874},[149,995,881],{"class":185},[149,997,998,1000,1002],{"class":151,"line":543},[149,999,916],{"class":185},[149,1001,931],{"class":874},[149,1003,881],{"class":185},[15,1005,1006,1007,1009],{},"store 자체는 ",[19,1008,340],{}," 객체처럼 동작한다.",[15,1011,1012],{},"state와 getters를 일반 구조 분해하면 반응성 연결이 끊어질 수 있다! 조심해야 한다.",[39,1014,1016],{"className":172,"code":1015,"language":174,"meta":45,"style":45},"\u002F\u002F 피해야 할 방식\nconst { items, totalPrice } = cartStore\n",[19,1017,1018,1023],{"__ignoreMap":45},[149,1019,1020],{"class":151,"line":152},[149,1021,1022],{"class":390},"\u002F\u002F 피해야 할 방식\n",[149,1024,1025,1027,1030,1033,1035,1038,1041,1043],{"class":151,"line":195},[149,1026,231],{"class":181},[149,1028,1029],{"class":185}," { ",[149,1031,1032],{"class":234},"items",[149,1034,294],{"class":185},[149,1036,1037],{"class":234},"totalPrice",[149,1039,1040],{"class":185}," } ",[149,1042,812],{"class":181},[149,1044,1045],{"class":185}," cartStore\n",[15,1047,1048,1049,1052],{},"구조 분해가 필요하면 ",[19,1050,1051],{},"storeToRefs()","를 사용한다. actions는 store에 바인딩되어 있어 직접 구조 분해해도 된다.",[39,1054,1056],{"className":862,"code":1055,"language":864,"meta":45,"style":45},"\u003Cscript setup>\nimport { storeToRefs } from 'pinia'\nimport { useCartStore } from '@\u002Fstores\u002Fcart'\n\nconst cartStore = useCartStore()\nconst { items, itemCount, totalPrice } = storeToRefs(cartStore)\nconst { removeItem } = cartStore\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cul>\n    \u003Cli v-for=\"item in items\" :key=\"item.id\">\n      {{ item.name }} × {{ item.quantity }}\n      \u003Cbutton @click=\"removeItem(item.id)\">제거\u003C\u002Fbutton>\n    \u003C\u002Fli>\n  \u003C\u002Ful>\n\n  \u003Cp>{{ itemCount }}개 \u002F {{ totalPrice.toLocaleString() }}원\u003C\u002Fp>\n\u003C\u002Ftemplate>\n",[19,1057,1058,1068,1079,1089,1093,1105,1132,1147,1155,1159,1167,1176,1202,1207,1228,1237,1245,1249,1262],{"__ignoreMap":45},[149,1059,1060,1062,1064,1066],{"class":151,"line":152},[149,1061,871],{"class":185},[149,1063,875],{"class":874},[149,1065,878],{"class":155},[149,1067,881],{"class":185},[149,1069,1070,1072,1075,1077],{"class":151,"line":195},[149,1071,182],{"class":181},[149,1073,1074],{"class":185}," { storeToRefs } ",[149,1076,189],{"class":181},[149,1078,205],{"class":159},[149,1080,1081,1083,1085,1087],{"class":151,"line":208},[149,1082,182],{"class":181},[149,1084,888],{"class":185},[149,1086,189],{"class":181},[149,1088,893],{"class":159},[149,1090,1091],{"class":151,"line":221},[149,1092,225],{"emptyLinePlaceholder":224},[149,1094,1095,1097,1099,1101,1103],{"class":151,"line":228},[149,1096,231],{"class":181},[149,1098,904],{"class":234},[149,1100,238],{"class":181},[149,1102,417],{"class":155},[149,1104,911],{"class":185},[149,1106,1107,1109,1111,1113,1115,1118,1120,1122,1124,1126,1129],{"class":151,"line":247},[149,1108,231],{"class":181},[149,1110,1029],{"class":185},[149,1112,1032],{"class":234},[149,1114,294],{"class":185},[149,1116,1117],{"class":234},"itemCount",[149,1119,294],{"class":185},[149,1121,1037],{"class":234},[149,1123,1040],{"class":185},[149,1125,812],{"class":181},[149,1127,1128],{"class":155}," storeToRefs",[149,1130,1131],{"class":185},"(cartStore)\n",[149,1133,1134,1136,1138,1141,1143,1145],{"class":151,"line":252},[149,1135,231],{"class":181},[149,1137,1029],{"class":185},[149,1139,1140],{"class":234},"removeItem",[149,1142,1040],{"class":185},[149,1144,812],{"class":181},[149,1146,1045],{"class":185},[149,1148,1149,1151,1153],{"class":151,"line":270},[149,1150,916],{"class":185},[149,1152,875],{"class":874},[149,1154,881],{"class":185},[149,1156,1157],{"class":151,"line":465},[149,1158,225],{"emptyLinePlaceholder":224},[149,1160,1161,1163,1165],{"class":151,"line":470},[149,1162,871],{"class":185},[149,1164,931],{"class":874},[149,1166,881],{"class":185},[149,1168,1169,1171,1174],{"class":151,"line":476},[149,1170,938],{"class":185},[149,1172,1173],{"class":874},"ul",[149,1175,881],{"class":185},[149,1177,1178,1181,1184,1187,1189,1192,1195,1197,1200],{"class":151,"line":496},[149,1179,1180],{"class":185},"    \u003C",[149,1182,1183],{"class":874},"li",[149,1185,1186],{"class":155}," v-for",[149,1188,812],{"class":185},[149,1190,1191],{"class":159},"\"item in items\"",[149,1193,1194],{"class":155}," :key",[149,1196,812],{"class":185},[149,1198,1199],{"class":159},"\"item.id\"",[149,1201,881],{"class":185},[149,1203,1204],{"class":151,"line":537},[149,1205,1206],{"class":185},"      {{ item.name }} × {{ item.quantity }}\n",[149,1208,1209,1212,1214,1216,1218,1221,1224,1226],{"class":151,"line":543},[149,1210,1211],{"class":185},"      \u003C",[149,1213,971],{"class":874},[149,1215,974],{"class":155},[149,1217,812],{"class":185},[149,1219,1220],{"class":159},"\"removeItem(item.id)\"",[149,1222,1223],{"class":185},">제거\u003C\u002F",[149,1225,971],{"class":874},[149,1227,881],{"class":185},[149,1229,1230,1233,1235],{"class":151,"line":548},[149,1231,1232],{"class":185},"    \u003C\u002F",[149,1234,1183],{"class":874},[149,1236,881],{"class":185},[149,1238,1239,1241,1243],{"class":151,"line":564},[149,1240,991],{"class":185},[149,1242,1173],{"class":874},[149,1244,881],{"class":185},[149,1246,1247],{"class":151,"line":576},[149,1248,225],{"emptyLinePlaceholder":224},[149,1250,1251,1253,1255,1258,1260],{"class":151,"line":605},[149,1252,938],{"class":185},[149,1254,15],{"class":874},[149,1256,1257],{"class":185},">{{ itemCount }}개 \u002F {{ totalPrice.toLocaleString() }}원\u003C\u002F",[149,1259,15],{"class":874},[149,1261,881],{"class":185},[149,1263,1264,1266,1268],{"class":151,"line":611},[149,1265,916],{"class":185},[149,1267,931],{"class":874},[149,1269,881],{"class":185},[15,1271,1272,1273,1276],{},"템플릿에서는 ref가 자동으로 언래핑되므로 ",[19,1274,1275],{},".value","를 붙이지 않는다.",[10,1278,1280],{"id":1279},"state-변경-방법","state 변경 방법",[15,1282,1283],{},"Pinia는 state를 직접 변경할 수 있지만, 의미 있는 변경 규칙은 action에 모으면 추적과 재사용이 쉽다.",[39,1285,1287],{"className":172,"code":1286,"language":174,"meta":45,"style":45},"cartStore.items.push(newItem)\ncartStore.addItem(product)\n",[19,1288,1289,1299],{"__ignoreMap":45},[149,1290,1291,1294,1296],{"class":151,"line":152},[149,1292,1293],{"class":185},"cartStore.items.",[149,1295,742],{"class":155},[149,1297,1298],{"class":185},"(newItem)\n",[149,1300,1301,1304,1307],{"class":151,"line":195},[149,1302,1303],{"class":185},"cartStore.",[149,1305,1306],{"class":155},"addItem",[149,1308,1309],{"class":185},"(product)\n",[15,1311,1312,1313,1316],{},"여러 값을 한 번에 묶어 변경할 때는 ",[19,1314,1315],{},"$patch()","를 사용할 수 있다.",[39,1318,1320],{"className":172,"code":1319,"language":174,"meta":45,"style":45},"cartStore.$patch((state) => {\n  state.items.push(newItem)\n  state.lastUpdatedAt = Date.now()\n})\n",[19,1321,1322,1339,1348,1363],{"__ignoreMap":45},[149,1323,1324,1326,1329,1331,1333,1335,1337],{"class":151,"line":152},[149,1325,1303],{"class":185},[149,1327,1328],{"class":155},"$patch",[149,1330,508],{"class":185},[149,1332,293],{"class":485},[149,1334,488],{"class":185},[149,1336,441],{"class":181},[149,1338,493],{"class":185},[149,1340,1341,1344,1346],{"class":151,"line":195},[149,1342,1343],{"class":185},"  state.items.",[149,1345,742],{"class":155},[149,1347,1298],{"class":185},[149,1349,1350,1353,1355,1358,1361],{"class":151,"line":208},[149,1351,1352],{"class":185},"  state.lastUpdatedAt ",[149,1354,812],{"class":181},[149,1356,1357],{"class":185}," Date.",[149,1359,1360],{"class":155},"now",[149,1362,911],{"class":185},[149,1364,1365],{"class":151,"line":221},[149,1366,852],{"class":185},[15,1368,1369,1370,1373],{},"Option Store는 ",[19,1371,1372],{},"$reset()","으로 처음 상태로 되돌릴 수 있다.",[39,1375,1377],{"className":172,"code":1376,"language":174,"meta":45,"style":45},"cartStore.$reset()\n",[19,1378,1379],{"__ignoreMap":45},[149,1380,1381,1383,1386],{"class":151,"line":152},[149,1382,1303],{"class":185},[149,1384,1385],{"class":155},"$reset",[149,1387,911],{"class":185},[10,1389,1391],{"id":1390},"store-설계-원칙","store 설계 원칙",[1393,1394,1396],"h3",{"id":1395},"도메인-단위로-나눈다","도메인 단위로 나눈다",[15,1398,1399,294,1402,1405],{},[19,1400,1401],{},"stateStore",[19,1403,1404],{},"getterStore","처럼 기술 요소별로 나누지 않고 역할이 같은 상태와 동작을 묶는다.",[39,1407,1410],{"className":1408,"code":1409,"language":44,"meta":45},[42],"stores\u002F\n├── auth.js\n├── cart.js\n└── products.js\n",[19,1411,1409],{"__ignoreMap":45},[1393,1413,1415],{"id":1414},"화면-전용-상태를-넣지-않는다","화면 전용 상태를 넣지 않는다",[15,1417,1418],{},[34,1419,1420],{},"넣으면 대참사가 일어나게 될 것",[15,1422,1423],{},"특정 드롭다운의 열림 여부나 한 폼의 임시 입력값까지 전역화하면 store가 화면 구현에 강하게 결합된다.",[1393,1425,1427],{"id":1426},"비동기-작업은-action에-둔다","비동기 작업은 action에 둔다",[15,1429,1430],{},"action은 비동기 함수가 될 수 있다.",[15,1432,1433],{},"로딩과 오류 상태를 함께 관리하면 화면에서는 결과만 사용하기 쉽다.",[39,1435,1437],{"className":172,"code":1436,"language":174,"meta":45,"style":45},"actions: {\n  async fetchProducts() {\n    this.loading = true\n    this.error = null\n\n    try {\n      const response = await fetch('\u002Fapi\u002Fproducts')\n\n      if (!response.ok) {\n        throw new Error('상품을 불러오지 못했습니다.')\n      }\n\n      this.products = await response.json()\n    } catch (error) {\n      this.error = error.message\n    } finally {\n      this.loading = false\n    }\n  }\n}\n",[19,1438,1439,1446,1457,1470,1482,1486,1493,1515,1519,1532,1550,1554,1558,1577,1588,1599,1608,1619,1623,1627],{"__ignoreMap":45},[149,1440,1441,1443],{"class":151,"line":152},[149,1442,300],{"class":155},[149,1444,1445],{"class":185},": {\n",[149,1447,1448,1451,1454],{"class":151,"line":195},[149,1449,1450],{"class":185},"  async ",[149,1452,1453],{"class":155},"fetchProducts",[149,1455,1456],{"class":185},"() {\n",[149,1458,1459,1462,1465,1467],{"class":151,"line":208},[149,1460,1461],{"class":234},"    this",[149,1463,1464],{"class":185},".loading ",[149,1466,812],{"class":181},[149,1468,1469],{"class":234}," true\n",[149,1471,1472,1474,1477,1479],{"class":151,"line":221},[149,1473,1461],{"class":234},[149,1475,1476],{"class":185},".error ",[149,1478,812],{"class":181},[149,1480,1481],{"class":234}," null\n",[149,1483,1484],{"class":151,"line":228},[149,1485,225],{"emptyLinePlaceholder":224},[149,1487,1488,1491],{"class":151,"line":247},[149,1489,1490],{"class":181},"    try",[149,1492,493],{"class":185},[149,1494,1495,1497,1500,1502,1505,1508,1510,1513],{"class":151,"line":252},[149,1496,657],{"class":181},[149,1498,1499],{"class":234}," response",[149,1501,238],{"class":181},[149,1503,1504],{"class":181}," await",[149,1506,1507],{"class":155}," fetch",[149,1509,261],{"class":185},[149,1511,1512],{"class":159},"'\u002Fapi\u002Fproducts'",[149,1514,283],{"class":185},[149,1516,1517],{"class":151,"line":270},[149,1518,225],{"emptyLinePlaceholder":224},[149,1520,1521,1523,1526,1529],{"class":151,"line":465},[149,1522,699],{"class":181},[149,1524,1525],{"class":185}," (",[149,1527,1528],{"class":181},"!",[149,1530,1531],{"class":185},"response.ok) {\n",[149,1533,1534,1537,1540,1543,1545,1548],{"class":151,"line":470},[149,1535,1536],{"class":181},"        throw",[149,1538,1539],{"class":181}," new",[149,1541,1542],{"class":155}," Error",[149,1544,261],{"class":185},[149,1546,1547],{"class":159},"'상품을 불러오지 못했습니다.'",[149,1549,283],{"class":185},[149,1551,1552],{"class":151,"line":476},[149,1553,726],{"class":185},[149,1555,1556],{"class":151,"line":496},[149,1557,225],{"emptyLinePlaceholder":224},[149,1559,1560,1562,1565,1567,1569,1572,1575],{"class":151,"line":537},[149,1561,737],{"class":234},[149,1563,1564],{"class":185},".products ",[149,1566,812],{"class":181},[149,1568,1504],{"class":181},[149,1570,1571],{"class":185}," response.",[149,1573,1574],{"class":155},"json",[149,1576,911],{"class":185},[149,1578,1579,1582,1585],{"class":151,"line":543},[149,1580,1581],{"class":185},"    } ",[149,1583,1584],{"class":181},"catch",[149,1586,1587],{"class":185}," (error) {\n",[149,1589,1590,1592,1594,1596],{"class":151,"line":548},[149,1591,737],{"class":234},[149,1593,1476],{"class":185},[149,1595,812],{"class":181},[149,1597,1598],{"class":185}," error.message\n",[149,1600,1601,1603,1606],{"class":151,"line":564},[149,1602,1581],{"class":185},[149,1604,1605],{"class":181},"finally",[149,1607,493],{"class":185},[149,1609,1610,1612,1614,1616],{"class":151,"line":576},[149,1611,737],{"class":234},[149,1613,1464],{"class":185},[149,1615,812],{"class":181},[149,1617,1618],{"class":234}," false\n",[149,1620,1621],{"class":151,"line":605},[149,1622,620],{"class":185},[149,1624,1625],{"class":151,"line":611},[149,1626,846],{"class":185},[149,1628,1629],{"class":151,"line":617},[149,1630,1631],{"class":185},"}\n",[10,1633,1634],{"id":1634},"정리",[1636,1637,1638],"blockquote",{},[15,1639,1640,1641],{},"Pinia는 props를 없애는 도구가 아니라 ",[52,1642,1643],{},"여러 화면이 공유하는 상태의 소유권과 변경 규칙을 분명하게 만드는 도구",[1645,1646,1647,1650,1653,1656,1662],"ol",{},[1183,1648,1649],{},"지역 상태는 컴포넌트에 둔다.",[1183,1651,1652],{},"공유 범위가 넓어질 때 store로 올린다.(로그인 값이나, 모드 같이 전역에서 쓰이는 것들)",[1183,1654,1655],{},"state는 원본 데이터, getters는 파생 값, actions는 동작으로 구분한다.",[1183,1657,1658,1659,1661],{},"구조 분해할 state와 getters에는 ",[19,1660,1051],{},"를 사용한다.",[1183,1663,1664],{},"store는 사용자, 장바구니 같은 도메인 단위로 나눈다.",[10,1666,1668],{"id":1667},"참고-자료","참고 자료",[1173,1670,1671,1680,1687,1694],{},[1183,1672,1673],{},[1674,1675,1679],"a",{"href":1676,"rel":1677},"https:\u002F\u002Fpinia.vuejs.org\u002Fcore-concepts\u002F",[1678],"nofollow","Pinia 공식 문서: Defining a Store",[1183,1681,1682],{},[1674,1683,1686],{"href":1684,"rel":1685},"https:\u002F\u002Fpinia.vuejs.org\u002Fcore-concepts\u002Fstate.html",[1678],"Pinia 공식 문서: State",[1183,1688,1689],{},[1674,1690,1693],{"href":1691,"rel":1692},"https:\u002F\u002Fpinia.vuejs.org\u002Fcore-concepts\u002Fgetters.html",[1678],"Pinia 공식 문서: Getters",[1183,1695,1696],{},[1674,1697,1700],{"href":1698,"rel":1699},"https:\u002F\u002Fpinia.vuejs.org\u002Fcore-concepts\u002Factions.html",[1678],"Pinia 공식 문서: Actions",[1702,1703],"hr",{},[1705,1706,1707],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":45,"searchDepth":195,"depth":195,"links":1709},[1710,1711,1712,1713,1714,1715,1716,1721,1722],{"id":12,"depth":195,"text":13},{"id":67,"depth":195,"text":68},{"id":139,"depth":195,"text":140},{"id":286,"depth":195,"text":287},{"id":855,"depth":195,"text":856},{"id":1279,"depth":195,"text":1280},{"id":1390,"depth":195,"text":1391,"children":1717},[1718,1719,1720],{"id":1395,"depth":208,"text":1396},{"id":1414,"depth":208,"text":1415},{"id":1426,"depth":208,"text":1427},{"id":1634,"depth":195,"text":1634},{"id":1667,"depth":195,"text":1668},[1724,1725],"Frontend","Vue","2026-08-03 02:00:00 +0900","Pinia가 필요한 시점과 state, getters, actions로 store를 설계하고 컴포넌트에서 사용하는 방법을 정리한다.","md",null,[1731,1732,1733],"state는 데이터, getters는 파생 값, actions는 상태를 다루는 동작.","store는 기술 단위보다 사용자·장바구니 같은 도메인 단위로 나눔.","구조 분해가 필요하면 state와 getters에 storeToRefs를 사용.","\u002Ffrontend\u002Fvue\u002F2026\u002F08\u002F03\u002Fpinia\u002F",{"layout":1736,"excerpt_separator":1737,"excerpt":1738},"post","\u003C!--more-->",{"type":7,"value":1739},[1740,1742,1748,1750,1754,1759,1763,1765,1770],[10,1741,13],{"id":12},[15,1743,17,1744,22,1746,26],{},[19,1745,21],{},[19,1747,25],{},[15,1749,29],{},[15,1751,32,1752,37],{},[34,1753,36],{},[39,1755,1757],{"className":1756,"code":43,"language":44,"meta":45},[42],[19,1758,43],{"__ignoreMap":45},[15,1760,50,1761,55],{},[52,1762,54],{},[15,1764,58],{},[39,1766,1768],{"className":1767,"code":62,"language":44,"meta":45},[42],[19,1769,62],{"__ignoreMap":45},[1705,1771,1707],{},"\u002Fposts\u002Ffrontend\u002Fvue\u002F2026-08-03-pinia",{"title":5,"description":1727},"posts\u002FFrontend\u002FVue\u002F2026-08-03-pinia",[1776,1777],"공유 상태의 위치와 변경 동작을 한곳에서 관리할 수 있음.","Vue Devtools와 TypeScript 추론을 활용하기 좋다.","여러 Vue 컴포넌트가 공유하는 상태를 Pinia store로 관리하는 기준과 기본 사용법을 살펴본다.",[1724,1780,1781,1782],"Vue.js","Pinia","State Management",[1784],"지역 상태까지 store에 넣으면 의존성과 추적 범위가 불필요하게 커짐.","lbJvzdvTFm5vG7gy63eu81_jWZmQhGS5naCqmhO0a5g",1788744789798]