# React 到 Vue 完整迁移方案 ## 📋 文档说明 本文档提供从 React 技术栈完整迁移到 Vue 3 技术栈的详细方案,包括技术选型、迁移步骤、代码转换示例和测试策略。 --- ## 🎯 迁移目标 - **完全移除 React 依赖** - **使用 Vue 3 Composition API** - **保持所有现有功能** - **保持 Tailwind CSS 样式** - **保持 TypeScript 类型系统** --- ## 🛠️ 技术选型 ### 核心框架 - **Vue**: `^3.4.0` (使用 Composition API) - **TypeScript**: `^5.2.2` (保持不变) - **Vite**: `^5.0.8` (保持不变,更换插件) ### 状态管理 - **Pinia**: `^2.1.0` (推荐,替代 Context API) ### 路由(可选) - **Vue Router**: `^4.2.0` (如果需要路由功能) ### UI 库 - **图标库**: `lucide-vue-next`: `^0.344.0` - **图表库**: `echarts-for-vue`: `^1.2.0` 或 `vue-echarts`: `^6.6.0` ### 样式 - **Tailwind CSS**: `^3.3.6` (保持不变) --- ## 📦 依赖包替换清单 ### 需要移除的依赖 ```json { "react": "^18.2.0", "react-dom": "^18.2.0", "lucide-react": "^0.344.0", "@vitejs/plugin-react": "^4.2.1", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5" } ``` ### 需要添加的依赖 ```json { "vue": "^3.4.0", "@vitejs/plugin-vue": "^5.0.0", "vue-tsc": "^1.8.25", "lucide-vue-next": "^0.344.0", "pinia": "^2.1.0", "vue-router": "^4.2.0", "@vueuse/core": "^10.7.0" } ``` ### 开发依赖 ```json { "@vitejs/plugin-vue": "^5.0.0", "@vue/eslint-config-typescript": "^12.0.0", "eslint-plugin-vue": "^9.20.0" } ``` --- ## 🗂️ 迁移阶段规划 ### 阶段一:项目基础配置(1-2 天) #### 1.1 更新 package.json ```json { "name": "finyx-frontend", "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vue-tsc && vite build", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx", "preview": "vite preview", "type-check": "vue-tsc --noEmit" }, "dependencies": { "vue": "^3.4.0", "pinia": "^2.1.0", "vue-router": "^4.2.0", "lucide-vue-next": "^0.344.0", "@vueuse/core": "^10.7.0" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.0", "@vue/eslint-config-typescript": "^12.0.0", "eslint-plugin-vue": "^9.20.0", "typescript": "^5.2.2", "vite": "^5.0.8", "tailwindcss": "^3.3.6", "vue-tsc": "^1.8.25" } } ``` #### 1.2 更新 vite.config.ts ```typescript import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { fileURLToPath, URL } from 'node:url' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { host: '0.0.0.0', port: 5173, strictPort: false, open: false, }, preview: { host: '0.0.0.0', port: 4173, }, }) ``` #### 1.3 更新 tsconfig.json ```json { "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "preserve", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"] } }, "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] } ``` #### 1.4 创建 env.d.ts ```typescript /// declare module '*.vue' { import type { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component } ``` ### 阶段二:类型定义迁移(1 天) 类型定义文件基本不需要修改,只需确保兼容 Vue。 **src/types/index.ts** - 保持不变 ### 阶段三:工具组件迁移(2-3 天) #### 3.1 ProgressBar 组件 **React 版本** (`src/components/ProgressBar.tsx`): ```typescript import React from 'react'; interface ProgressBarProps { percent: number; status: string; } export const ProgressBar: React.FC = ({ percent, status }) => { // ... }; ``` **Vue 版本** (`src/components/ProgressBar.vue`): ```vue ``` #### 3.2 Toast 组件迁移 **React 版本** (`src/components/Toast.tsx`): ```typescript export const useToast = () => { const [toasts, setToasts] = React.useState([]); // ... }; ``` **Vue 版本** (`src/components/Toast.vue`): ```vue ``` **Toast Context 迁移为 Pinia Store** (`src/stores/toast.ts`): ```typescript import { defineStore } from 'pinia'; import { ref } from 'vue'; import type { Toast, ToastType } from '@/types'; export const useToastStore = defineStore('toast', () => { const toasts = ref([]); const showToast = (message: string, type: ToastType = 'info', duration?: number) => { const id = Math.random().toString(36).substring(7); const newToast: Toast = { id, message, type, duration }; toasts.value.push(newToast); setTimeout(() => { closeToast(id); }, duration || 3000); return id; }; const closeToast = (id: string) => { const index = toasts.value.findIndex(t => t.id === id); if (index > -1) { toasts.value.splice(index, 1); } }; const success = (message: string, duration?: number) => showToast(message, 'success', duration); const error = (message: string, duration?: number) => showToast(message, 'error', duration); const warning = (message: string, duration?: number) => showToast(message, 'warning', duration); const info = (message: string, duration?: number) => showToast(message, 'info', duration); return { toasts, showToast, closeToast, success, error, warning, info, }; }); ``` ### 阶段四:布局组件迁移(2-3 天) #### 4.1 MainLayout 组件 **React 版本**: ```typescript export const MainLayout: React.FC = ({ currentView, setCurrentView, // ... }) => (
{/* ... */}
); ``` **Vue 版本** (`src/layouts/MainLayout.vue`): ```vue ``` ### 阶段五:页面组件迁移(5-7 天) #### 5.1 SetupStep 组件示例 **React 版本关键代码**: ```typescript export const SetupStep: React.FC = ({ setCurrentStep }) => { const toast = useToast(); const [projectName, setProjectName] = useState(''); const [selectedIndustries, setSelectedIndustries] = useState([]); const [errors, setErrors] = useState({}); useEffect(() => { // 验证逻辑 }, [projectName, selectedIndustries, touched]); return (
setProjectName(e.target.value)} />
); }; ``` **Vue 版本** (`src/pages/engagement/SetupStep.vue`): ```vue ``` #### 5.2 InventoryStep 组件迁移要点 **useMemo 转换**: ```typescript // React const sortedData = useMemo(() => { // 排序逻辑 }, [sortField, sortDirection]); // Vue const sortedData = computed(() => { if (!sortField.value || !sortDirection.value) return inventoryData; // 排序逻辑 }); ``` **useEffect 转换**: ```typescript // React useEffect(() => { if (inventoryMode === 'processing') { // 处理逻辑 } }, [inventoryMode]); // Vue watch(() => props.inventoryMode, (newMode) => { if (newMode === 'processing') { // 处理逻辑 } }, { immediate: true }); ``` ### 阶段六:主应用入口迁移(1 天) #### 6.1 main.ts 文件 **React 版本** (`src/main.tsx`): ```typescript import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' import './index.css' ReactDOM.createRoot(document.getElementById('root')!).render( , ) ``` **Vue 版本** (`src/main.ts`): ```typescript import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' import './index.css' const app = createApp(App) const pinia = createPinia() app.use(pinia) app.mount('#root') ``` #### 6.2 App.vue **React 版本** (`src/App.tsx`): ```typescript function App() { const [currentView, setCurrentView] = useState('projects'); // ... return ( ); } ``` **Vue 版本** (`src/App.vue`): ```vue ``` ### 阶段七:图标库迁移(1 天) #### 7.1 全局替换图标导入 **查找替换规则**: ```typescript // 从 import { IconName } from 'lucide-react'; // 到 import { IconName } from 'lucide-vue-next'; ``` **批量替换脚本** (`scripts/replace-icons.sh`): ```bash #!/bin/bash # 替换所有文件中的 lucide-react 导入 find src -type f \( -name "*.vue" -o -name "*.ts" -o -name "*.tsx" \) -exec sed -i 's/lucide-react/lucide-vue-next/g' {} \; # 替换图标属性 size={} 为 :size="" find src -type f -name "*.vue" -exec sed -i 's/size={\([^}]*\)}/:size="\1"/g' {} \; ``` ### 阶段八:测试与验证(3-5 天) #### 8.1 功能测试清单 - [ ] Dashboard 页面渲染正常 - [ ] 项目列表显示正常 - [ ] 项目搜索和筛选功能 - [ ] 项目作业台所有步骤正常 - [ ] 步骤导航功能 - [ ] 表单验证功能 - [ ] Toast 通知功能 - [ ] 演示模式切换 - [ ] 响应式布局 - [ ] 所有交互功能 #### 8.2 性能测试 - [ ] 首屏加载时间 - [ ] 组件渲染性能 - [ ] 大数据量表格性能 - [ ] 内存使用情况 --- ## 📝 代码转换对照表 ### Hooks 转换对照 | React | Vue 3 | |-------|-------| | `useState(value)` | `ref(value)` 或 `reactive({})` | | `useEffect(() => {}, [deps])` | `watch(() => deps, () => {})` 或 `watchEffect(() => {})` | | `useMemo(() => value, [deps])` | `computed(() => value)` | | `useCallback(() => {}, [deps])` | `computed(() => () => {})` 或直接定义函数 | | `useContext(Context)` | `inject(key)` 或 Pinia store | | `useRef(initial)` | `ref(initial)` | | `useReducer(reducer, initial)` | `ref(initial)` + 自定义函数或 Pinia | ### 事件处理转换 | React | Vue 3 | |-------|-------| | `onClick={handler}` | `@click="handler"` | | `onChange={(e) => {}}` | `@change="handler"` 或 `v-model="value"` | | `onSubmit={handler}` | `@submit.prevent="handler"` | | `onKeyDown={handler}` | `@keydown="handler"` | ### 条件渲染转换 | React | Vue 3 | |-------|-------| | `{condition && }` | `` | | `{condition ? : }` | `` | | `{items.map(item => )}` | `` | ### Props 传递转换 | React | Vue 3 | |-------|-------| | `` | `` | | `` | `` | | `{...props}` | `v-bind="props"` | --- ## 🔧 配置文件更新清单 ### 1. package.json - [ ] 移除所有 React 相关依赖 - [ ] 添加 Vue 3 相关依赖 - [ ] 更新构建脚本 - [ ] 更新 lint 脚本 ### 2. vite.config.ts - [ ] 替换 `@vitejs/plugin-react` 为 `@vitejs/plugin-vue` - [ ] 添加路径别名配置 - [ ] 保持服务器配置 ### 3. tsconfig.json - [ ] 更新 `jsx` 选项为 `"preserve"` - [ ] 添加 Vue 文件类型支持 - [ ] 添加路径别名 ### 4. 创建 env.d.ts - [ ] 添加 Vue 模块声明 - [ ] 添加 Vite 客户端类型 ### 5. ESLint 配置 创建 `.eslintrc.cjs`: ```javascript module.exports = { root: true, env: { node: true, browser: true, es2021: true, }, extends: [ 'plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-typescript', ], parserOptions: { ecmaVersion: 2021, }, rules: { // 自定义规则 }, }; ``` --- ## 🚀 迁移执行步骤 ### 第一步:备份当前代码 ```bash git checkout -b vue-migration git commit -am "Backup before Vue migration" ``` ### 第二步:安装新依赖 ```bash npm uninstall react react-dom lucide-react @vitejs/plugin-react @types/react @types/react-dom npm install vue@^3.4.0 pinia@^2.1.0 vue-router@^4.2.0 lucide-vue-next@^0.344.0 @vueuse/core@^10.7.0 npm install -D @vitejs/plugin-vue@^5.0.0 vue-tsc@^1.8.25 @vue/eslint-config-typescript@^12.0.0 eslint-plugin-vue@^9.20.0 ``` ### 第三步:更新配置文件 按照上述配置文件更新清单逐一更新。 ### 第四步:迁移组件(按阶段执行) 1. 先迁移简单组件(ProgressBar、SidebarItem) 2. 迁移工具组件(Toast) 3. 迁移布局组件(Sidebar、MainLayout) 4. 迁移页面组件(按复杂度从低到高) ### 第五步:测试验证 - 运行 `npm run dev` 检查编译错误 - 运行 `npm run type-check` 检查类型错误 - 手动测试所有功能 ### 第六步:清理 - 删除所有 `.tsx` 文件 - 删除 React 相关配置 - 更新文档 --- ## ⚠️ 常见问题与解决方案 ### 问题 1: 图标组件不显示 **解决方案**: 确保使用 `lucide-vue-next` 并正确导入: ```typescript import { IconName } from 'lucide-vue-next'; // 使用 :size 而不是 size ``` ### 问题 2: TypeScript 类型错误 **解决方案**: - 确保 `env.d.ts` 文件存在 - 检查 `tsconfig.json` 配置 - 使用 `vue-tsc` 进行类型检查 ### 问题 3: 响应式数据不更新 **解决方案**: - 使用 `ref()` 包装基本类型 - 使用 `reactive()` 包装对象 - 访问 ref 值时使用 `.value` ### 问题 4: 事件处理不工作 **解决方案**: - 使用 `@click` 而不是 `onClick` - 使用 `@submit.prevent` 阻止默认行为 - 事件参数通过 `$event` 访问 --- ## 📊 迁移进度跟踪 ### 组件迁移清单 #### 基础组件 - [ ] ProgressBar.vue - [ ] SidebarItem.vue - [ ] TableCheckItem.vue - [ ] Toast.vue - [ ] ToastContainer.vue #### 布局组件 - [ ] Sidebar.vue - [ ] MainLayout.vue #### 页面组件 - [ ] DashboardView.vue - [ ] ProjectListView.vue - [ ] EngagementView.vue #### 工作流步骤组件 - [ ] SetupStep.vue - [ ] InventoryStep.vue - [ ] ContextStep.vue - [ ] ValueStep.vue - [ ] DeliveryStep.vue #### 状态管理 - [ ] stores/toast.ts (Pinia) - [ ] stores/app.ts (可选,全局状态) #### 配置文件 - [ ] package.json - [ ] vite.config.ts - [ ] tsconfig.json - [ ] env.d.ts - [ ] .eslintrc.cjs - [ ] main.ts - [ ] App.vue --- ## 🎯 验收标准 ### 功能完整性 - ✅ 所有原有功能正常工作 - ✅ 所有交互行为保持一致 - ✅ 所有样式保持一致 ### 代码质量 - ✅ 无 TypeScript 类型错误 - ✅ 无 ESLint 错误 - ✅ 代码符合 Vue 3 最佳实践 ### 性能指标 - ✅ 首屏加载时间 ≤ 2s - ✅ 页面切换流畅 - ✅ 无内存泄漏 --- ## 📚 参考资源 - [Vue 3 官方文档](https://vuejs.org/) - [Vue 3 Composition API](https://vuejs.org/guide/extras/composition-api-faq.html) - [Pinia 文档](https://pinia.vuejs.org/) - [Vue Router 文档](https://router.vuejs.org/) - [Vite 文档](https://vitejs.dev/) --- ## 📅 时间估算 | 阶段 | 工作量 | 说明 | |------|--------|------| | 阶段一:项目配置 | 1-2 天 | 配置文件更新 | | 阶段二:类型定义 | 1 天 | 类型文件检查 | | 阶段三:工具组件 | 2-3 天 | 简单组件迁移 | | 阶段四:布局组件 | 2-3 天 | 布局组件迁移 | | 阶段五:页面组件 | 5-7 天 | 复杂页面迁移 | | 阶段六:应用入口 | 1 天 | 主文件迁移 | | 阶段七:图标库 | 1 天 | 图标替换 | | 阶段八:测试验证 | 3-5 天 | 功能测试和修复 | | **总计** | **16-25 天** | **约 3-5 周** | --- ## 📝 更新记录 - **2025-01-XX**: 初始迁移方案创建 --- ## 👥 贡献者 - Finyx AI Team