[{"data":1,"prerenderedAt":2487},["ShallowReactive",2],{"content-/nuxt3-composables-guide":3,"all-pages-for-dir":2465,"related-/nuxt3-composables-guide":2466,"og-image-/nuxt3-composables-guide":2486},{"id":4,"title":5,"body":6,"category":2449,"concepts":2450,"description":2451,"extension":2452,"meta":2453,"navigation":325,"ogImage":2450,"path":2454,"project_name":2450,"published":2455,"publishedAt":2456,"seo":2457,"source":2450,"stem":2458,"tags":2459,"todo":2450,"unpublished":2455,"updatedAt":2450,"__hash__":2464},"pages/2026-01/2026-01-21/nuxt3-composables-guide.md","Nuxt 3のcomposablesディレクトリ完全ガイド - componentとの違いと使い分け",{"type":7,"value":8,"toc":2422},"minimark",[9,14,18,36,122,133,136,139,156,166,170,185,192,197,219,225,228,232,235,240,492,497,709,806,810,813,817,820,824,834,902,912,916,920,926,945,948,959,963,973,981,988,991,1001,1004,1011,1049,1059,1141,1192,1196,1206,1209,1215,1218,1223,1256,1261,1320,1324,1327,1995,2000,2153,2156,2160,2167,2184,2194,2198,2208,2300,2303,2306,2311,2326,2331,2352,2355,2389,2392,2418],[10,11,13],"h1",{"id":12},"nuxt-3のcomposablesディレクトリ完全ガイド","Nuxt 3のcomposablesディレクトリ完全ガイド",[15,16,17],"h2",{"id":17},"結論",[19,20,21,22,26,27,31,32,35],"p",{},"Nuxt 3の ",[23,24,25],"code",{},"composables/"," ディレクトリは、",[28,29,30],"strong",{},"UIを持たない再利用可能なロジック","を配置する場所です。",[23,33,34],{},"components/"," ディレクトリとは明確に役割が異なります。",[37,38,39,52],"table",{},[40,41,42],"thead",{},[43,44,45,48,50],"tr",{},[46,47],"th",{},[46,49,34],{},[46,51,25],{},[53,54,55,76,91,102],"tbody",{},[43,56,57,61,67],{},[58,59,60],"td",{},"ファイル形式",[58,62,63,64],{},"基本は ",[23,65,66],{},".vue",[58,68,69,72,73],{},[23,70,71],{},".ts"," または ",[23,74,75],{},".js",[43,77,78,81,86],{},[58,79,80],{},"UI（template）",[58,82,83],{},[28,84,85],{},"あり",[58,87,88],{},[28,89,90],{},"なし",[43,92,93,96,99],{},[58,94,95],{},"用途",[58,97,98],{},"画面の部品",[58,100,101],{},"再利用可能なロジック",[43,103,104,107,110],{},[58,105,106],{},"例",[58,108,109],{},"ボタン、フォーム、カード",[58,111,112,115,116,115,119],{},[23,113,114],{},"useAuth()",", ",[23,117,118],{},"useCounter()",[23,120,121],{},"useTabSync()",[19,123,124,125,128,129,132],{},"※ ",[23,126,127],{},"useFetch()"," や ",[23,130,131],{},"useAsyncData()"," はNuxt 3の組み込みcomposableです。自作composableと区別して覚えておきましょう。",[15,134,135],{"id":135},"composableとは何か",[19,137,138],{},"Vue公式ドキュメントによると、composableは以下のように定義されています。",[140,141,142,149],"blockquote",{},[19,143,144,145,148],{},"In the context of Vue applications, a \"composable\" is a function that leverages Vue's Composition API to encapsulate and reuse ",[28,146,147],{},"stateful logic",".",[19,150,151,152,155],{},"（Vueアプリケーションのコンテキストにおいて、「composable」はVueのComposition APIを活用して",[28,153,154],{},"ステートフルロジック","をカプセル化し、再利用する関数です。）",[19,157,158,159],{},"出典: ",[160,161,165],"a",{"href":162,"rel":163},"https://vuejs.org/guide/reusability/composables.html",[164],"nofollow","Vue.js - Composables",[167,168,169],"h3",{"id":169},"ステートレスとステートフルの違い",[171,172,173,180],"ul",{},[174,175,176,179],"li",{},[28,177,178],{},"ステートレスロジック",": 入力を受け取り、すぐに出力を返す（例：日付フォーマット関数）",[174,181,182,184],{},[28,183,154],{},": 時間とともに変化する状態を管理（例：マウス位置の追跡、認証状態）",[19,186,187,188,191],{},"composableは",[28,189,190],{},"典型的にはステートフルロジック","を扱いますが、ステートレスな共通処理を含めることもあります。",[19,193,194],{},[28,195,196],{},"使い分けの目安:",[171,198,199,213],{},[174,200,201,115,204,115,207,210,211],{},[23,202,203],{},"ref",[23,205,206],{},"reactive",[23,208,209],{},"watch"," などを使う → ",[23,212,25],{},[174,214,215,216],{},"純粋な変換関数（日付フォーマット等） → ",[23,217,218],{},"utils/",[19,220,221,222,224],{},"※ Nuxt 3では ",[23,223,218],{}," ディレクトリも自動インポートの対象です。",[15,226,227],{"id":227},"なぜcomposableを使うのか",[167,229,231],{"id":230},"_1-ロジックの再利用","1. ロジックの再利用",[19,233,234],{},"同じロジックを複数のコンポーネントで使いたい場合、composableに抽出すると1箇所にまとめられます。",[19,236,237],{},[28,238,239],{},"Before: 各コンポーネントで同じコードを書く",[241,242,247],"pre",{"className":243,"code":244,"language":245,"meta":246,"style":246},"language-typescript shiki shiki-themes vitesse-light vitesse-light","// ComponentA.vue\n\u003Cscript setup>\nconst x = ref(0)\nconst y = ref(0)\n\nfunction update(event) {\n  x.value = event.pageX\n  y.value = event.pageY\n}\n\nonMounted(() => window.addEventListener('mousemove', update))\nonUnmounted(() => window.removeEventListener('mousemove', update))\n\u003C/script>\n\n// ComponentB.vue でも同じコードを書く...\n","typescript","",[23,248,249,258,275,302,320,327,347,368,387,393,398,438,469,481,486],{"__ignoreMap":246},[250,251,254],"span",{"class":252,"line":253},"line",1,[250,255,257],{"class":256},"sxvE3","// ComponentA.vue\n",[250,259,261,265,269,272],{"class":252,"line":260},2,[250,262,264],{"class":263},"shFtX","\u003C",[250,266,268],{"class":267},"s4oTP","script",[250,270,271],{"class":267}," setup",[250,273,274],{"class":263},">\n",[250,276,278,282,285,288,292,295,299],{"class":252,"line":277},3,[250,279,281],{"class":280},"stQ0i","const ",[250,283,284],{"class":267},"x",[250,286,287],{"class":263}," =",[250,289,291],{"class":290},"senZ8"," ref",[250,293,294],{"class":263},"(",[250,296,298],{"class":297},"sM54T","0",[250,300,301],{"class":263},")\n",[250,303,305,307,310,312,314,316,318],{"class":252,"line":304},4,[250,306,281],{"class":280},[250,308,309],{"class":267},"y",[250,311,287],{"class":263},[250,313,291],{"class":290},[250,315,294],{"class":263},[250,317,298],{"class":297},[250,319,301],{"class":263},[250,321,323],{"class":252,"line":322},5,[250,324,326],{"emptyLinePlaceholder":325},true,"\n",[250,328,330,333,336,338,341,344],{"class":252,"line":329},6,[250,331,332],{"class":280},"function",[250,334,335],{"class":290}," update",[250,337,294],{"class":263},[250,339,340],{"class":267},"event",[250,342,343],{"class":263},")",[250,345,346],{"class":263}," {\n",[250,348,350,353,355,358,360,363,365],{"class":252,"line":349},7,[250,351,352],{"class":267},"  x",[250,354,148],{"class":263},[250,356,357],{"class":267},"value",[250,359,287],{"class":263},[250,361,362],{"class":267}," event",[250,364,148],{"class":263},[250,366,367],{"class":267},"pageX\n",[250,369,371,374,376,378,380,382,384],{"class":252,"line":370},8,[250,372,373],{"class":267},"  y",[250,375,148],{"class":263},[250,377,357],{"class":267},[250,379,287],{"class":263},[250,381,362],{"class":267},[250,383,148],{"class":263},[250,385,386],{"class":267},"pageY\n",[250,388,390],{"class":252,"line":389},9,[250,391,392],{"class":263},"}\n",[250,394,396],{"class":252,"line":395},10,[250,397,326],{"emptyLinePlaceholder":325},[250,399,401,404,407,410,413,415,418,420,424,428,430,433,435],{"class":252,"line":400},11,[250,402,403],{"class":290},"onMounted",[250,405,406],{"class":263},"(()",[250,408,409],{"class":263}," =>",[250,411,412],{"class":267}," window",[250,414,148],{"class":263},[250,416,417],{"class":290},"addEventListener",[250,419,294],{"class":263},[250,421,423],{"class":422},"sMJiu","'",[250,425,427],{"class":426},"sdGka","mousemove",[250,429,423],{"class":422},[250,431,432],{"class":263},",",[250,434,335],{"class":267},[250,436,437],{"class":263},"))\n",[250,439,441,444,446,448,450,452,455,457,459,461,463,465,467],{"class":252,"line":440},12,[250,442,443],{"class":290},"onUnmounted",[250,445,406],{"class":263},[250,447,409],{"class":263},[250,449,412],{"class":267},[250,451,148],{"class":263},[250,453,454],{"class":290},"removeEventListener",[250,456,294],{"class":263},[250,458,423],{"class":422},[250,460,427],{"class":426},[250,462,423],{"class":422},[250,464,432],{"class":263},[250,466,335],{"class":267},[250,468,437],{"class":263},[250,470,472,474,477,479],{"class":252,"line":471},13,[250,473,264],{"class":263},[250,475,476],{"class":280},"/",[250,478,268],{"class":267},[250,480,274],{"class":263},[250,482,484],{"class":252,"line":483},14,[250,485,326],{"emptyLinePlaceholder":325},[250,487,489],{"class":252,"line":488},15,[250,490,491],{"class":256},"// ComponentB.vue でも同じコードを書く...\n",[19,493,494],{},[28,495,496],{},"After: composableに抽出",[241,498,500],{"className":243,"code":499,"language":245,"meta":246,"style":246},"// composables/useMouse.ts\nexport function useMouse() {\n  const x = ref(0)\n  const y = ref(0)\n\n  function update(event: MouseEvent) {\n    x.value = event.pageX\n    y.value = event.pageY\n  }\n\n  onMounted(() => window.addEventListener('mousemove', update))\n  onUnmounted(() => window.removeEventListener('mousemove', update))\n\n  return { x, y }\n}\n",[23,501,502,507,524,541,557,561,583,600,617,622,626,655,684,688,705],{"__ignoreMap":246},[250,503,504],{"class":252,"line":253},[250,505,506],{"class":256},"// composables/useMouse.ts\n",[250,508,509,513,516,519,522],{"class":252,"line":260},[250,510,512],{"class":511},"sHkkW","export",[250,514,515],{"class":280}," function",[250,517,518],{"class":290}," useMouse",[250,520,521],{"class":263},"()",[250,523,346],{"class":263},[250,525,526,529,531,533,535,537,539],{"class":252,"line":277},[250,527,528],{"class":280},"  const ",[250,530,284],{"class":267},[250,532,287],{"class":263},[250,534,291],{"class":290},[250,536,294],{"class":263},[250,538,298],{"class":297},[250,540,301],{"class":263},[250,542,543,545,547,549,551,553,555],{"class":252,"line":304},[250,544,528],{"class":280},[250,546,309],{"class":267},[250,548,287],{"class":263},[250,550,291],{"class":290},[250,552,294],{"class":263},[250,554,298],{"class":297},[250,556,301],{"class":263},[250,558,559],{"class":252,"line":322},[250,560,326],{"emptyLinePlaceholder":325},[250,562,563,566,568,570,572,575,579,581],{"class":252,"line":329},[250,564,565],{"class":280},"  function",[250,567,335],{"class":290},[250,569,294],{"class":263},[250,571,340],{"class":267},[250,573,574],{"class":263},": ",[250,576,578],{"class":577},"sSkh3","MouseEvent",[250,580,343],{"class":263},[250,582,346],{"class":263},[250,584,585,588,590,592,594,596,598],{"class":252,"line":349},[250,586,587],{"class":267},"    x",[250,589,148],{"class":263},[250,591,357],{"class":267},[250,593,287],{"class":263},[250,595,362],{"class":267},[250,597,148],{"class":263},[250,599,367],{"class":267},[250,601,602,605,607,609,611,613,615],{"class":252,"line":370},[250,603,604],{"class":267},"    y",[250,606,148],{"class":263},[250,608,357],{"class":267},[250,610,287],{"class":263},[250,612,362],{"class":267},[250,614,148],{"class":263},[250,616,386],{"class":267},[250,618,619],{"class":252,"line":389},[250,620,621],{"class":263},"  }\n",[250,623,624],{"class":252,"line":395},[250,625,326],{"emptyLinePlaceholder":325},[250,627,628,631,633,635,637,639,641,643,645,647,649,651,653],{"class":252,"line":400},[250,629,630],{"class":290},"  onMounted",[250,632,406],{"class":263},[250,634,409],{"class":263},[250,636,412],{"class":267},[250,638,148],{"class":263},[250,640,417],{"class":290},[250,642,294],{"class":263},[250,644,423],{"class":422},[250,646,427],{"class":426},[250,648,423],{"class":422},[250,650,432],{"class":263},[250,652,335],{"class":267},[250,654,437],{"class":263},[250,656,657,660,662,664,666,668,670,672,674,676,678,680,682],{"class":252,"line":440},[250,658,659],{"class":290},"  onUnmounted",[250,661,406],{"class":263},[250,663,409],{"class":263},[250,665,412],{"class":267},[250,667,148],{"class":263},[250,669,454],{"class":290},[250,671,294],{"class":263},[250,673,423],{"class":422},[250,675,427],{"class":426},[250,677,423],{"class":422},[250,679,432],{"class":263},[250,681,335],{"class":267},[250,683,437],{"class":263},[250,685,686],{"class":252,"line":471},[250,687,326],{"emptyLinePlaceholder":325},[250,689,690,693,696,698,700,702],{"class":252,"line":483},[250,691,692],{"class":511},"  return",[250,694,695],{"class":263}," { ",[250,697,284],{"class":267},[250,699,115],{"class":263},[250,701,309],{"class":267},[250,703,704],{"class":263}," }\n",[250,706,707],{"class":252,"line":488},[250,708,392],{"class":263},[241,710,712],{"className":243,"code":711,"language":245,"meta":246,"style":246},"// 各コンポーネントで使用\n\u003Cscript setup>\nconst { x, y } = useMouse()\n\u003C/script>\n\n\u003Ctemplate>Mouse position: {{ x }}, {{ y }}\u003C/template>\n",[23,713,714,719,729,754,764,768],{"__ignoreMap":246},[250,715,716],{"class":252,"line":253},[250,717,718],{"class":256},"// 各コンポーネントで使用\n",[250,720,721,723,725,727],{"class":252,"line":260},[250,722,264],{"class":263},[250,724,268],{"class":267},[250,726,271],{"class":267},[250,728,274],{"class":263},[250,730,731,733,736,739,741,744,747,749,751],{"class":252,"line":277},[250,732,281],{"class":280},[250,734,735],{"class":263},"{",[250,737,738],{"class":267}," x",[250,740,432],{"class":263},[250,742,743],{"class":267}," y",[250,745,746],{"class":263}," }",[250,748,287],{"class":263},[250,750,518],{"class":290},[250,752,753],{"class":263},"()\n",[250,755,756,758,760,762],{"class":252,"line":304},[250,757,264],{"class":263},[250,759,476],{"class":280},[250,761,268],{"class":267},[250,763,274],{"class":263},[250,765,766],{"class":252,"line":322},[250,767,326],{"emptyLinePlaceholder":325},[250,769,770,772,775,778,781,784,787,790,792,795,797,800,802,804],{"class":252,"line":329},[250,771,264],{"class":263},[250,773,774],{"class":577},"template",[250,776,777],{"class":263},">",[250,779,780],{"class":267},"Mouse",[250,782,783],{"class":290}," position",[250,785,786],{"class":263},":",[250,788,789],{"class":263}," {{",[250,791,738],{"class":267},[250,793,794],{"class":263}," }}, {{ ",[250,796,309],{"class":267},[250,798,799],{"class":263}," }}\u003C",[250,801,476],{"class":280},[250,803,774],{"class":267},[250,805,274],{"class":263},[167,807,809],{"id":808},"_2-コードの組織化","2. コードの組織化",[19,811,812],{},"大きなコンポーネントを小さな関数に分割できます。関連するロジックをグループ化することで、コードの見通しが良くなります。",[167,814,816],{"id":815},"_3-テストの容易さ","3. テストの容易さ",[19,818,819],{},"composableは独立した関数なので、コンポーネントに依存せず単体テストを書けます。",[15,821,823],{"id":822},"命名規則-useプレフィックス","命名規則: useプレフィックス",[19,825,826,827,833],{},"Vue公式の慣例として、composable関数は",[28,828,829,832],{},[23,830,831],{},"use"," で始まるcamelCase","で命名します。",[241,835,837],{"className":243,"code":836,"language":245,"meta":246,"style":246},"// ✅ 推奨\nuseMouse()\nuseFetch()\nuseAuth()\nuseTabQuerySync()\n\n// ❌ 非推奨\ngetMouse()\nfetchData()\nauthHelper()\n",[23,838,839,844,851,858,865,872,876,881,888,895],{"__ignoreMap":246},[250,840,841],{"class":252,"line":253},[250,842,843],{"class":256},"// ✅ 推奨\n",[250,845,846,849],{"class":252,"line":260},[250,847,848],{"class":290},"useMouse",[250,850,753],{"class":263},[250,852,853,856],{"class":252,"line":277},[250,854,855],{"class":290},"useFetch",[250,857,753],{"class":263},[250,859,860,863],{"class":252,"line":304},[250,861,862],{"class":290},"useAuth",[250,864,753],{"class":263},[250,866,867,870],{"class":252,"line":322},[250,868,869],{"class":290},"useTabQuerySync",[250,871,753],{"class":263},[250,873,874],{"class":252,"line":329},[250,875,326],{"emptyLinePlaceholder":325},[250,877,878],{"class":252,"line":349},[250,879,880],{"class":256},"// ❌ 非推奨\n",[250,882,883,886],{"class":252,"line":370},[250,884,885],{"class":290},"getMouse",[250,887,753],{"class":263},[250,889,890,893],{"class":252,"line":389},[250,891,892],{"class":290},"fetchData",[250,894,753],{"class":263},[250,896,897,900],{"class":252,"line":395},[250,898,899],{"class":290},"authHelper",[250,901,753],{"class":263},[19,903,904,905,115,908,911],{},"この命名規則により、関数がcomposableであることが一目でわかります。React Hooksの ",[23,906,907],{},"useState",[23,909,910],{},"useEffect"," と同じ発想です。",[15,913,915],{"id":914},"nuxt-2とnuxt-3の違い","Nuxt 2とNuxt 3の違い",[167,917,919],{"id":918},"nuxt-2の時代","Nuxt 2の時代",[19,921,922,923,925],{},"Nuxt 2には ",[23,924,25],{}," ディレクトリは存在しませんでした。再利用可能なロジックは以下の場所に配置されていました。",[171,927,928,934,939],{},[174,929,930,933],{},[23,931,932],{},"plugins/"," - プラグインとして登録",[174,935,936,938],{},[23,937,218],{}," - ユーティリティ関数",[174,940,941,944],{},[28,942,943],{},"mixins"," - Vue 2のコード共有方法",[19,946,947],{},"mixinsはVue 2でコンポーネント間のコード共有に使われていましたが、以下の問題がありました。",[171,949,950,953,956],{},[174,951,952],{},"名前の衝突が起きやすい",[174,954,955],{},"どこから来たプロパティか追跡しにくい",[174,957,958],{},"複数のmixinを組み合わせると複雑になる",[167,960,962],{"id":961},"nuxt-3で変わったこと","Nuxt 3で変わったこと",[19,964,965,966,968,969,972],{},"Nuxt 3では ",[23,967,25],{}," ディレクトリが導入され、",[28,970,971],{},"自動インポート","が標準機能となりました。",[241,974,979],{"className":975,"code":977,"language":978},[976],"language-text","project/\n├── composables/\n│   ├── useAuth.ts      ← 自動インポートされる\n│   └── useFetch.ts     ← 自動インポートされる\n├── components/\n│   └── Header.vue\n└── pages/\n    └── index.vue\n","text",[23,980,977],{"__ignoreMap":246},[19,982,158,983],{},[160,984,987],{"href":985,"rel":986},"https://nuxt.com/docs/guide/directory-structure/composables",[164],"Nuxt 3 - Composables Directory",[15,989,990],{"id":990},"自動インポートの仕組み",[19,992,993,994,996,997,1000],{},"Nuxt 3では、",[23,995,25],{}," ディレクトリ内のファイルは",[28,998,999],{},"自動的にインポート","されます。",[167,1002,1003],{"id":1003},"自動インポートの注意点",[19,1005,1006,1007,1010],{},"便利な反面、",[28,1008,1009],{},"名前の衝突","が起きやすい点に注意が必要です。",[241,1012,1014],{"className":243,"code":1013,"language":245,"meta":246,"style":246},"// composables/useData.ts\nexport function useData() { ... }\n\n// 別のライブラリにもuseData()がある場合、衝突する\n",[23,1015,1016,1021,1040,1044],{"__ignoreMap":246},[250,1017,1018],{"class":252,"line":253},[250,1019,1020],{"class":256},"// composables/useData.ts\n",[250,1022,1023,1025,1027,1030,1032,1035,1038],{"class":252,"line":260},[250,1024,512],{"class":511},[250,1026,515],{"class":280},[250,1028,1029],{"class":290}," useData",[250,1031,521],{"class":263},[250,1033,1034],{"class":263}," {",[250,1036,1037],{"class":263}," ...",[250,1039,704],{"class":263},[250,1041,1042],{"class":252,"line":277},[250,1043,326],{"emptyLinePlaceholder":325},[250,1045,1046],{"class":252,"line":304},[250,1047,1048],{"class":256},"// 別のライブラリにもuseData()がある場合、衝突する\n",[19,1050,1051,1052,115,1055,1058],{},"命名規則を決めてプレフィックスをつける（例: ",[23,1053,1054],{},"useAppData",[23,1056,1057],{},"useMyData","）と安全です。",[241,1060,1062],{"className":243,"code":1061,"language":245,"meta":246,"style":246},"// composables/useCounter.ts\nexport function useCounter() {\n  const count = ref(0)\n  const increment = () => count.value++\n  return { count, increment }\n}\n",[23,1063,1064,1069,1082,1099,1123,1137],{"__ignoreMap":246},[250,1065,1066],{"class":252,"line":253},[250,1067,1068],{"class":256},"// composables/useCounter.ts\n",[250,1070,1071,1073,1075,1078,1080],{"class":252,"line":260},[250,1072,512],{"class":511},[250,1074,515],{"class":280},[250,1076,1077],{"class":290}," useCounter",[250,1079,521],{"class":263},[250,1081,346],{"class":263},[250,1083,1084,1086,1089,1091,1093,1095,1097],{"class":252,"line":277},[250,1085,528],{"class":280},[250,1087,1088],{"class":267},"count",[250,1090,287],{"class":263},[250,1092,291],{"class":290},[250,1094,294],{"class":263},[250,1096,298],{"class":297},[250,1098,301],{"class":263},[250,1100,1101,1103,1106,1108,1111,1113,1116,1118,1120],{"class":252,"line":304},[250,1102,528],{"class":280},[250,1104,1105],{"class":290},"increment",[250,1107,287],{"class":263},[250,1109,1110],{"class":263}," ()",[250,1112,409],{"class":263},[250,1114,1115],{"class":267}," count",[250,1117,148],{"class":263},[250,1119,357],{"class":267},[250,1121,1122],{"class":280},"++\n",[250,1124,1125,1127,1129,1131,1133,1135],{"class":252,"line":322},[250,1126,692],{"class":511},[250,1128,695],{"class":263},[250,1130,1088],{"class":267},[250,1132,115],{"class":263},[250,1134,1105],{"class":267},[250,1136,704],{"class":263},[250,1138,1139],{"class":252,"line":329},[250,1140,392],{"class":263},[241,1142,1144],{"className":243,"code":1143,"language":245,"meta":246,"style":246},"// pages/index.vue - importなしで使える！\n\u003Cscript setup>\nconst { count, increment } = useCounter()\n\u003C/script>\n",[23,1145,1146,1151,1161,1182],{"__ignoreMap":246},[250,1147,1148],{"class":252,"line":253},[250,1149,1150],{"class":256},"// pages/index.vue - importなしで使える！\n",[250,1152,1153,1155,1157,1159],{"class":252,"line":260},[250,1154,264],{"class":263},[250,1156,268],{"class":267},[250,1158,271],{"class":267},[250,1160,274],{"class":263},[250,1162,1163,1165,1167,1169,1171,1174,1176,1178,1180],{"class":252,"line":277},[250,1164,281],{"class":280},[250,1166,735],{"class":263},[250,1168,1115],{"class":267},[250,1170,432],{"class":263},[250,1172,1173],{"class":267}," increment",[250,1175,746],{"class":263},[250,1177,287],{"class":263},[250,1179,1077],{"class":290},[250,1181,753],{"class":263},[250,1183,1184,1186,1188,1190],{"class":252,"line":304},[250,1185,264],{"class":263},[250,1187,476],{"class":280},[250,1189,268],{"class":267},[250,1191,274],{"class":263},[167,1193,1195],{"id":1194},"注意-ネストされたディレクトリ","注意: ネストされたディレクトリ",[19,1197,1198,1199,1201,1202,1205],{},"Nuxt公式ドキュメントによると、",[23,1200,25],{}," の",[28,1203,1204],{},"トップレベルのみ","がデフォルトでスキャンされます。サブディレクトリは自動インポートの対象外です。",[19,1207,1208],{},"※ Nuxtのバージョンや設定によって挙動が異なる場合があります。プロジェクトで確認してください。",[241,1210,1213],{"className":1211,"code":1212,"language":978},[976],"composables/\n├── useAuth.ts           ← ✅ 自動インポートされる\n├── useFetch.ts          ← ✅ 自動インポートされる\n└── validation/\n    └── useEmail.ts      ← ❌ 自動インポートされない\n",[23,1214,1212],{"__ignoreMap":246},[19,1216,1217],{},"サブディレクトリのcomposableを使う場合は、以下のいずれかの方法を取ります。",[19,1219,1220],{},[28,1221,1222],{},"方法1: index.tsから再エクスポート（推奨）",[241,1224,1226],{"className":243,"code":1225,"language":245,"meta":246,"style":246},"// composables/index.ts\nexport { useEmail } from './validation/useEmail'\n",[23,1227,1228,1233],{"__ignoreMap":246},[250,1229,1230],{"class":252,"line":253},[250,1231,1232],{"class":256},"// composables/index.ts\n",[250,1234,1235,1237,1239,1242,1244,1247,1250,1253],{"class":252,"line":260},[250,1236,512],{"class":511},[250,1238,1034],{"class":263},[250,1240,1241],{"class":267}," useEmail",[250,1243,746],{"class":263},[250,1245,1246],{"class":511}," from",[250,1248,1249],{"class":422}," '",[250,1251,1252],{"class":426},"./validation/useEmail",[250,1254,1255],{"class":422},"'\n",[19,1257,1258],{},[28,1259,1260],{},"方法2: nuxt.config.tsで設定",[241,1262,1264],{"className":243,"code":1263,"language":245,"meta":246,"style":246},"// nuxt.config.ts\nexport default defineNuxtConfig({\n  imports: {\n    dirs: ['composables/validation']\n  }\n})\n",[23,1265,1266,1271,1284,1293,1311,1315],{"__ignoreMap":246},[250,1267,1268],{"class":252,"line":253},[250,1269,1270],{"class":256},"// nuxt.config.ts\n",[250,1272,1273,1275,1278,1281],{"class":252,"line":260},[250,1274,512],{"class":511},[250,1276,1277],{"class":511}," default",[250,1279,1280],{"class":290}," defineNuxtConfig",[250,1282,1283],{"class":263},"({\n",[250,1285,1286,1290],{"class":252,"line":277},[250,1287,1289],{"class":1288},"sz8Xr","  imports",[250,1291,1292],{"class":263},": {\n",[250,1294,1295,1298,1301,1303,1306,1308],{"class":252,"line":304},[250,1296,1297],{"class":1288},"    dirs",[250,1299,1300],{"class":263},": [",[250,1302,423],{"class":422},[250,1304,1305],{"class":426},"composables/validation",[250,1307,423],{"class":422},[250,1309,1310],{"class":263},"]\n",[250,1312,1313],{"class":252,"line":322},[250,1314,621],{"class":263},[250,1316,1317],{"class":252,"line":329},[250,1318,1319],{"class":263},"})\n",[15,1321,1323],{"id":1322},"実践例-タブ同期のcomposable","実践例: タブ同期のcomposable",[19,1325,1326],{},"実際のプロジェクトで使用したcomposableの例を紹介します。複数のタブコンポーネントで共通するURL同期ロジックを抽出しました。",[241,1328,1330],{"className":243,"code":1329,"language":245,"meta":246,"style":246},"// composables/useTabQuerySync.ts\ninterface UseTabQuerySyncOptions {\n  tabName: string\n  buildQuery: () => Record\u003Cstring, string | undefined>\n  onActivate?: () => void\n}\n\nexport function useTabQuerySync(options: UseTabQuerySyncOptions) {\n  const { tabName, buildQuery, onActivate } = options\n  const router = useRouter()\n  const currentTab = inject\u003CRef\u003Cstring>>('currentTab')\n  const isInitialized = ref(false)\n\n  function updateQueryParams() {\n    if (!import.meta.client || !isInitialized.value) return\n    if (currentTab?.value !== tabName) return\n\n    const rawQuery = buildQuery()\n    const query: Record\u003Cstring, string> = { tab: tabName }\n\n    for (const [key, value] of Object.entries(rawQuery)) {\n      if (value !== undefined && value !== '') {\n        query[key] = value\n      }\n    }\n\n    router.push({ query })\n  }\n\n  watch(() => currentTab?.value, (newTab) => {\n    if (newTab === tabName && isInitialized.value) {\n      nextTick(() => {\n        updateQueryParams()\n        onActivate?.()\n      })\n    }\n  })\n\n  function markInitialized() {\n    isInitialized.value = true\n    updateQueryParams()\n  }\n\n  return {\n    currentTab,\n    isInitialized,\n    updateQueryParams,\n    markInitialized,\n  }\n}\n",[23,1331,1332,1337,1347,1357,1385,1398,1402,1406,1429,1455,1469,1501,1519,1523,1534,1575,1598,1603,1618,1653,1658,1702,1731,1748,1754,1760,1765,1784,1789,1794,1824,1851,1863,1871,1880,1886,1891,1897,1902,1914,1929,1937,1942,1947,1954,1963,1970,1977,1985,1990],{"__ignoreMap":246},[250,1333,1334],{"class":252,"line":253},[250,1335,1336],{"class":256},"// composables/useTabQuerySync.ts\n",[250,1338,1339,1342,1345],{"class":252,"line":260},[250,1340,1341],{"class":280},"interface",[250,1343,1344],{"class":577}," UseTabQuerySyncOptions",[250,1346,346],{"class":263},[250,1348,1349,1352,1354],{"class":252,"line":277},[250,1350,1351],{"class":267},"  tabName",[250,1353,574],{"class":263},[250,1355,1356],{"class":577},"string\n",[250,1358,1359,1362,1365,1368,1370,1373,1375,1377,1380,1383],{"class":252,"line":304},[250,1360,1361],{"class":290},"  buildQuery",[250,1363,1364],{"class":263},": () => ",[250,1366,1367],{"class":577},"Record",[250,1369,264],{"class":263},[250,1371,1372],{"class":577},"string",[250,1374,115],{"class":263},[250,1376,1372],{"class":577},[250,1378,1379],{"class":263}," | ",[250,1381,1382],{"class":280},"undefined",[250,1384,274],{"class":263},[250,1386,1387,1390,1393,1395],{"class":252,"line":322},[250,1388,1389],{"class":290},"  onActivate",[250,1391,1392],{"class":280},"?",[250,1394,1364],{"class":263},[250,1396,1397],{"class":577},"void\n",[250,1399,1400],{"class":252,"line":329},[250,1401,392],{"class":263},[250,1403,1404],{"class":252,"line":349},[250,1405,326],{"emptyLinePlaceholder":325},[250,1407,1408,1410,1412,1415,1417,1420,1422,1425,1427],{"class":252,"line":370},[250,1409,512],{"class":511},[250,1411,515],{"class":280},[250,1413,1414],{"class":290}," useTabQuerySync",[250,1416,294],{"class":263},[250,1418,1419],{"class":267},"options",[250,1421,574],{"class":263},[250,1423,1424],{"class":577},"UseTabQuerySyncOptions",[250,1426,343],{"class":263},[250,1428,346],{"class":263},[250,1430,1431,1433,1435,1438,1440,1443,1445,1448,1450,1452],{"class":252,"line":389},[250,1432,528],{"class":280},[250,1434,735],{"class":263},[250,1436,1437],{"class":267}," tabName",[250,1439,432],{"class":263},[250,1441,1442],{"class":267}," buildQuery",[250,1444,432],{"class":263},[250,1446,1447],{"class":267}," onActivate",[250,1449,746],{"class":263},[250,1451,287],{"class":263},[250,1453,1454],{"class":267}," options\n",[250,1456,1457,1459,1462,1464,1467],{"class":252,"line":395},[250,1458,528],{"class":280},[250,1460,1461],{"class":267},"router",[250,1463,287],{"class":263},[250,1465,1466],{"class":290}," useRouter",[250,1468,753],{"class":263},[250,1470,1471,1473,1476,1478,1481,1483,1486,1488,1490,1493,1495,1497,1499],{"class":252,"line":400},[250,1472,528],{"class":280},[250,1474,1475],{"class":267},"currentTab",[250,1477,287],{"class":263},[250,1479,1480],{"class":290}," inject",[250,1482,264],{"class":263},[250,1484,1485],{"class":577},"Ref",[250,1487,264],{"class":263},[250,1489,1372],{"class":577},[250,1491,1492],{"class":263},">>(",[250,1494,423],{"class":422},[250,1496,1475],{"class":426},[250,1498,423],{"class":422},[250,1500,301],{"class":263},[250,1502,1503,1505,1508,1510,1512,1514,1517],{"class":252,"line":440},[250,1504,528],{"class":280},[250,1506,1507],{"class":267},"isInitialized",[250,1509,287],{"class":263},[250,1511,291],{"class":290},[250,1513,294],{"class":263},[250,1515,1516],{"class":511},"false",[250,1518,301],{"class":263},[250,1520,1521],{"class":252,"line":471},[250,1522,326],{"emptyLinePlaceholder":325},[250,1524,1525,1527,1530,1532],{"class":252,"line":483},[250,1526,565],{"class":280},[250,1528,1529],{"class":290}," updateQueryParams",[250,1531,521],{"class":263},[250,1533,346],{"class":263},[250,1535,1536,1539,1542,1545,1548,1550,1553,1555,1558,1561,1564,1566,1568,1570,1572],{"class":252,"line":488},[250,1537,1538],{"class":511},"    if",[250,1540,1541],{"class":263}," (",[250,1543,1544],{"class":280},"!",[250,1546,1547],{"class":511},"import",[250,1549,148],{"class":263},[250,1551,1552],{"class":1288},"meta",[250,1554,148],{"class":263},[250,1556,1557],{"class":267},"client",[250,1559,1560],{"class":280}," ||",[250,1562,1563],{"class":280}," !",[250,1565,1507],{"class":267},[250,1567,148],{"class":263},[250,1569,357],{"class":267},[250,1571,343],{"class":263},[250,1573,1574],{"class":511}," return\n",[250,1576,1578,1580,1582,1584,1587,1589,1592,1594,1596],{"class":252,"line":1577},16,[250,1579,1538],{"class":511},[250,1581,1541],{"class":263},[250,1583,1475],{"class":267},[250,1585,1586],{"class":263},"?.",[250,1588,357],{"class":267},[250,1590,1591],{"class":280}," !==",[250,1593,1437],{"class":267},[250,1595,343],{"class":263},[250,1597,1574],{"class":511},[250,1599,1601],{"class":252,"line":1600},17,[250,1602,326],{"emptyLinePlaceholder":325},[250,1604,1606,1609,1612,1614,1616],{"class":252,"line":1605},18,[250,1607,1608],{"class":280},"    const ",[250,1610,1611],{"class":267},"rawQuery",[250,1613,287],{"class":263},[250,1615,1442],{"class":290},[250,1617,753],{"class":263},[250,1619,1621,1623,1626,1628,1630,1632,1634,1636,1638,1641,1643,1646,1648,1651],{"class":252,"line":1620},19,[250,1622,1608],{"class":280},[250,1624,1625],{"class":267},"query",[250,1627,574],{"class":263},[250,1629,1367],{"class":577},[250,1631,264],{"class":263},[250,1633,1372],{"class":577},[250,1635,115],{"class":263},[250,1637,1372],{"class":577},[250,1639,1640],{"class":263},"> =",[250,1642,695],{"class":263},[250,1644,1645],{"class":1288},"tab",[250,1647,574],{"class":263},[250,1649,1650],{"class":267},"tabName",[250,1652,704],{"class":263},[250,1654,1656],{"class":252,"line":1655},20,[250,1657,326],{"emptyLinePlaceholder":325},[250,1659,1661,1664,1666,1668,1671,1674,1676,1679,1682,1685,1688,1690,1693,1695,1697,1700],{"class":252,"line":1660},21,[250,1662,1663],{"class":511},"    for",[250,1665,1541],{"class":263},[250,1667,281],{"class":280},[250,1669,1670],{"class":263},"[",[250,1672,1673],{"class":267},"key",[250,1675,432],{"class":263},[250,1677,1678],{"class":267}," value",[250,1680,1681],{"class":263},"]",[250,1683,1684],{"class":280}," of",[250,1686,1687],{"class":267}," Object",[250,1689,148],{"class":263},[250,1691,1692],{"class":290},"entries",[250,1694,294],{"class":263},[250,1696,1611],{"class":267},[250,1698,1699],{"class":263},"))",[250,1701,346],{"class":263},[250,1703,1705,1708,1710,1712,1714,1717,1720,1722,1724,1727,1729],{"class":252,"line":1704},22,[250,1706,1707],{"class":511},"      if",[250,1709,1541],{"class":263},[250,1711,357],{"class":267},[250,1713,1591],{"class":280},[250,1715,1716],{"class":280}," undefined",[250,1718,1719],{"class":280}," &&",[250,1721,1678],{"class":267},[250,1723,1591],{"class":280},[250,1725,1726],{"class":422}," ''",[250,1728,343],{"class":263},[250,1730,346],{"class":263},[250,1732,1734,1737,1739,1741,1743,1745],{"class":252,"line":1733},23,[250,1735,1736],{"class":267},"        query",[250,1738,1670],{"class":263},[250,1740,1673],{"class":267},[250,1742,1681],{"class":263},[250,1744,287],{"class":263},[250,1746,1747],{"class":267}," value\n",[250,1749,1751],{"class":252,"line":1750},24,[250,1752,1753],{"class":263},"      }\n",[250,1755,1757],{"class":252,"line":1756},25,[250,1758,1759],{"class":263},"    }\n",[250,1761,1763],{"class":252,"line":1762},26,[250,1764,326],{"emptyLinePlaceholder":325},[250,1766,1768,1771,1773,1776,1779,1781],{"class":252,"line":1767},27,[250,1769,1770],{"class":267},"    router",[250,1772,148],{"class":263},[250,1774,1775],{"class":290},"push",[250,1777,1778],{"class":263},"({ ",[250,1780,1625],{"class":267},[250,1782,1783],{"class":263}," })\n",[250,1785,1787],{"class":252,"line":1786},28,[250,1788,621],{"class":263},[250,1790,1792],{"class":252,"line":1791},29,[250,1793,326],{"emptyLinePlaceholder":325},[250,1795,1797,1800,1802,1804,1807,1809,1811,1813,1815,1818,1820,1822],{"class":252,"line":1796},30,[250,1798,1799],{"class":290},"  watch",[250,1801,406],{"class":263},[250,1803,409],{"class":263},[250,1805,1806],{"class":267}," currentTab",[250,1808,1586],{"class":263},[250,1810,357],{"class":267},[250,1812,432],{"class":263},[250,1814,1541],{"class":263},[250,1816,1817],{"class":267},"newTab",[250,1819,343],{"class":263},[250,1821,409],{"class":263},[250,1823,346],{"class":263},[250,1825,1827,1829,1831,1833,1836,1838,1840,1843,1845,1847,1849],{"class":252,"line":1826},31,[250,1828,1538],{"class":511},[250,1830,1541],{"class":263},[250,1832,1817],{"class":267},[250,1834,1835],{"class":280}," ===",[250,1837,1437],{"class":267},[250,1839,1719],{"class":280},[250,1841,1842],{"class":267}," isInitialized",[250,1844,148],{"class":263},[250,1846,357],{"class":267},[250,1848,343],{"class":263},[250,1850,346],{"class":263},[250,1852,1854,1857,1859,1861],{"class":252,"line":1853},32,[250,1855,1856],{"class":290},"      nextTick",[250,1858,406],{"class":263},[250,1860,409],{"class":263},[250,1862,346],{"class":263},[250,1864,1866,1869],{"class":252,"line":1865},33,[250,1867,1868],{"class":290},"        updateQueryParams",[250,1870,753],{"class":263},[250,1872,1874,1877],{"class":252,"line":1873},34,[250,1875,1876],{"class":290},"        onActivate",[250,1878,1879],{"class":263},"?.()\n",[250,1881,1883],{"class":252,"line":1882},35,[250,1884,1885],{"class":263},"      })\n",[250,1887,1889],{"class":252,"line":1888},36,[250,1890,1759],{"class":263},[250,1892,1894],{"class":252,"line":1893},37,[250,1895,1896],{"class":263},"  })\n",[250,1898,1900],{"class":252,"line":1899},38,[250,1901,326],{"emptyLinePlaceholder":325},[250,1903,1905,1907,1910,1912],{"class":252,"line":1904},39,[250,1906,565],{"class":280},[250,1908,1909],{"class":290}," markInitialized",[250,1911,521],{"class":263},[250,1913,346],{"class":263},[250,1915,1917,1920,1922,1924,1926],{"class":252,"line":1916},40,[250,1918,1919],{"class":267},"    isInitialized",[250,1921,148],{"class":263},[250,1923,357],{"class":267},[250,1925,287],{"class":263},[250,1927,1928],{"class":511}," true\n",[250,1930,1932,1935],{"class":252,"line":1931},41,[250,1933,1934],{"class":290},"    updateQueryParams",[250,1936,753],{"class":263},[250,1938,1940],{"class":252,"line":1939},42,[250,1941,621],{"class":263},[250,1943,1945],{"class":252,"line":1944},43,[250,1946,326],{"emptyLinePlaceholder":325},[250,1948,1950,1952],{"class":252,"line":1949},44,[250,1951,692],{"class":511},[250,1953,346],{"class":263},[250,1955,1957,1960],{"class":252,"line":1956},45,[250,1958,1959],{"class":267},"    currentTab",[250,1961,1962],{"class":263},",\n",[250,1964,1966,1968],{"class":252,"line":1965},46,[250,1967,1919],{"class":267},[250,1969,1962],{"class":263},[250,1971,1973,1975],{"class":252,"line":1972},47,[250,1974,1934],{"class":267},[250,1976,1962],{"class":263},[250,1978,1980,1983],{"class":252,"line":1979},48,[250,1981,1982],{"class":267},"    markInitialized",[250,1984,1962],{"class":263},[250,1986,1988],{"class":252,"line":1987},49,[250,1989,621],{"class":263},[250,1991,1993],{"class":252,"line":1992},50,[250,1994,392],{"class":263},[19,1996,1997],{},[28,1998,1999],{},"使用側のコンポーネント:",[241,2001,2003],{"className":243,"code":2002,"language":245,"meta":246,"style":246},"// components/tabs/CreditCardTab.vue\n\u003Cscript setup>\nconst { updateQueryParams, markInitialized } = useTabQuerySync({\n  tabName: 'creditcard',\n  buildQuery: () => ({\n    ccYear: selectedYear.value,\n    ccMonth: selectedMonth.value,\n  }),\n})\n\nonMounted(async () => {\n  await loadData()\n  markInitialized()\n})\n\u003C/script>\n",[23,2004,2005,2010,2020,2040,2055,2062,2078,2094,2099,2103,2107,2122,2132,2139,2143],{"__ignoreMap":246},[250,2006,2007],{"class":252,"line":253},[250,2008,2009],{"class":256},"// components/tabs/CreditCardTab.vue\n",[250,2011,2012,2014,2016,2018],{"class":252,"line":260},[250,2013,264],{"class":263},[250,2015,268],{"class":267},[250,2017,271],{"class":267},[250,2019,274],{"class":263},[250,2021,2022,2024,2026,2028,2030,2032,2034,2036,2038],{"class":252,"line":277},[250,2023,281],{"class":280},[250,2025,735],{"class":263},[250,2027,1529],{"class":267},[250,2029,432],{"class":263},[250,2031,1909],{"class":267},[250,2033,746],{"class":263},[250,2035,287],{"class":263},[250,2037,1414],{"class":290},[250,2039,1283],{"class":263},[250,2041,2042,2044,2046,2048,2051,2053],{"class":252,"line":304},[250,2043,1351],{"class":1288},[250,2045,574],{"class":263},[250,2047,423],{"class":422},[250,2049,2050],{"class":426},"creditcard",[250,2052,423],{"class":422},[250,2054,1962],{"class":263},[250,2056,2057,2059],{"class":252,"line":322},[250,2058,1361],{"class":290},[250,2060,2061],{"class":263},": () => ({\n",[250,2063,2064,2067,2069,2072,2074,2076],{"class":252,"line":329},[250,2065,2066],{"class":1288},"    ccYear",[250,2068,574],{"class":263},[250,2070,2071],{"class":267},"selectedYear",[250,2073,148],{"class":263},[250,2075,357],{"class":267},[250,2077,1962],{"class":263},[250,2079,2080,2083,2085,2088,2090,2092],{"class":252,"line":349},[250,2081,2082],{"class":1288},"    ccMonth",[250,2084,574],{"class":263},[250,2086,2087],{"class":267},"selectedMonth",[250,2089,148],{"class":263},[250,2091,357],{"class":267},[250,2093,1962],{"class":263},[250,2095,2096],{"class":252,"line":370},[250,2097,2098],{"class":263},"  }),\n",[250,2100,2101],{"class":252,"line":389},[250,2102,1319],{"class":263},[250,2104,2105],{"class":252,"line":395},[250,2106,326],{"emptyLinePlaceholder":325},[250,2108,2109,2111,2113,2116,2118,2120],{"class":252,"line":400},[250,2110,403],{"class":290},[250,2112,294],{"class":263},[250,2114,2115],{"class":280},"async",[250,2117,1110],{"class":263},[250,2119,409],{"class":263},[250,2121,346],{"class":263},[250,2123,2124,2127,2130],{"class":252,"line":440},[250,2125,2126],{"class":511},"  await",[250,2128,2129],{"class":290}," loadData",[250,2131,753],{"class":263},[250,2133,2134,2137],{"class":252,"line":471},[250,2135,2136],{"class":290},"  markInitialized",[250,2138,753],{"class":263},[250,2140,2141],{"class":252,"line":483},[250,2142,1319],{"class":263},[250,2144,2145,2147,2149,2151],{"class":252,"line":488},[250,2146,264],{"class":263},[250,2148,476],{"class":280},[250,2150,268],{"class":267},[250,2152,274],{"class":263},[19,2154,2155],{},"このように、7つのタブコンポーネントで同じロジックを再利用できるようになりました。",[167,2157,2159],{"id":2158},"補足-routerpushとrouterreplace","補足: router.pushとrouter.replace",[19,2161,2162,2163,2166],{},"上記の例では ",[23,2164,2165],{},"router.push()"," を使用しています。",[171,2168,2169,2176],{},[174,2170,2171,2175],{},[28,2172,2173],{},[23,2174,2165],{},": ブラウザ履歴に追加される（戻るボタンで戻れる）",[174,2177,2178,2183],{},[28,2179,2180],{},[23,2181,2182],{},"router.replace()",": 履歴を置換する（戻るボタンで戻れない）",[19,2185,2186,2187,2189,2190,2193],{},"URL同期の用途では、ユーザーが戻るボタンで前の状態に戻りたい場合は ",[23,2188,1775],{},"、履歴を汚したくない場合は ",[23,2191,2192],{},"replace"," を選択します。",[167,2195,2197],{"id":2196},"補足-型の注意点","補足: 型の注意点",[19,2199,2200,2203,2204,2207],{},[23,2201,2202],{},"buildQuery"," で数値を返す場合は、明示的に ",[23,2205,2206],{},"String()"," で変換しておくと安全です。",[241,2209,2211],{"className":243,"code":2210,"language":245,"meta":246,"style":246},"buildQuery: () => ({\n  ccYear: selectedYear.value ? String(selectedYear.value) : undefined,\n  ccMonth: selectedMonth.value ? String(selectedMonth.value) : undefined,\n}),\n",[23,2212,2213,2226,2262,2295],{"__ignoreMap":246},[250,2214,2215,2217,2219,2221,2223],{"class":252,"line":253},[250,2216,2202],{"class":290},[250,2218,786],{"class":263},[250,2220,1110],{"class":263},[250,2222,409],{"class":263},[250,2224,2225],{"class":263}," ({\n",[250,2227,2228,2231,2233,2235,2237,2239,2242,2245,2247,2249,2251,2253,2256,2258,2260],{"class":252,"line":260},[250,2229,2230],{"class":1288},"  ccYear",[250,2232,574],{"class":263},[250,2234,2071],{"class":267},[250,2236,148],{"class":263},[250,2238,357],{"class":267},[250,2240,2241],{"class":280}," ?",[250,2243,2244],{"class":290}," String",[250,2246,294],{"class":263},[250,2248,2071],{"class":267},[250,2250,148],{"class":263},[250,2252,357],{"class":267},[250,2254,2255],{"class":263},") ",[250,2257,786],{"class":280},[250,2259,1716],{"class":280},[250,2261,1962],{"class":263},[250,2263,2264,2267,2269,2271,2273,2275,2277,2279,2281,2283,2285,2287,2289,2291,2293],{"class":252,"line":277},[250,2265,2266],{"class":1288},"  ccMonth",[250,2268,574],{"class":263},[250,2270,2087],{"class":267},[250,2272,148],{"class":263},[250,2274,357],{"class":267},[250,2276,2241],{"class":280},[250,2278,2244],{"class":290},[250,2280,294],{"class":263},[250,2282,2087],{"class":267},[250,2284,148],{"class":263},[250,2286,357],{"class":267},[250,2288,2255],{"class":263},[250,2290,786],{"class":280},[250,2292,1716],{"class":280},[250,2294,1962],{"class":263},[250,2296,2297],{"class":252,"line":304},[250,2298,2299],{"class":263},"}),\n",[15,2301,2302],{"id":2302},"componentとcomposableの使い分け",[19,2304,2305],{},"迷ったときは以下の基準で判断してください。",[19,2307,2308],{},[28,2309,2310],{},"componentを使う場合:",[171,2312,2313,2320,2323],{},[174,2314,2315,2316,2319],{},"UIを描画する（",[23,2317,2318],{},"\u003Ctemplate>"," が必要）",[174,2321,2322],{},"ユーザーに見える要素を作る",[174,2324,2325],{},"例: ボタン、フォーム、カード、モーダル",[19,2327,2328],{},[28,2329,2330],{},"composableを使う場合:",[171,2332,2333,2336,2339,2349],{},[174,2334,2335],{},"UIを持たない",[174,2337,2338],{},"ロジックだけを再利用したい",[174,2340,2341,115,2343,115,2345,2348],{},[23,2342,203],{},[23,2344,209],{},[23,2346,2347],{},"computed"," などの状態管理",[174,2350,2351],{},"例: 認証状態管理、API通信、ローカルストレージ操作",[15,2353,2354],{"id":2354},"まとめ",[171,2356,2357,2363,2371,2379,2386],{},[174,2358,2359,2362],{},[28,2360,2361],{},"composable","はVue Composition APIを使った再利用可能なロジック関数",[174,2364,21,2365,2367,2368,2370],{},[23,2366,25],{}," ディレクトリは",[28,2369,971],{},"が効く",[174,2372,2373,2375,2376,2378],{},[23,2374,34],{}," はUI、",[23,2377,25],{}," はロジック、と明確に分離する",[174,2380,2381,2382,2385],{},"命名は ",[23,2383,2384],{},"useXxx"," の形式で統一する",[174,2387,2388],{},"Nuxt 2のmixinsやplugins内のロジックは、composableに移行すると保守性が上がる",[15,2390,2391],{"id":2391},"参考リンク",[171,2393,2394,2399,2404,2411],{},[174,2395,2396],{},[160,2397,165],{"href":162,"rel":2398},[164],[174,2400,2401],{},[160,2402,987],{"href":985,"rel":2403},[164],[174,2405,2406],{},[160,2407,2410],{"href":2408,"rel":2409},"https://vueschool.io/articles/vuejs-tutorials/understanding-the-directory-structure-in-nuxt-3/",[164],"Vue School - Understanding the Directory structure in Nuxt 3",[174,2412,2413],{},[160,2414,2417],{"href":2415,"rel":2416},"https://nuxt.com/docs/migration/overview",[164],"Nuxt 3 Migration Guide",[2419,2420,2421],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}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 .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .sSkh3, html code.shiki .sSkh3{--shiki-default:#2E8F82;--shiki-dark:#2E8F82}html pre.shiki code .sz8Xr, html code.shiki .sz8Xr{--shiki-default:#998418;--shiki-dark:#998418}",{"title":246,"searchDepth":260,"depth":260,"links":2423},[2424,2425,2428,2433,2434,2438,2442,2446,2447,2448],{"id":17,"depth":260,"text":17},{"id":135,"depth":260,"text":135,"children":2426},[2427],{"id":169,"depth":277,"text":169},{"id":227,"depth":260,"text":227,"children":2429},[2430,2431,2432],{"id":230,"depth":277,"text":231},{"id":808,"depth":277,"text":809},{"id":815,"depth":277,"text":816},{"id":822,"depth":260,"text":823},{"id":914,"depth":260,"text":915,"children":2435},[2436,2437],{"id":918,"depth":277,"text":919},{"id":961,"depth":277,"text":962},{"id":990,"depth":260,"text":990,"children":2439},[2440,2441],{"id":1003,"depth":277,"text":1003},{"id":1194,"depth":277,"text":1195},{"id":1322,"depth":260,"text":1323,"children":2443},[2444,2445],{"id":2158,"depth":277,"text":2159},{"id":2196,"depth":277,"text":2197},{"id":2302,"depth":260,"text":2302},{"id":2354,"depth":260,"text":2354},{"id":2391,"depth":260,"text":2391},"dev",null,"Nuxt 3で導入されたcomposablesディレクトリの仕組みと、componentとの違いを解説。Nuxt 2からの移行ポイントや自動インポートの活用方法も紹介します。","md",{},"/nuxt3-composables-guide",false,"2026-01-21T00:00:00.000Z",{"title":5,"description":2451},"2026-01/2026-01-21/nuxt3-composables-guide",[2460,2461,2462,2463],"Nuxt","Vue","Composition API","フロントエンド","lmcK0HAep3XUjvn8xousbQagStoUBSE8WCOMoig4iKo",[],[2467,2471,2475,2479,2482],{"title":2468,"path":2469,"publishedAt":2470},"人生計画ExcelをWebアプリに移植する設計と実装方針","/life-plan-webapp-migration","2026-01-16T00:00:00.000Z",{"title":2472,"path":2473,"publishedAt":2474},"NuxtでMiller Columns UIを実装する - macOS Finderライクな階層ナビゲーション","/miller-columns-ui","2026-01-13T00:00:00.000Z",{"title":2476,"path":2477,"publishedAt":2478},"全18講に触って動くシミュレーターを入れた日 — PR12本のレビューから講座導線の作り直しまで","/code-course-interactive-rollout","2026-07-26T00:00:00.000Z",{"title":2480,"path":2481,"publishedAt":2478},"インタラクティブ教材を1枚に集約し、本文の線を減らした日","/interactive-content-consolidation",{"title":2483,"path":2484,"publishedAt":2485},"Webサービスの技術構成 2026年版 — Vue.js/Nuxt/Astroの使い分けとその理由","/web-tech-stack-2026","2026-07-22T00:00:00.000Z","https://log.eurekapu.com/og/blog/nuxt3-composables-guide.png?v=2026-01-21T00%3A00%3A00.000Z&title=Nuxt%203%E3%81%AEcomposables%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E5%AE%8C%E5%85%A8%E3%82%AC%E3%82%A4%E3%83%89%20-%20component%E3%81%A8%E3%81%AE%E9%81%95%E3%81%84%E3%81%A8%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91&author=Kei%20Komatsu&sig=0fcf1e5816759c60",1785654996619]