| name | refactor-composable-pattern |
| description | Refactoring pattern untuk menghindari import tidak perlu, menggunakan composable untuk logic, centralized type management di shared/types, dan form component pattern yang mendukung edit dan create dengan sama composable. |
| metadata | {"author":"Project Team","version":"1.0","created":"2026-03-13","category":"refactoring","tags":["composable","component-structure","type-management","form-pattern"]} |
Refactoring dengan Composable Pattern
Panduan lengkap untuk refactoring komponen dan logic dengan pola berbasis composable, centralized type management, dan form component pattern yang efisien.
🎯 Tujuan Refactoring
- Menghindari Import Tidak Perlu - Komponenten hanya import apa yang dibutuhkan
- Separation of Concerns - Setiap komponen bertanggung jawab untuk fungsinya, logic ada di composable
- Reusable Logic - Composable dapat digunakan di multiple komponen
- Centralized Types - Semua interface/type di
shared/types/** agar bisa diakses server dan app
- Form Pattern Unified - Form untuk edit dan create menggunakan 1 composable, props determines action
📋 Core Patterns
✅ Quick Start: Refactoring Steps
Step 1: Map Types ke Shared/Types
shared/types/
├── blog.ts
├── category.ts
├── user.ts
├── media.ts
├── common.ts
└── index.ts
Step 2: Create Composables untuk Business Logic
app/composables/
├── useBlogForm.ts
├── useBlogList.ts
├── useBlogDetail.ts
└── useValidation.ts
Step 3: Refactor Components sebagai Presentational
app/components/
├── Blog/
│ ├── BlogForm.vue
│ ├── BlogList.vue
│ └── BlogCard.vue
└── Common/
└── FormInput.vue
📐 Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ Component (*.vue) │
│ - Minimal logic, focused on rendering │
│ - Pass props/events to composable │
│ - Handle UI state (modal open/close, focus) │
└─────────────────┬───────────────────────────────────────┘
│ useComposable()
▼
┌─────────────────────────────────────────────────────────┐
│ Composable (useXxx.ts) │
│ - Business logic (fetch, validate, transform) │
│ - State management (ref, reactive) │
│ - Methods (create, update, delete, search) │
│ - Smart action detection (create vs update) │
└─────────────────┬───────────────────────────────────────┘
│ useAPI / useFetch
▼
┌─────────────────────────────────────────────────────────┐
│ API Routes (server/api) │
│ - Request validation │
│ - Business logic │
│ - Database operations │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Shared Types (shared/types) │
│ - Interfaces shared between server & client │
│ - Barrel exports for easy import │
└─────────────────────────────────────────────────────────┘
🔄 Form Component Pattern (Smart Edit/Create)
Core Concept
Form detect apakah edit atau create berdasarkan props:
<!-- Penggunaan: Props "blog" menentukan apakah edit/create -->
<BlogForm />
<!-- No props = Create mode -->
<BlogForm :blog="item" />
<!-- With props = Edit mode -->
Composable useBlogForm secara otomatis menentukan action:
export function useBlogForm(initialBlog?: BlogData) {
const isEdit = computed(() => !!initialBlog?.id);
const isCreate = computed(() => !isEdit.value);
const submitForm = async (data) => {
if (isEdit.value) {
return updateBlog(initialBlog.id, data);
} else {
return createBlog(data);
}
};
}
⚠️ Common Mistakes
❌ DON'T: Import component dari component
<!-- ❌ JANGAN -->
<script setup lang="ts">
import BlogList from "./BlogList.vue";
import BlogForm from "./BlogForm.vue";
</script>
Alasan: Terjadi circular dependency, hard to test, tidak reusable
✅ DO: Use parent layout atau page untuk compose
<!-- ✅ LAKUKAN -->
<!-- pages/admin/blog/index.vue -->
<script setup lang="ts">
import BlogList from "@/components/Blog/BlogList.vue";
import BlogForm from "@/components/Blog/BlogForm.vue";
</script>
❌ DON'T: Put form logic di component
<!-- ❌ JANGAN -->
<script setup lang="ts">
const blog = ref<BlogData | null>(null);
const submitForm = async (data) => {
// 200 lines of form logic di sini
// tidak bisa di-reuse
};
</script>
✅ DO: Move ke composable
export function useBlogForm(initialBlog?: BlogData) {
const blog = ref<BlogData | null>(initialBlog || null);
const submitForm = async (data) => {
};
return { blog, submitForm };
}
<!-- Components/Blog/BlogForm.vue -->
<script setup lang="ts">
const { blog, submitForm } = useBlogForm(props.blog);
</script>
🚀 Next Steps
- Read: component-structure.md - Understand component layout
- Read: type-management.md - Centralize your types
- Read: composable-patterns.md - Build composables
- Read: form-pattern.md - Implement smart form
- Read: import-best-practices.md - Avoid import issues
📚 File Structure After Refactoring
app/
├── components/
│ ├── Blog/
│ │ ├── BlogForm.vue # Smart form (edit/create)
│ │ ├── BlogList.vue # List presentation
│ │ ├── BlogCard.vue # Item card
│ │ └── BlogDetail.vue # Single item view
│ └── Common/
│ ├── FormInput.vue # Reusable input
│ ├── FormSelect.vue
│ └── FormTextarea.vue
│
├── composables/
│ ├── useBlogForm.ts # Form logic (create + update)
│ ├── useBlogList.ts # List logic (fetch + filter)
│ ├── useBlogDetail.ts # Detail logic
│ ├── useValidation.ts # Validation helpers
│ └── useCategoryList.ts
│
├── pages/
│ └── admin/
│ └── blog/
│ ├── index.vue # List page
│ ├── [id].vue # Detail page
│ └── new.vue # Create page (OR can use [id] with conditional)
│
└── types/
└── (moved to shared/types)
shared/
├── types/
│ ├── blog.ts
│ ├── category.ts
│ ├── media.ts
│ ├── user.ts
│ ├── common.ts
│ └── index.ts
│
└── utils/
├── file.ts
└── (other utilities)
server/
├── api/
│ ├── admin/
│ │ └── blog/
│ │ ├── index.get.ts # List
│ │ ├── index.post.ts # Create
│ │ └── [id].patch.ts # Update
│ └── (other routes)
│
├── utils/
│ ├── validation.ts
│ ├── auth.ts
│ └── (other utilities)
│
└── db/
└── schema.ts
🎓 Learning Resources