docs/developer/general-experience.md
本指南收录项目开发中的通用经验与最佳实践,快速解决常见问题,提升开发效率。
注意: 功能特定的经验已归档到
docs/archives/对应目录中。
// 统一 OpenAI 兼容格式
const config = {
baseURL: "https://api.provider.com/v1",
models: ["model-name"],
apiKey: import.meta.env.VITE_API_KEY // 必须使用 Vite 环境变量
};
核心原则:
try {
await apiCall();
} catch (error) {
console.error('[Service Error]', error); // 开发日志
throw new Error('操作失败,请稍后重试'); // 用户友好提示
}
describe("功能测试", () => {
beforeEach(() => {
testId = `test-${Date.now()}`; // 唯一标识避免冲突
});
// LLM参数测试:每个参数独立测试
it("should handle temperature parameter", async () => {
await modelManager.updateModel(configKey, {
llmParams: { temperature: 0.7 } // 只测试一个参数
});
});
});
要点:
问题:当一个Vue组件有多个根节点时,它无法自动继承父组件传递的非prop属性(如 class),并会产生警告。
方案:
<script setup> 中使用 defineOptions({ inheritAttrs: false }) 禁用默认的属性继承行为v-bind="$attrs" 手动绑定到你希望接收这些属性的特定根节点上示例:
<template>
<!-- $attrs 会将 class, id 等属性应用到此组件 -->
<OutputDisplayCore v-bind="$attrs" ... />
<OutputDisplayFullscreen ... />
</template>
<script setup>
defineOptions({
inheritAttrs: false,
});
</script>
问题:当全局状态变化需要通知多层级嵌套的组件时,事件传播可能中断,导致深层组件无法及时更新。
典型场景:
App.vue → ComponentA(直接引用)vs App.vue → ComponentB → ComponentC(间接引用)核心原因:
解决方案:
使用v-show替代v-if:确保组件实例始终存在,ref保持有效
<!-- ❌ 问题方案:组件会被销毁 -->
<Modal v-if="showModal">
<TemplateSelect ref="templateRef" />
</Modal>
<!-- ✅ 推荐方案:组件始终渲染 -->
<Modal v-show="showModal">
<TemplateSelect ref="templateRef" />
</Modal>
建立完整事件传播链:从事件源到所有消费组件
// 父组件:建立事件传播
const handleGlobalStateChange = (newState) => {
// 刷新直接子组件
if (directChildRef.value?.refresh) {
directChildRef.value.refresh()
}
// 刷新深层组件(通过中间组件的暴露方法)
if (intermediateRef.value?.refreshDeepChild) {
intermediateRef.value.refreshDeepChild()
}
}
// 中间组件:暴露深层组件的刷新方法
const deepChildRef = ref()
const refreshDeepChild = () => {
if (deepChildRef.value?.refresh) {
deepChildRef.value.refresh()
}
}
defineExpose({
refreshDeepChild
})
统一刷新接口:所有相关组件都暴露相同的刷新方法
// 每个需要响应全局状态变化的组件都实现refresh方法
const refresh = () => {
// 重新加载数据或更新状态
}
defineExpose({
refresh
})
最佳实践:
refresh()方法)适用场景:
min-h-0 是否添加display: flexoverflow 属性overflow-y: auto深层组件未更新:
v-if 导致组件被销毁Modal内组件状态异常:
v-show 而非 v-if组件ref调用失败:
nextTick)VITE_ 前缀)// package.json
{
"scripts": {
"version": "pnpm run version:sync && git add -A"
}
}
关键:使用 version 钩子而非 postversion,确保同步文件包含在版本提交中。
// ❌ 错误:自动设置默认值
if (!config.temperature) config.temperature = 0.7;
// ✅ 正确:只使用用户配置的参数
const requestConfig = {
model: modelConfig.defaultModel,
messages: formattedMessages,
...userLlmParams // 只传递用户明确配置的参数
};
// 白名单验证 + 类型检查
for (const [key, value] of Object.entries(importData)) {
if (!ALLOWED_KEYS.includes(key)) {
console.warn(`跳过未知配置: ${key}`);
continue;
}
if (typeof value !== 'string') {
console.warn(`跳过无效类型 ${key}: ${typeof value}`);
continue;
}
await storage.setItem(key, value);
}
问题:[intlify] Not found 'key' in 'locale' messages 错误,通常由中英文语言包键值不同步引起。
方案:创建自动化脚本比较两个语言文件,列出差异。
遇到新问题或找到更好解决方案时,应及时更新此文档:
记住:好的经验文档应该能让团队成员快速找到解决方案,而不是重新踩坑。
问题场景:多个组件使用同一个composable时,如果每次调用都创建新实例,会导致状态不同步。
错误实现:
export function useUpdater() {
const state = reactive({...}) // 每次调用都创建新实例
return { state, ... }
}
正确实现:
let globalUpdaterInstance: any = null
export function useUpdater() {
if (globalUpdaterInstance) {
return globalUpdaterInstance // 返回已有实例
}
const state = reactive({...})
const instance = { state, ... }
globalUpdaterInstance = instance // 缓存实例
return instance
}
判断标准:如果多个组件需要访问同一份状态,就应该使用单例模式。
常见需要单例的场景:
渐进式重构原则:
核心原则:
避免抽象泄漏:
MCP工具应用:
process.env-r 参数: 是在模块系统初始化前预加载脚本的最可靠方法相关归档:
问题:Node.js 环境变量必须在模块导入前加载,否则模块初始化时读取不到
# ✅ 正确:使用 -r 参数预加载
node -r ./preload-env.js dist/index.js
# ❌ 错误:模块导入后加载环境变量
node dist/index.js # 此时环境变量可能未加载
解决方案:
问题:构建工具(如 tsup)执行模块级代码时会导致服务器意外启动
// ❌ 错误:入口文件直接执行
import { startServer } from './server'
startServer() // 构建时会执行
// ✅ 正确:分离导出和执行
export { startServer } from './server'
// 使用单独的启动文件执行主逻辑
问题:Windows 下 concurrently 等进程管理工具信号处理有问题
// ❌ 避免:复杂的进程管理
"scripts": {
"dev": "concurrently \"npm run build:watch\" \"npm run start\""
}
// ✅ 推荐:简单的分离脚本
"scripts": {
"build": "tsup",
"start": "node dist/index.js",
"dev": "npm run build && npm run start"
}