best-practices.md 6.05 KB

最佳实践

本文档列出项目开发中的最佳实践。

组件开发

✅ 推荐做法

<script setup>
// 1. 使用 <script setup> 语法
import { ref, computed } from 'vue'

// 2. Props 应该有类型定义
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  }
})

// 3. 使用 emit 进行子到父通信
const emit = defineEmits(['update', 'delete'])

// 4. 使用 Composables 处理可复用逻辑
const { data, loading } = use fetchData()
</script>

❌ 避免做法

<script>
// ❌ 不要使用 Options API
export default {
  data() {
    return { }
  }
}
</script>

<script setup>
// ❌ 不要省略 Props 类型
const props = defineProps(['title'])

// ❌ 不要直接修改 props
props.title = 'new title'
</script>

样式开发

TailwindCSS vs Less

场景 使用 比例
布局(flex、grid、absolute) TailwindCSS 80%
间距(padding、margin、gap) TailwindCSS
排版(font-size、text-align) TailwindCSS
颜色(bg-、text-、border-*) TailwindCSS
响应式设计(sm:、md:、lg:) TailwindCSS
组件特定样式(需要 scoped) Less 20%
深度选择器(:deep() Less
动画和过渡 Less
伪元素(::before::after Less

✅ 优先使用 TailwindCSS

<template>
  <!-- ✅ 使用 TailwindCSS -->
  <view class="flex items-center gap-4 p-4 bg-white rounded-lg">
    <text class="text-lg font-semibold">标题</text>
  </view>
</template>

❌ 避免过度使用 Less

<style lang="less" scoped>
// ❌ 能用 TailwindCSS 的就不要用 Less
.custom-container {
  display: flex;
  align-items: center;
  padding: 16px;
}
</style>

API 集成

✅ 推荐做法

// 1. 始终检查 res.code === 1
const res = await fn(yourAPI(params))
if (res.code === 1) {
  // 成功处理
}

// 2. 使用 try/catch 进行错误处理
try {
  const res = await fn(yourAPI(params))
} catch (err) {
  console.error('API 错误:', err)
}

// 3. 请求期间显示加载状态
loading.value = true
await fn(yourAPI(params))
loading.value = false

// 4. 优雅地处理网络错误
try {
  await fn(yourAPI(params))
} catch (err) {
  if (is_network_error(err)) {
    Taro.showToast({ title: '网络错误,请重试', icon: 'none' })
  }
}

❌ 避免做法

// ❌ 不要只检查 res.code
if (res.code) { }

// ❌ 不要忽略错误处理
await fn(yourAPI(params))

// ❌ 不要不显示加载状态
await fn(yourAPI(params))  // 用户不知道发生了什么

性能优化

✅ 推荐做法

// 1. 使用页面懒加载(分包)
// 在 app.config.js 中配置分包
subPackages: [
  {
    root: 'pages/business',
    pages: ['/*']
  }
]

// 2. 使用 CDN 参数优化图片
const imageUrl = 'https://cdn.example.com/image.jpg?w=750&q=80'

// 3. 避免无分页的大数据集
const res = await fn(getListAPI({ page: 1, limit: 20 }))

// 4. 在 onUnmounted 中清理
onUnmounted(() => {
  timer && clearInterval(timer)
})

// 5. 使用 shallowRef + markRaw 处理组件对象
import { shallowRef, markRaw } from 'vue'

const menuItems = shallowRef([
  { icon: markRaw(IconFont), name: 'heart' }
])

❌ 避免做法

// ❌ 不要一次性加载所有数据
const allData = await fn(getAllDataAPI())

// ❌ 不要忘记清理定时器
const timer = setInterval(() => { }, 1000)
// 没有清理

// ❌ 不要深度代理组件对象
const menuItems = ref([
  { icon: IconFont, name: 'heart' }  // Vue 会深度代理
])

代码质量

✅ 推荐做法

// 1. 遵循 Vue 3 Composition API 模式
<script setup>
// ...
</script>

// 2. 使用描述性变量名
const isLoadingUserFavorites = ref(false)

// 3. 保持函数聚焦且简短(< 50 行)
const fetchUserData = async () => {
  // 单一职责
}

// 4. 所有函数必须有 JSDoc 注释
/**
 * 获取用户数据
 * @param {number} userId - 用户 ID
 * @returns {Promise<User>} 用户数据
 */
async function getUserData(userId) { }

// 5. 提交前运行 pnpm lint

❌ 避免做法

// ❌ 不要使用无意义的变量名
const a = ref(false)
const temp = ref(null)

// ❌ 不要写超长函数(> 50 行)
const doEverything = async () => {
  // 100+ 行代码
}

// ❌ 不要省略函数注释
function process(data) { }

代码复用

第 3 次出现原则

当相同代码模式出现 3 次时,必须抽取为 Composable 或组件。

✅ 推荐做法

// 抽取为 Composable
// src/composables/useUserData.js
export function useUserData() {
  const user = ref(null)
  const loading = ref(false)

  const fetchUser = async () => {
    loading.value = true
    // ...
  }

  return { user, loading, fetchUser }
}

// 在组件中使用
const { user, loading, fetchUser } = useUserData()

组件自包含原则

对于重复的UI结构,抽取为可复用组件:

<!-- MaterialCard.vue - 自包含业务逻辑 -->
<script setup>
const props = defineProps(['item'])
const emit = defineEmits(['view', 'collect'])

// 组件内部处理业务逻辑
const handleView = () => {
  emit('view', props.item.id)
}
</script>

<!-- 父组件只需要传递数据 -->
<MaterialCard :item="material" @view="handleView" />

安全性

✅ 推荐做法

// 1. 用户输入验证
const validateInput = (input) => {
  if (!input || input.length > 100) {
    return false
  }
  return true
}

// 2. XSS 防护(使用 v-html 时净化)
import DOMPurify from 'dompurify'
const sanitizedHtml = DOMPurify.sanitize(rawHtml)

// 3. 敏感数据不存储在 localStorage
// ❌ 不要存储
localStorage.setItem('password', password)

// ✅ 使用 Pinia(内存存储)
const authStore = useAuthStore()
authStore.setToken(token)

相关文档