Vue 实战技巧与最佳实践
中等 🟡Vue 生态
4 个标签
预计阅读时间:21 分钟
Vue实战技巧最佳实践代码规范
Vue 实战技巧与最佳实践
本文总结了 Vue 开发中的实用技巧、常见问题的解决方案和最佳实践,帮助开发者写出更高质量、更易维护的 Vue 代码。
一、组件设计技巧
1. 智能组件与展示组件分离
展示组件(Dumb Component):
只负责 UI 渲染,不关心数据来源。
vueCode
<!-- UserCard.vue -->
<script setup>
defineProps({
user: {
type: Object,
required: true
},
loading: {
type: Boolean,
default: false
}
});
defineEmits(['edit', 'delete']);
</script>
<template>
<div class="user-card">
<div v-if="loading">加载中...</div>
<div v-else>
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button @click="$emit('edit', user)">编辑</button>
<button @click="$emit('delete', user.id)">删除</button>
</div>
</div>
</template>智能组件(Smart Component):
负责数据获取和业务逻辑。
vueCode
<!-- UserCardContainer.vue -->
<script setup>
import { ref, onMounted } from 'vue';
import UserCard from './UserCard.vue';
import { fetchUser } from '@/api/user';
const props = defineProps({
userId: Number
});
const user = ref(null);
const loading = ref(false);
const loadUser = async () => {
loading.value = true;
try {
user.value = await fetchUser(props.userId);
} finally {
loading.value = false;
}
};
const handleEdit = (user) => {
// 编辑逻辑
};
const handleDelete = (userId) => {
// 删除逻辑
};
onMounted(() => {
loadUser();
});
</script>
<template>
<UserCard
:user="user"
:loading="loading"
@edit="handleEdit"
@delete="handleDelete"
/>
</template>2. 使用 v-bind="$attrs" 透传属性
vueCode
<!-- BaseInput.vue -->
<template>
<div class="input-wrapper">
<label v-if="label">{{ label }}</label>
<input v-bind="$attrs" />
<span v-if="error" class="error">{{ error }}</span>
</div>
</template>
<script setup>
defineProps({
label: String,
error: String
});
</script>
<!-- 使用 -->
<BaseInput
v-model="username"
label="用户名"
placeholder="请输入用户名"
:disabled="true"
/>3. 使用 defineModel 简化双向绑定(Vue 3.3+)
vueCode
<!-- CustomInput.vue -->
<script setup>
const modelValue = defineModel({
type: String,
required: true
});
const onInput = (event) => {
modelValue.value = event.target.value;
};
</script>
<template>
<input :value="modelValue" @input="onInput" />
</template>
<!-- 使用 -->
<template>
<CustomInput v-model="searchQuery" />
</template>二、性能优化技巧
1. 使用 v-memo 缓存模板(Vue 3.2+)
vueCode
<script setup>
import { ref } from 'vue';
const items = ref([]);
const selectedItem = ref(null);
</script>
<template>
<div>
<!-- 只有当 items 变化时才重新渲染 -->
<div v-for="item in items" :key="item.id" v-memo="[items.length]">
{{ item.name }}
</div>
<!-- 只有当 selectedItem 变化时才重新渲染 -->
<div v-memo="[selectedItem?.id]">
<p>选中:{{ selectedItem?.name }}</p>
</div>
</div>
</template>2. 使用 shallowRef 优化大对象
vueCode
<script setup>
import { ref, shallowRef, triggerRef } from 'vue';
// 深层响应式(适合小对象)
const deepData = ref({ nested: { value: 1 } });
// 浅层响应式(适合大对象)
const largeList = shallowRef([]);
// 修改浅层响应式对象时,需要手动触发
const updateList = () => {
largeList.value.push({ id: 1 });
triggerRef(largeList); // 手动触发更新
};
</script>3. 使用 markRaw 避免不必要的响应式转换
vueCode
<script setup>
import { ref, markRaw } from 'vue';
import Chart from 'chart.js';
const chart = ref(null);
const initChart = () => {
// 使用 markRaw 避免将 Chart 实例转换为响应式
chart.value = markRaw(new Chart(ctx, config));
};
</script>4. 使用 Object.freeze 冻结静态数据
vueCode
<script setup>
import { ref } from 'vue';
// 冻结静态配置
const CONFIG = Object.freeze({
API_URL: 'https://api.example.com',
TIMEOUT: 5000
});
// 冻结大型静态列表
const staticOptions = Object.freeze([
{ value: 1, label: 'Option 1' },
{ value: 2, label: 'Option 2' },
// ... 大量数据
]);
</script>三、表单处理技巧
1. 使用自定义 Composable 处理表单
javascriptCode
// composables/useForm.js
import { ref, reactive } from 'vue';
export function useForm(initialValues, validateFn) {
const values = reactive({ ...initialValues });
const errors = ref({});
const isSubmitting = ref(false);
function validate() {
errors.value = validateFn(values) || {};
return Object.keys(errors.value).length === 0;
}
async function submit(submitFn) {
if (!validate()) return;
isSubmitting.value = true;
try {
await submitFn(values);
} catch (error) {
errors.value.submit = error.message;
} finally {
isSubmitting.value = false;
}
}
function reset() {
Object.assign(values, initialValues);
errors.value = {};
}
return {
values,
errors,
isSubmitting,
validate,
submit,
reset
};
}使用示例:
vueCode
<script setup>
import { useForm } from '@/composables/useForm';
const { values, errors, submit, isSubmitting } = useForm(
{ username: '', email: '', password: '' },
(values) => {
const errors = {};
if (!values.username) errors.username = '用户名必填';
if (!values.email) errors.email = '邮箱必填';
if (!values.password) errors.password = '密码必填';
return errors;
}
);
const handleSubmit = async () => {
await submit(async (data) => {
// 提交逻辑
await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(data)
});
});
};
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="values.username" />
<span v-if="errors.username">{{ errors.username }}</span>
<input v-model="values.email" type="email" />
<span v-if="errors.email">{{ errors.email }}</span>
<input v-model="values.password" type="password" />
<span v-if="errors.password">{{ errors.password }}</span>
<button type="submit" :disabled="isSubmitting">
{{ isSubmitting ? '提交中...' : '注册' }}
</button>
</form>
</template>2. 使用 v-model 处理多个输入
vueCode
<script setup>
import { ref } from 'vue';
const formData = ref({
username: '',
email: '',
phone: ''
});
</script>
<template>
<form>
<input v-model="formData.username" placeholder="用户名" />
<input v-model="formData.email" placeholder="邮箱" />
<input v-model="formData.phone" placeholder="手机" />
</form>
</template>四、API 请求处理技巧
1. 使用自定义 Composable 处理请求
javascriptCode
// composables/useFetch.js
import { ref } from 'vue';
export function useFetch(url, options = {}) {
const data = ref(null);
const error = ref(null);
const loading = ref(false);
async function execute(body = null) {
loading.value = true;
error.value = null;
try {
const response = await fetch(url, {
...options,
body: body ? JSON.stringify(body) : null,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
throw new Error(response.statusText);
}
data.value = await response.json();
return data.value;
} catch (err) {
error.value = err;
throw err;
} finally {
loading.value = false;
}
}
return {
data,
error,
loading,
execute
};
}使用示例:
vueCode
<script setup>
import { onMounted } from 'vue';
import { useFetch } from '@/composables/useFetch';
const { data: users, loading, execute } = useFetch('/api/users');
onMounted(() => {
execute();
});
</script>
<template>
<div v-if="loading">加载中...</div>
<div v-else-if="users">
<div v-for="user in users" :key="user.id">
{{ user.name }}
</div>
</div>
</template>2. 使用 Axios 拦截器
javascriptCode
// utils/request.js
import axios from 'axios';
const request = axios.create({
baseURL: '/api',
timeout: 5000
});
// 请求拦截器
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器
request.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response?.status === 401) {
// 处理未授权
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default request;五、代码组织技巧
1. 使用 Composables 组织可复用逻辑
javascriptCode
// composables/useDarkMode.js
import { ref, onMounted, onUnmounted } from 'vue';
export function useDarkMode() {
const isDark = ref(false);
const updateTheme = () => {
isDark.value = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', isDark.value);
};
onMounted(() => {
updateTheme();
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', updateTheme);
});
onUnmounted(() => {
window.matchMedia('(prefers-color-scheme: dark)')
.removeEventListener('change', updateTheme);
});
const toggle = () => {
isDark.value = !isDark.value;
document.documentElement.classList.toggle('dark', isDark.value);
};
return { isDark, toggle };
}2. 使用脚本片段组织大型组件
vueCode
<script setup>
import { ref, computed } from 'vue';
// === 类型定义 ===
// const types...
// === Props ===
const props = defineProps({
userId: Number
});
// === Emits ===
const emit = defineEmits(['update', 'delete']);
// === 状态 ===
const user = ref(null);
const loading = ref(false);
// === 计算属性 ===
const userName = computed(() => user.value?.name || '未知用户');
// === 方法 ===
const loadUser = async () => {
loading.value = true;
// 加载逻辑
loading.value = false;
};
const handleEdit = () => {
emit('update', user.value);
};
// === 生命周期 ===
onMounted(() => {
loadUser();
});
</script>六、调试技巧
1. 使用 Vue Devtools
•检查组件树和状态
•追踪事件和生命周期
•性能分析
2. 添加调试日志
vueCode
<script setup>
import { ref, watch } from 'vue';
const count = ref(0);
// 调试 watch
watch(
count,
(newVal, oldVal) => {
console.log('[DEBUG] count changed:', { oldVal, newVal });
},
{ immediate: true }
);
</script>3. 使用错误边界
vueCode
<script setup>
import { onErrorCaptured } from 'vue';
onErrorCaptured((error, instance, info) => {
console.error('捕获到错误:', error);
console.error('组件实例:', instance);
console.error('错误信息:', info);
// 返回 false 阻止错误继续向上传播
return false;
});
</script>七、最佳实践总结
1.组件设计:智能组件与展示组件分离
2.性能优化:合理使用 shallowRef、markRaw、v-memo
3.表单处理:使用 Composables 封装表单逻辑
4.API 请求:统一处理请求和错误
5.代码组织:使用注释分隔不同代码块
6.类型安全:使用 TypeScript 定义类型
7.代码复用:使用 Composables 提取可复用逻辑
8.错误处理:添加错误边界和全局错误处理