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)