ae39e45460
NOVAS SKILLS: - next-best-practices v0.1.0 (CLEAN) — Next.js App Router, RSC, caching, data - nextjs-patterns v1.0.0 (CLEAN) — Next.js 15: Server Actions, route handlers - vite v1.0.0 (CLEAN) — env vars, aliases, proxy, CJS compat - uncle-bob v1.0.0 (CLEAN) — Clean Code, SOLID, Clean Architecture - clean-code-review v1.0.0 (CLEAN) — naming, guard clauses, anti-patterns, refactoring - vue v1.0.0 (CLEAN) — Vue framework - vue-composition-api-best-practices v1.0.0 (CLEAN) — composables, Pinia, reactivity BIBLIOTECA INTELIGENTE libs/ (10 dominios, 11 arquivos): - typescript/ — TS safe + generics gotchas - react/ — Next.js App Router + Vite config - vue/ — Composition API + Pinia - linux/ — System diagnostic cheatsheet - database/ — PostgreSQL + MySQL patterns - browser/ — Chromium CLI + E2E testing - security/ — SAST audit (OWASP Top 10) - best-practices/ — Clean Code + SOLID + Clean Architecture - deploy/ — Docker multi-stack + OpenClaw ops - + INDEX.md como guia de navegacao .learnings/ — LRN-20260519-003 criado (biblioteca compartilhada)
3.0 KiB
3.0 KiB
Vue 3 — Composition API Best Practices
Extraído de skills:
vue+vue-composition-api-best-practices
<script setup> Padrão Ouro
<script setup lang="ts">
// ✅ Importações no topo — tree-shakeable
import { ref, computed, onMounted } from 'vue'
import { useUserStore } from '@/stores/user'
// ✅ Composables começam com `use`
const user = useUserStore()
const selectedId = ref<number | null>(null)
// ✅ Estado + getters no mesmo lugar
const filteredUsers = computed(() =>
user.list.filter(u => u.active)
)
// ✅ Lifecycle no fim
onMounted(() => user.fetchAll())
</script>
<template>
<button v-if="filteredUsers.length > 0" @click="selectedId = null">
Clear
</button>
</template>
Composables — useXxx Pattern
// composables/usePagination.ts
export function usePagination<T>(items: T[], pageSize = 20) {
const page = ref(1)
const totalPages = computed(() => Math.ceil(items.value.length / pageSize))
const paginated = computed(() =>
items.value.slice((page.value - 1) * pageSize, page.value * pageSize)
)
return { page, totalPages, paginated }
}
<script setup> Deep Dive
<!-- ✅ BOM — validade automática, minificado -->
<script setup lang="ts">
defineProps<{ msg: string }>()
const emit = defineEmits<{ (e: 'update', id: number): void }>()
</script>
<!-- ❌ RUIM — option API misturado -->
<script lang="ts">
export default {
data() { return { x: 1 } },
methods: { /* ... */ }
}
</script>
State Management — Pinia
// stores/counter.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() { count.value++ }
return { count, double, increment }
})
Reactividade — Principais Armadilhas
refvsreactive: userefpor padrão (tipagem simples);reactivepara objetos grandes- Perda de reatividade ao desestruturar — usar
toRefs()oustoreToRefs() watchvswatchEffect:watché mais controlado;watchEffecté automático mas menos previsívelv-ifvsv-show:v-ifremove do DOM;v-showtoggleadisplay
Type-Safe Vue
<script setup lang="ts">
// Props tipadas — importa de arquivo separado se for reutilizável
const props = defineProps<{
userId: number
items: Item[]
requiredValue: string
}>()
const emit = defineEmits<{
(e: 'delete', id: number): void
(e: 'save', data: FormData): void
}>()
</script>
Vue Router Traps
useRoute()para rota atual — reativa, usar em setupuseRouter()para navegação —router.push('/path')- Guards:
beforeEach,beforeResolve,afterEach— retornarfalsecancela <RouterView>com named views — múltiplas views por rota
Common Mistakes
keyemv-foré obrigatório —v-for="item in items" :key="item.id"- Ordem de event modifiers importa —
.prevent.stop≠.stop.prevent Teleportpara modais —<Teleport to="body">renderiza fora da árvore