Files
pulse-memory/skills/vue-composition-api-best-practices/reference/sfc-code-organization.md
T
Pulse ae39e45460 feat: biblioteca inteligente libs/ + 5 novas skills (20 skills total)
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)
2026-05-19 21:03:25 -03:00

7.7 KiB
Raw Blame History

title, impact, impactDescription, type, tags
title impact impactDescription type tags
SFC Code Organization Order HIGH 代码组织混乱会导致维护困难、难以理解组件结构,以及团队成员之间代码风格不一致 best-practice
vue3
composition-api
script-setup
code-organization
maintainability

SFC 代码组织顺序

影响等级:高 - 良好组织的 SFC(单文件组件)对可维护性和团队协作至关重要。遵循一致的顺序使代码可预测且易于导航。

任务清单

  • 遵循标准的 SFC 代码组织顺序
  • 使用 useXxx 函数按功能分组相关代码
  • 将 Vue 公共项(options、props、emits 等)放在顶部
  • 将功能实现放在底部,IDE 中默认折叠
  • 使用清晰的区块注释进行分隔

问题所在

<script setup> 带来了自由和灵活性,但如果没有约定,每个 SFC 文件可能看起来完全不同,使得维护和重构变得困难。

BAD - 组织混乱的代码:

<script setup lang="ts">
import { ref, computed, watch, onMounted, provide, inject } from 'vue'

// 分散的状态
const count = ref(0)
const user = ref(null)
const loading = ref(false)

// 函数随意穿插在中间
function fetchUser() {
  loading.value = true
  // ...
}

// 计算属性混在其中
const doubleCount = computed(() => count.value * 2)

// 又一个状态
const theme = ref('dark')

// props 定义在很后面
const props = defineProps<{
  id: string
}>()

// watch 散落在某处
watch(() => props.id, fetchUser)

// 生命周期钩子
onMounted(() => {
  fetchUser()
})

// 更多函数...
function increment() {
  count.value++
}

// emits 放在底部
const emit = defineEmits(['update'])

// provide 分散在各处
provide('theme', theme)
</script>

GOOD - 组织良好的代码:

<script setup lang="ts">
import { ref, computed, watch, onMounted, provide, inject } from 'vue'

// 组件名
defineOptions({
  name: 'UserComponent'
})

// props
const props = defineProps<{
  id: string
}>()

// model
const model = defineModel<string>()

// inject
const globalConfig = inject('config')

// emits
const emit = defineEmits<{
  update: [value: string]
}>()

// store
const appStore = useAppStore()

// 外部 hooks
const { user, loading, fetchUser } = useUser()
const { theme, toggleTheme } = useTheme()

// 功能声明
const { count, doubleCount, increment } = useCounter()

// provide
provide('theme', theme)

// expose
defineExpose({
  increment,
  fetchUser
})

// ============ 功能实现 ============

// 用户管理功能
function useUser() {
  const user = ref<User | null>(null)
  const loading = ref(false)

  const fetchUser = async () => {
    loading.value = true
    try {
      user.value = await fetchUserData(props.id)
    } finally {
      loading.value = false
    }
  }

  watch(() => props.id, fetchUser, { immediate: true })
  onMounted(fetchUser)

  return {
    user,
    loading,
    fetchUser
  }
}

// 主题功能
function useTheme() {
  const theme = ref<'light' | 'dark'>('dark')

  const toggleTheme = () => {
    theme.value = theme.value === 'light' ? 'dark' : 'light'
  }

  return {
    theme,
    toggleTheme
  }
}

// 计数器功能
function useCounter() {
  const count = ref(0)

  const doubleCount = computed(() => count.value * 2)

  const increment = () => {
    count.value++
    emit('update', count.value.toString())
  }

  return {
    count,
    doubleCount,
    increment
  }
}
</script>

标准组织顺序

顺序 区块 是否必须 描述
1 defineOptions 推荐 组件名称(DevTools、keep-alive、递归组件)
2 defineProps 可选 带类型声明的组件 props
3 defineModel 可选 双向绑定 modelVue 3.4+
4 inject 可选 注入的依赖
5 defineEmits 可选 带类型声明的组件事件
6 Store 声明 可选 Pinia store 实例(useXxxStore()
7 外部 hooks 可选 导入的组合式函数
8 功能声明 可选 const { ... } = useFeature()
9 provide 可选 提供的依赖
10 defineExpose 可选 暴露的公共 API
11 功能实现 按需 function useFeature() {}

区块注释风格

使用清晰、简洁的区块注释。两种常见风格:

风格一:简洁中文注释(推荐中文团队使用)

// 组件名
defineOptions({ name: 'Layout' })

// props
const props = defineProps<{ id: string }>()

// emits
const emit = defineEmits<{ update: [value: string] }>()

// store
const appStore = useAppStore()

// 外部 hooks
const { t } = useI18n()

// 功能声明
const { imageBgUrl, videoBgUrl } = useBackground()

// ============ 功能实现 ============

function useBackground() { /* ... */ }

风格二:分隔线风格(适用于大型组件)

// ============ Vue 公共项 ============

defineOptions({ name: 'UserComponent' })

// ============ Store ============

const appStore = useAppStore()

// ============ 外部 Hooks ============

const { t } = useI18n()

// ============ 功能声明 ============

const { search, results } = useSearch()

// ============ 功能实现 ============

function useSearch() { /* ... */ }

收益

  1. 结构可预测:团队成员知道在哪里找到特定代码
  2. 快速概览:顶部区域让组件接口一目了然
  3. IDE 导航:点击声明中的函数名即可跳转到实现
  4. 默认折叠:功能实现保持折叠状态,减少视觉干扰
  5. 依赖清晰:一眼看清每个功能返回和消费了什么
  6. Store 聚合:所有 store 实例集中声明,便于识别

适用场景

  • 始终遵循:对所有 <script setup> 组件使用此组织方式
  • 小型组件:即使区块较少,仍应遵循此顺序
  • 大型组件:功能较多时,对保持可读性至关重要
  • 团队项目:通过代码审查和 lint 规则强制保持一致性

真实案例

<script setup lang="ts">
import { useAppStore } from '@/store/modules/app'

// 组件名
defineOptions({
  name: 'MiSearch'
})

// store
const appStore = useAppStore()
const businessStore = useBusinessStore()

// 多语言
const { t } = useI18n()

// 简单前缀
const { getPrefixCls } = useDesign()

// useEngine
const { engineInfo, nextEngine } = useEngine()

// useSearchInput
const {
  searchContent,
  handleChange,
  handleSearch,
  isComposing,
  searchHistoryRef,
  searchSuggestionRef,
  operaHistoryOrSuggestion,
  searchInputRef
} = useSearchInput()

// 输入框
function useSearchInput() {
  const searchContent = ref('')
  const isComposing = ref(false)

  const handleSearch = (val) => {
    if (isComposing.value) return
    const useContent = encodeURIComponent(val)
    if (!useContent) return
    businessStore.updateHistoryList(val)
    window.open(`${engineInfo.value.url}${useContent}`, appStore.openType)
  }

  // ... 更多逻辑

  return {
    searchContent,
    isComposing,
    handleSearch,
    handleChange,
    searchHistoryRef,
    searchSuggestionRef,
    operaHistoryOrSuggestion,
    searchInputRef
  }
}
</script>

参考