@wooojin/forgen 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/assets/dev-guide/be/README.md +226 -0
  3. package/assets/dev-guide/be/adapters/build-agents-md.sh +63 -0
  4. package/assets/dev-guide/be/principles/common.md +433 -0
  5. package/assets/dev-guide/be/principles/go.md +469 -0
  6. package/assets/dev-guide/be/principles/node.md +388 -0
  7. package/assets/dev-guide/be/skills/go/be-build/SKILL.md +262 -0
  8. package/assets/dev-guide/be/skills/go/be-perf/SKILL.md +308 -0
  9. package/assets/dev-guide/be/skills/go/be-review/SKILL.md +119 -0
  10. package/assets/dev-guide/be/skills/go/be-security/SKILL.md +362 -0
  11. package/assets/dev-guide/be/skills/node/be-build/SKILL.md +239 -0
  12. package/assets/dev-guide/be/skills/node/be-perf/SKILL.md +272 -0
  13. package/assets/dev-guide/be/skills/node/be-review/SKILL.md +118 -0
  14. package/assets/dev-guide/be/skills/node/be-security/SKILL.md +355 -0
  15. package/assets/dev-guide/be/sources/12factor/INDEX.md +53 -0
  16. package/assets/dev-guide/be/sources/api-design/INDEX.md +56 -0
  17. package/assets/dev-guide/be/sources/ddia/INDEX.md +55 -0
  18. package/assets/dev-guide/be/sources/go-runtime/INDEX.md +62 -0
  19. package/assets/dev-guide/be/sources/node-runtime/INDEX.md +60 -0
  20. package/assets/dev-guide/be/sources/otel/INDEX.md +53 -0
  21. package/assets/dev-guide/be/sources/owasp-api/INDEX.md +52 -0
  22. package/assets/dev-guide/be/sources/postgres/INDEX.md +55 -0
  23. package/assets/dev-guide/be/sources/sre-book/INDEX.md +48 -0
  24. package/assets/dev-guide/fe/README.md +197 -0
  25. package/assets/dev-guide/fe/adapters/build-agents-md.sh +63 -0
  26. package/assets/dev-guide/fe/adapters/refresh.sh +68 -0
  27. package/assets/dev-guide/fe/principles/common.md +160 -0
  28. package/assets/dev-guide/fe/principles/react.md +183 -0
  29. package/assets/dev-guide/fe/principles/vue.md +196 -0
  30. package/assets/dev-guide/fe/skills/react/fe-build/SKILL.md +139 -0
  31. package/assets/dev-guide/fe/skills/react/fe-perf/SKILL.md +179 -0
  32. package/assets/dev-guide/fe/skills/react/fe-review/SKILL.md +141 -0
  33. package/assets/dev-guide/fe/skills/vue/fe-build/SKILL.md +148 -0
  34. package/assets/dev-guide/fe/skills/vue/fe-perf/SKILL.md +163 -0
  35. package/assets/dev-guide/fe/skills/vue/fe-review/SKILL.md +136 -0
  36. package/assets/dev-guide/fe/sources/a11y-dx/INDEX.md +41 -0
  37. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-memory.md +150 -0
  38. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-performance.md +99 -0
  39. package/assets/dev-guide/fe/sources/a11y-dx/lighthouse-audits.md +146 -0
  40. package/assets/dev-guide/fe/sources/a11y-dx/react-devtools-profiler.md +128 -0
  41. package/assets/dev-guide/fe/sources/a11y-dx/wcag22-new-criteria.md +174 -0
  42. package/assets/dev-guide/fe/sources/perf/01-core-web-vitals.md +58 -0
  43. package/assets/dev-guide/fe/sources/perf/02-inp.md +83 -0
  44. package/assets/dev-guide/fe/sources/perf/03-lcp-cls.md +130 -0
  45. package/assets/dev-guide/fe/sources/perf/04-speculation-rules.md +148 -0
  46. package/assets/dev-guide/fe/sources/perf/05-view-transitions.md +153 -0
  47. package/assets/dev-guide/fe/sources/perf/06-nextjs-caching.md +188 -0
  48. package/assets/dev-guide/fe/sources/perf/07-server-components.md +181 -0
  49. package/assets/dev-guide/fe/sources/perf/08-ppr.md +133 -0
  50. package/assets/dev-guide/fe/sources/perf/09-nextjs-image.md +200 -0
  51. package/assets/dev-guide/fe/sources/perf/10-optimize-lcp.md +201 -0
  52. package/assets/dev-guide/fe/sources/perf/INDEX.md +88 -0
  53. package/assets/dev-guide/fe/sources/react/INDEX.md +41 -0
  54. package/assets/dev-guide/fe/sources/react/keeping-components-pure.md +135 -0
  55. package/assets/dev-guide/fe/sources/react/no-effect-patterns.md +183 -0
  56. package/assets/dev-guide/fe/sources/react/react-compiler.md +182 -0
  57. package/assets/dev-guide/fe/sources/react/server-components.md +194 -0
  58. package/assets/dev-guide/fe/sources/react/server-functions.md +192 -0
  59. package/assets/dev-guide/fe/sources/react/suspense.md +218 -0
  60. package/assets/dev-guide/fe/sources/react/use-action-state.md +123 -0
  61. package/assets/dev-guide/fe/sources/react/use-form-status.md +158 -0
  62. package/assets/dev-guide/fe/sources/react/use-hook.md +153 -0
  63. package/assets/dev-guide/fe/sources/react/use-optimistic.md +194 -0
  64. package/assets/dev-guide/fe/sources/toss-ff/INDEX.md +58 -0
  65. package/assets/dev-guide/fe/sources/toss-ff/cohesion-code-directory.md +79 -0
  66. package/assets/dev-guide/fe/sources/toss-ff/cohesion-form-fields.md +110 -0
  67. package/assets/dev-guide/fe/sources/toss-ff/cohesion-magic-number.md +47 -0
  68. package/assets/dev-guide/fe/sources/toss-ff/coupling-item-edit-modal.md +124 -0
  69. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-bottom-sheet.md +57 -0
  70. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-page-state.md +71 -0
  71. package/assets/dev-guide/fe/sources/toss-ff/overview-4-principles.md +77 -0
  72. package/assets/dev-guide/fe/sources/toss-ff/predictability-hidden-logic.md +59 -0
  73. package/assets/dev-guide/fe/sources/toss-ff/predictability-http.md +77 -0
  74. package/assets/dev-guide/fe/sources/toss-ff/predictability-use-user.md +110 -0
  75. package/assets/dev-guide/fe/sources/toss-ff/readability-comparison-order.md +52 -0
  76. package/assets/dev-guide/fe/sources/toss-ff/readability-condition-name.md +64 -0
  77. package/assets/dev-guide/fe/sources/toss-ff/readability-login-start-page.md +183 -0
  78. package/assets/dev-guide/fe/sources/toss-ff/readability-magic-number.md +53 -0
  79. package/assets/dev-guide/fe/sources/toss-ff/readability-submit-button.md +73 -0
  80. package/assets/dev-guide/fe/sources/toss-ff/readability-ternary-operator.md +38 -0
  81. package/assets/dev-guide/fe/sources/toss-ff/readability-use-page-state.md +77 -0
  82. package/assets/dev-guide/fe/sources/toss-ff/readability-user-policy.md +98 -0
  83. package/assets/dev-guide/fe/sources/vue/INDEX.md +17 -0
  84. package/assets/dev-guide/fe/sources/vue/composition-api.md +251 -0
  85. package/assets/dev-guide/fe/sources/vue/nuxt-data-fetching.md +232 -0
  86. package/assets/dev-guide/fe/sources/vue/pinia-state-management.md +134 -0
  87. package/assets/dev-guide/fe/sources/vue/reactivity-pitfalls.md +261 -0
  88. package/assets/dev-guide/fe/sources/vue/style-guide-priority-a.md +117 -0
  89. package/assets/dev-guide/fe/sources/vue/style-guide-priority-b.md +231 -0
  90. package/assets/dev-guide/fe/sources/vue/style-guide-priority-c.md +86 -0
  91. package/assets/dev-guide/fe/sources/vue/style-guide-priority-d.md +72 -0
  92. package/dist/cli.js +42 -0
  93. package/dist/core/dashboard-cli.d.ts +12 -0
  94. package/dist/core/dashboard-cli.js +226 -0
  95. package/dist/core/dev-guide-injector.d.ts +26 -0
  96. package/dist/core/dev-guide-injector.js +137 -0
  97. package/dist/core/init.js +53 -0
  98. package/dist/core/lifecycle-classifier.d.ts +23 -0
  99. package/dist/core/lifecycle-classifier.js +104 -0
  100. package/dist/core/observability-backfill.d.ts +31 -0
  101. package/dist/core/observability-backfill.js +178 -0
  102. package/dist/core/observability-store.d.ts +58 -0
  103. package/dist/core/observability-store.js +195 -0
  104. package/dist/core/session-store.js +4 -0
  105. package/dist/core/spawn.d.ts +17 -0
  106. package/dist/core/spawn.js +179 -2
  107. package/dist/core/statusline-cli.js +34 -1
  108. package/dist/engine/compound-extractor.js +39 -0
  109. package/dist/engine/compound-loop.js +6 -0
  110. package/dist/engine/compound-retire.d.ts +20 -0
  111. package/dist/engine/compound-retire.js +85 -0
  112. package/dist/hooks/context-guard.js +25 -1
  113. package/dist/hooks/post-tool-use.js +48 -0
  114. package/dist/hooks/solution-injector.js +93 -0
  115. package/dist/host/install-claude.d.ts +6 -2
  116. package/dist/host/install-claude.js +74 -2
  117. package/dist/host/install-codex.d.ts +4 -0
  118. package/dist/host/install-codex.js +71 -0
  119. package/dist/host/install-orchestrator.js +1 -0
  120. package/package.json +6 -6
  121. package/plugin.json +1 -1
  122. package/scripts/postinstall.js +134 -0
@@ -0,0 +1,53 @@
1
+ ---
2
+ title: 매직 넘버에 이름 붙이기
3
+ source: https://frontend-fundamentals.com/code-quality/code/examples/magic-number-readability.html
4
+ fetched: 2026-05-18
5
+ principle: readability
6
+ ---
7
+
8
+ # 매직 넘버에 이름 붙이기
9
+
10
+ **매직 넘버**(Magic Number)란 정확한 뜻을 밝히지 않고 소스 코드 안에 직접 숫자 값을 넣는 것을 말해요.
11
+
12
+ 예를 들어, 찾을 수 없음(Not Found)을 나타내는 HTTP 상태 코드로 `404` 값을 바로 사용하는 것이나,
13
+ 하루를 나타내는 `86400`초를 그대로 사용하는 것이 있어요.
14
+
15
+ ## 📝 코드 예시
16
+
17
+ 다음 코드는 좋아요 버튼을 눌렀을 때 좋아요 개수를 새로 내려받는 함수예요.
18
+
19
+ ```typescript
20
+ async function onLikeClick() {
21
+ await postLike(url);
22
+ await delay(300);
23
+ await refetchPostLike();
24
+ }
25
+ ```
26
+
27
+ ## 👃 코드 냄새 맡아보기
28
+
29
+ ### 가독성
30
+
31
+ 이 코드는 `delay` 함수에 전달된 `300`이라고 하는 값이 어떤 맥락으로 쓰였는지 알 수 없어요.
32
+
33
+ - 애니메이션이 완료될 때까지 기다리는 걸까?
34
+ - 좋아요 반영에 시간이 걸려서 기다리는 걸까?
35
+ - 테스트 코드였는데, 깜빡하고 안 지운 걸까?
36
+
37
+ ## ✏️ 개선해보기
38
+
39
+ 숫자 `300`의 맥락을 정확하게 표시하기 위해서 상수 `ANIMATION_DELAY_MS`로 선언할 수 있어요.
40
+
41
+ ```typescript
42
+ const ANIMATION_DELAY_MS = 300;
43
+
44
+ async function onLikeClick() {
45
+ await postLike(url);
46
+ await delay(ANIMATION_DELAY_MS);
47
+ await refetchPostLike();
48
+ }
49
+ ```
50
+
51
+ ## 🔍 더 알아보기
52
+
53
+ 매직 넘버는 응집도 관점에서도 살펴볼 수 있어요. [매직 넘버 없애서 응집도 높이기](./cohesion-magic-number.md) 문서도 참고해 보세요.
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: 같이 실행되지 않는 코드 분리하기
3
+ source: https://frontend-fundamentals.com/code-quality/code/examples/submit-button.html
4
+ fetched: 2026-05-18
5
+ principle: readability
6
+ ---
7
+
8
+ # 같이 실행되지 않는 코드 분리하기
9
+
10
+ 동시에 실행되지 않는 코드가 하나의 함수 또는 컴포넌트에 있으면, 동작을 한눈에 파악하기 어려워요.
11
+ 구현 부분에 많은 숫자의 분기가 들어가서, 어떤 역할을 하는지 이해하기 어렵기도 해요.
12
+
13
+ ## 📝 코드 예시
14
+
15
+ 다음 `<SubmitButton />` 컴포넌트는 사용자의 권한에 따라서 다르게 동작해요.
16
+
17
+ - 사용자의 권한이 보기 전용(`"viewer"`)이면, 초대 버튼은 비활성화되어 있고, 애니메이션도 재생하지 않아요.
18
+ - 사용자가 일반 사용자이면, 초대 버튼을 사용할 수 있고, 애니메이션도 재생해요.
19
+
20
+ ```tsx
21
+ function SubmitButton() {
22
+ const isViewer = useRole() === "viewer";
23
+
24
+ useEffect(() => {
25
+ if (isViewer) {
26
+ return;
27
+ }
28
+ showButtonAnimation();
29
+ }, [isViewer]);
30
+
31
+ return isViewer ? (
32
+ <TextButton disabled>Submit</TextButton>
33
+ ) : (
34
+ <Button type="submit">Submit</Button>
35
+ );
36
+ }
37
+ ```
38
+
39
+ ## 👃 코드 냄새 맡아보기
40
+
41
+ ### 가독성
42
+
43
+ `<SubmitButton />` 컴포넌트에서는 사용자가 가질 수 있는 2가지의 권한 상태를 하나의 컴포넌트 안에서 한 번에 처리하고 있어요.
44
+ 그래서 코드를 읽는 사람이 한 번에 고려해야 하는 맥락이 많아요.
45
+
46
+ 동시에 실행되지 않는 코드가 교차되어서 나타나서 코드를 이해할 때 부담을 줘요.
47
+
48
+ ## ✏️ 개선해보기
49
+
50
+ 다음 코드는 사용자가 보기 전용 권한을 가질 때와 일반 사용자일 때를 완전히 나누어서 관리하도록 하는 코드예요.
51
+
52
+ ```tsx
53
+ function SubmitButton() {
54
+ const isViewer = useRole() === "viewer";
55
+
56
+ return isViewer ? <ViewerSubmitButton /> : <AdminSubmitButton />;
57
+ }
58
+
59
+ function ViewerSubmitButton() {
60
+ return <TextButton disabled>Submit</TextButton>;
61
+ }
62
+
63
+ function AdminSubmitButton() {
64
+ useEffect(() => {
65
+ showButtonAnimation();
66
+ }, []);
67
+
68
+ return <Button type="submit">Submit</Button>;
69
+ }
70
+ ```
71
+
72
+ - `<SubmitButton />` 코드 곳곳에 있던 분기가 단 하나로 합쳐지면서, 분기가 줄어들었어요.
73
+ - `<ViewerSubmitButton />`과 `<AdminSubmitButton />` 에서는 하나의 분기만 관리하기 때문에, 코드를 읽는 사람이 한 번에 고려해야 할 맥락이 적어요.
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: 삼항 연산자 단순하게 하기
3
+ source: https://frontend-fundamentals.com/code-quality/code/examples/ternary-operator.html
4
+ fetched: 2026-05-18
5
+ principle: readability
6
+ ---
7
+
8
+ # 삼항 연산자 단순하게 하기
9
+
10
+ 삼항 연산자를 복잡하게 사용하면 조건의 구조가 명확하게 보이지 않아서 코드를 읽기 어려울 수 있어요.
11
+
12
+ ## 📝 코드 예시
13
+
14
+ 다음 코드는 `A조건`과 `B조건`에 따라서 `"BOTH"`, `"A"`, `"B"` 또는 `"NONE"` 중 하나를 `status`에 지정하는 코드예요.
15
+
16
+ ```typescript
17
+ const status =
18
+ A조건 && B조건 ? "BOTH" : A조건 || B조건 ? (A조건 ? "A" : "B") : "NONE";
19
+ ```
20
+
21
+ ## 👃 코드 냄새 맡아보기
22
+
23
+ ### 가독성
24
+
25
+ 이 코드는 여러 삼항 연산자가 중첩되어 사용되어서, 정확하게 어떤 조건으로 값이 계산되는지 한눈에 파악하기 어려워요.
26
+
27
+ ## ✏️ 개선해보기
28
+
29
+ 다음과 같이 조건을 `if` 문으로 풀어서 사용하면 보다 명확하고 간단하게 조건을 드러낼 수 있어요.
30
+
31
+ ```typescript
32
+ const status = (() => {
33
+ if (A조건 && B조건) return "BOTH";
34
+ if (A조건) return "A";
35
+ if (B조건) return "B";
36
+ return "NONE";
37
+ })();
38
+ ```
@@ -0,0 +1,77 @@
1
+ ---
2
+ title: 로직 종류에 따라 합쳐진 함수 쪼개기
3
+ source: https://frontend-fundamentals.com/code-quality/code/examples/use-page-state-readability.html
4
+ fetched: 2026-05-18
5
+ principle: readability
6
+ ---
7
+
8
+ # 로직 종류에 따라 합쳐진 함수 쪼개기
9
+
10
+ 쿼리 파라미터, 상태, API 호출과 같은 로직의 종류에 따라서 함수나 컴포넌트, Hook을 만들지 마세요. 한 번에 다루는 맥락의 종류가 많아져서 이해하기 힘들고 수정하기 어려운 코드가 돼요.
11
+
12
+ ## 📝 코드 예시
13
+
14
+ 다음 `usePageState()` Hook은 페이지 전체의 URL 쿼리 파라미터를 한 번에 관리해요.
15
+
16
+ ```typescript
17
+ export function usePageState() {
18
+ const [query, setQuery] = useQueryParams({
19
+ cardId: NumberParam,
20
+ statementId: NumberParam,
21
+ dateFrom: DateParam,
22
+ dateTo: DateParam,
23
+ statusList: ArrayParam
24
+ });
25
+
26
+ return useMemo(
27
+ () => ({
28
+ values: {
29
+ cardId: query.cardId ?? undefined,
30
+ statementId: query.statementId ?? undefined,
31
+ dateFrom: query.dateFrom == null ? defaultDateFrom : moment(query.dateFrom),
32
+ dateTo: query.dateTo == null ? defaultDateTo : moment(query.dateTo),
33
+ statusList: query.statusList as StatementStatusType[] | undefined
34
+ },
35
+ controls: {
36
+ setCardId: (cardId: number) => setQuery({ cardId }, "replaceIn"),
37
+ setStatementId: (statementId: number) => setQuery({ statementId }, "replaceIn"),
38
+ setDateFrom: (date?: Moment) => setQuery({ dateFrom: date?.toDate() }, "replaceIn"),
39
+ setDateTo: (date?: Moment) => setQuery({ dateTo: date?.toDate() }, "replaceIn"),
40
+ setStatusList: (statusList?: StatementStatusType[]) => setQuery({ statusList }, "replaceIn")
41
+ }
42
+ }),
43
+ [query, setQuery]
44
+ );
45
+ }
46
+ ```
47
+
48
+ ## 👃 코드 냄새 맡아보기
49
+
50
+ ### 가독성
51
+
52
+ 이 Hook이 가지고 있는 책임이 "페이지가 필요로 하는 모든 쿼리 파라미터를 관리하는 것" 임을 고려했을 때, 이 Hook이 담당할 책임이 무제한적으로 늘어날 가능성이 있어요.
53
+
54
+ 점점 Hook이 담당하고 있는 영역이 넓어지면서, 구현이 길어지고, 어떤 역할을 하는 Hook인지 파악하기 힘들어져요.
55
+
56
+ ### 성능
57
+
58
+ 이 Hook을 쓰는 컴포넌트는, 이 Hook이 관리하는 어떤 쿼리 파라미터가 수정되더라도 리렌더링이 발생해요.
59
+
60
+ ## ✏️ 개선해보기
61
+
62
+ 다음 코드와 같이 각각의 쿼리 파라미터별로 별도의 Hook을 작성할 수 있어요.
63
+
64
+ ```typescript
65
+ export function useCardIdQueryParam() {
66
+ const [cardId, _setCardId] = useQueryParam("cardId", NumberParam);
67
+
68
+ const setCardId = useCallback((cardId: number) => {
69
+ _setCardId({ cardId }, "replaceIn");
70
+ }, []);
71
+
72
+ return [cardId ?? undefined, setCardId] as const;
73
+ }
74
+ ```
75
+
76
+ Hook이 담당하는 책임을 분리했기 때문에, 기존 `usePageState()` Hook보다 명확한 이름을 가져요.
77
+ 또한 Hook을 수정했을 때 영향이 갈 범위를 좁혀서, 예상하지 못한 변경이 생기는 것을 막을 수 있어요.
@@ -0,0 +1,98 @@
1
+ ---
2
+ title: 시점 이동 줄이기
3
+ source: https://frontend-fundamentals.com/code-quality/code/examples/user-policy.html
4
+ fetched: 2026-05-18
5
+ principle: readability
6
+ ---
7
+
8
+ # 시점 이동 줄이기
9
+
10
+ 코드를 읽을 때 코드의 위아래를 왔다갔다 하면서 읽거나, 여러 파일이나 함수, 변수를 넘나들면서 읽는 것을 **시점 이동**이라고 해요.
11
+ 시점이 여러 번 이동할수록 코드를 파악하는 데에 시간이 더 걸리고, 맥락을 파악하는 데에 어려움이 있을 수 있어요.
12
+
13
+ ## 📝 코드 예시
14
+
15
+ 다음 코드에서는 사용자의 권한에 따라서 버튼을 다르게 보여줘요.
16
+
17
+ ```tsx
18
+ function Page() {
19
+ const user = useUser();
20
+ const policy = getPolicyByRole(user.role);
21
+
22
+ return (
23
+ <div>
24
+ <Button disabled={!policy.canInvite}>Invite</Button>
25
+ <Button disabled={!policy.canView}>View</Button>
26
+ </div>
27
+ );
28
+ }
29
+
30
+ function getPolicyByRole(role) {
31
+ const policy = POLICY_SET[role];
32
+
33
+ return {
34
+ canInvite: policy.includes("invite"),
35
+ canView: policy.includes("view")
36
+ };
37
+ }
38
+
39
+ const POLICY_SET = {
40
+ admin: ["invite", "view"],
41
+ viewer: ["view"]
42
+ };
43
+ ```
44
+
45
+ ## 👃 코드 냄새 맡아보기
46
+
47
+ ### 가독성
48
+
49
+ 이 코드에서 `Invite` 버튼이 비활성화된 이유를 이해하려고 한다면, `policy.canInvite` → `getPolicyByRole(user.role)` → `POLICY_SET` 순으로 코드를 위아래를 오가며 읽어야 해요.
50
+ 이 과정에서 3번의 시점 이동이 발생해서, 코드를 읽는 사람이 맥락을 유지해 가며 읽기 어려워졌어요.
51
+
52
+ ## ✏️ 개선해보기
53
+
54
+ ### A. 조건을 펼쳐서 그대로 드러내기
55
+
56
+ ```tsx
57
+ function Page() {
58
+ const user = useUser();
59
+
60
+ switch (user.role) {
61
+ case "admin":
62
+ return (
63
+ <div>
64
+ <Button disabled={false}>Invite</Button>
65
+ <Button disabled={false}>View</Button>
66
+ </div>
67
+ );
68
+ case "viewer":
69
+ return (
70
+ <div>
71
+ <Button disabled={true}>Invite</Button>
72
+ <Button disabled={false}>View</Button>
73
+ </div>
74
+ );
75
+ default:
76
+ return null;
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### B. 조건을 한눈에 볼 수 있는 객체로 만들기
82
+
83
+ ```tsx
84
+ function Page() {
85
+ const user = useUser();
86
+ const policy = {
87
+ admin: { canInvite: true, canView: true },
88
+ viewer: { canInvite: false, canView: true }
89
+ }[user.role];
90
+
91
+ return (
92
+ <div>
93
+ <Button disabled={!policy.canInvite}>Invite</Button>
94
+ <Button disabled={!policy.canView}>View</Button>
95
+ </div>
96
+ );
97
+ }
98
+ ```
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: Vue/Nuxt 문서 인덱스
3
+ fetched: 2026-05-18
4
+ ---
5
+
6
+ # Vue/Nuxt FE 가이드 소스 인덱스
7
+
8
+ | 파일 | 카테고리 | 소스 |
9
+ |------|----------|------|
10
+ | [style-guide-priority-a.md](./style-guide-priority-a.md) | style-guide | vuejs.org/style-guide/rules-essential |
11
+ | [style-guide-priority-b.md](./style-guide-priority-b.md) | style-guide | vuejs.org/style-guide/rules-strongly-recommended |
12
+ | [style-guide-priority-c.md](./style-guide-priority-c.md) | style-guide | vuejs.org/style-guide/rules-recommended |
13
+ | [style-guide-priority-d.md](./style-guide-priority-d.md) | style-guide | vuejs.org/style-guide/rules-use-with-caution |
14
+ | [composition-api.md](./composition-api.md) | composition-api | vuejs.org (composition-api-faq + sfc-script-setup) |
15
+ | [pinia-state-management.md](./pinia-state-management.md) | state | pinia.vuejs.org/core-concepts/ |
16
+ | [nuxt-data-fetching.md](./nuxt-data-fetching.md) | nuxt | nuxt.com/docs/getting-started/data-fetching |
17
+ | [reactivity-pitfalls.md](./reactivity-pitfalls.md) | reactivity | vuejs.org/guide/extras/reactivity-in-depth |
@@ -0,0 +1,251 @@
1
+ ---
2
+ title: Composition API + script setup 베스트 프랙티스
3
+ source: https://vuejs.org/guide/extras/composition-api-faq.html, https://vuejs.org/api/sfc-script-setup.html
4
+ fetched: 2026-05-18
5
+ category: composition-api
6
+ vue_version: 3
7
+ ---
8
+
9
+ # Composition API + `<script setup>` 베스트 프랙티스
10
+
11
+ ---
12
+
13
+ ## Composition API란?
14
+
15
+ 임포트한 함수를 사용해 Vue 컴포넌트를 작성하는 API 세트.
16
+
17
+ - **Reactivity API**: `ref()`, `reactive()` — 반응형 상태, computed, watcher 생성
18
+ - **Lifecycle Hooks**: `onMounted()`, `onUnmounted()` — 생명주기 훅
19
+ - **Dependency Injection**: `provide()`, `inject()` — 의존성 주입
20
+
21
+ ```vue
22
+ <script setup>
23
+ import { ref, onMounted } from 'vue'
24
+
25
+ const count = ref(0)
26
+
27
+ function increment() {
28
+ count.value++
29
+ }
30
+
31
+ onMounted(() => {
32
+ console.log(`Initial count is ${count.value}.`)
33
+ })
34
+ </script>
35
+
36
+ <template>
37
+ <button @click="increment">Count is: {{ count }}</button>
38
+ </template>
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Composition API를 사용하는 이유
44
+
45
+ ### 1. 로직 재사용 (Composables)
46
+ - 믹스인의 단점을 모두 해결
47
+ - VueUse 같은 생태계 활용 가능
48
+
49
+ ### 2. 유연한 코드 구성
50
+ - Options API: 관련 코드가 `data`, `methods`, `computed` 등에 분산됨
51
+ - Composition API: 관련 로직을 같은 위치에 모아서 관리
52
+
53
+ ### 3. 뛰어난 타입 추론
54
+ - 일반 변수/함수를 사용하므로 TypeScript 친화적
55
+ - Options API의 복잡한 타입 체조 불필요
56
+
57
+ ### 4. 더 작은 번들 크기
58
+ - `<script setup>`에서 템플릿이 같은 스코프의 함수로 컴파일됨
59
+ - 인스턴스 프록시 없이 직접 변수 접근 → 더 나은 minification
60
+
61
+ ---
62
+
63
+ ## React Hooks와의 차이점
64
+
65
+ | 항목 | React Hooks | Vue Composition API |
66
+ |------|-------------|---------------------|
67
+ | 호출 횟수 | 렌더링마다 재실행 | `setup()`이 한 번만 실행 |
68
+ | Stale closures | 의존성 배열 관리 필요 | 런타임 반응형으로 자동 추적 |
69
+ | 최적화 | `useMemo`, `useCallback` 필요 | 세밀한 반응성으로 자동 최적화 |
70
+ | 조건부 사용 | 불가 | 자유롭게 사용 가능 |
71
+
72
+ ---
73
+
74
+ ## `<script setup>` 완전 가이드
75
+
76
+ ### 기본 문법
77
+
78
+ ```vue
79
+ <script setup>
80
+ // 최상위 바인딩(변수, 함수, import)이 템플릿에 자동 노출
81
+ const msg = 'Hello!'
82
+
83
+ function log() {
84
+ console.log(msg)
85
+ }
86
+ </script>
87
+
88
+ <template>
89
+ <button @click="log">{{ msg }}</button>
90
+ </template>
91
+ ```
92
+
93
+ ### defineProps()
94
+
95
+ ```vue
96
+ <script setup>
97
+ // 런타임 선언
98
+ const props = defineProps({
99
+ foo: String,
100
+ bar: { type: Number, required: true }
101
+ })
102
+
103
+ // 타입 선언 (TypeScript)
104
+ const props = defineProps<{
105
+ foo: string
106
+ bar?: number
107
+ }>()
108
+ </script>
109
+ ```
110
+
111
+ **반응형 구조 분해 (Vue 3.5+):**
112
+ ```ts
113
+ const { foo } = defineProps(['foo'])
114
+ // foo 변경 시 watchEffect 자동 재실행
115
+ watchEffect(() => { console.log(foo) })
116
+ ```
117
+
118
+ **기본값 (Vue 3.5+ 권장):**
119
+ ```ts
120
+ const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()
121
+ ```
122
+
123
+ **기본값 (Vue 3.4 이하):**
124
+ ```ts
125
+ const props = withDefaults(defineProps<Props>(), {
126
+ msg: 'hello',
127
+ labels: () => ['one', 'two'] // 뮤터블 타입은 함수로 래핑
128
+ })
129
+ ```
130
+
131
+ ### defineEmits()
132
+
133
+ ```vue
134
+ <script setup>
135
+ const emit = defineEmits(['change', 'delete'])
136
+
137
+ // TypeScript
138
+ const emit = defineEmits<{
139
+ change: [id: number]
140
+ update: [value: string]
141
+ }>()
142
+ </script>
143
+ ```
144
+
145
+ ### defineModel() (Vue 3.4+)
146
+
147
+ ```js
148
+ // v-model 양방향 바인딩
149
+ const model = defineModel() // modelValue prop
150
+ model.value = 'hello'
151
+
152
+ // named model
153
+ const count = defineModel('count', { type: Number, default: 0 })
154
+ count.value++
155
+
156
+ // modifier 처리
157
+ const [modelValue, modelModifiers] = defineModel({
158
+ set(value) {
159
+ if (modelModifiers.trim) return value.trim()
160
+ return value
161
+ }
162
+ })
163
+ ```
164
+
165
+ ### defineExpose()
166
+
167
+ `<script setup>` 컴포넌트는 기본적으로 닫혀있다. 외부에서 접근할 속성은 명시적으로 노출한다.
168
+
169
+ ```vue
170
+ <script setup>
171
+ import { ref } from 'vue'
172
+
173
+ const a = 1
174
+ const b = ref(2)
175
+
176
+ defineExpose({ a, b }) // 부모의 template ref로 접근 가능
177
+ </script>
178
+ ```
179
+
180
+ ### defineOptions() (Vue 3.3+)
181
+
182
+ ```vue
183
+ <script setup>
184
+ defineOptions({
185
+ inheritAttrs: false,
186
+ name: 'MyComponent'
187
+ })
188
+ </script>
189
+ ```
190
+
191
+ ### defineSlots() (Vue 3.3+)
192
+
193
+ ```vue
194
+ <script setup lang="ts">
195
+ const slots = defineSlots<{
196
+ default(props: { msg: string }): any
197
+ }>()
198
+ </script>
199
+ ```
200
+
201
+ ### useSlots() & useAttrs()
202
+
203
+ ```vue
204
+ <script setup>
205
+ import { useSlots, useAttrs } from 'vue'
206
+
207
+ const slots = useSlots()
208
+ const attrs = useAttrs()
209
+ </script>
210
+ ```
211
+
212
+ ### Top-level await
213
+
214
+ ```vue
215
+ <script setup>
216
+ const post = await fetch('/api/post/1').then((r) => r.json())
217
+ </script>
218
+ ```
219
+
220
+ `async setup()`으로 컴파일됨. `<Suspense>`와 함께 사용해야 한다.
221
+
222
+ ### 제네릭 (TypeScript)
223
+
224
+ ```vue
225
+ <script setup lang="ts" generic="T extends string | number, U extends Item">
226
+ import type { Item } from './types'
227
+
228
+ defineProps<{
229
+ id: T
230
+ list: U[]
231
+ }>()
232
+ </script>
233
+ ```
234
+
235
+ ---
236
+
237
+ ## 일반 `<script>`와 혼용
238
+
239
+ ```vue
240
+ <script>
241
+ // 모듈 스코프에서 1회 실행
242
+ runSideEffectOnce()
243
+ export default { inheritAttrs: false }
244
+ </script>
245
+
246
+ <script setup>
247
+ // 각 인스턴스 setup() 스코프에서 실행
248
+ </script>
249
+ ```
250
+
251
+ 기존 Options API 코드베이스와의 점진적 통합에만 사용. 일반적으로는 권장하지 않는다.