Cursor项目重构实践

在2025年3月的这个清晨,当我通过Cursor生成的TodoList项目首次运行时,那个将所有逻辑堆积在app.js中的"面条式代码"令人如鲠在喉。这促使我开启了一场与AI协作的重构之旅,以下是完整的心得记录

一、原始架构的痛点分析

初始项目采用典型的单体组件架构,app.js承载了:

  • 接口请求(直接调用fetch)
  • 全局状态管理(useState贯穿全组件)
  • 界面渲染(500+行JSX代码)
    这种架构导致调试困难、复用率低、维护成本高,完全不符合现代Web开发规范
     
App.js(old)
 import React, { useState, useEffect } from 'react';
import { FiSun, FiMoon, FiPlus, FiTrash2, FiFilter, FiCheck } from 'react-icons/fi';
import './App.css';

function App() {
  const [darkMode, setDarkMode] = useState(false);
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [filter, setFilter] = useState('all');

  // 获取所有待办事项
  const fetchTodos = async () => {
    try {
      setLoading(true);
      const response = await fetch('/api/get-todo');
      if (!response.ok) {
        throw new Error('获取待办事项失败');
      }
      const data = await response.json();
      setTodos(data);
      setError(null);
    } catch (err) {
      setError('获取待办事项时出错: ' + err.message);
      console.error('获取待办事项时出错:', err);
    } finally {
      setLoading(false);
    }
  };

  // 添加新待办事项
  const addTodo = async (e) => {
    e.preventDefault();
    if (!newTodo.trim()) return;

    try {
      const response = await fetch('/api/add-todo', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ value: newTodo, isCompleted: false }),
      });

      if (!response.ok) {
        throw new Error('添加待办事项失败');
      }

      const addedTodo = await response.json();
      setTodos([...todos, addedTodo]);
      setNewTodo('');
    } catch (err) {
      setError('添加待办事项时出错: ' + err.message);
      console.error('添加待办事项时出错:', err);
    }
  };

  // 更新待办事项状态
  const toggleTodo = async (id) => {
    try {
      const response = await fetch(`/api/update-todo/${id}`, {
        method: 'POST',
      });

      if (!response.ok) {
        throw new Error('更新待办事项状态失败');
      }

      const updatedTodo = await response.json();
      setTodos(todos.map(todo => todo.id === id ? updatedTodo : todo));
    } catch (err) {
      setError('更新待办事项状态时出错: ' + err.message);
      console.error('更新待办事项状态时出错:', err);
    }
  };

  // 删除待办事项
  const deleteTodo = async (id) => {
    try {
      const response = await fetch(`/api/del-todo/${id}`, {
        method: 'POST',
      });

      if (!response.ok) {
        throw new Error('删除待办事项失败');
      }

      await response.json();
      setTodos(todos.filter(todo => todo.id !== id));
    } catch (err) {
      setError('删除待办事项时出错: ' + err.message);
      console.error('删除待办事项时出错:', err);
    }
  };

  // 组件加载时获取待办事项
  useEffect(() => {
    fetchTodos();
  }, []);

  // 检测系统主题偏好
  useEffect(() => {
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
      setDarkMode(true);
    }

    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
      setDarkMode(e.matches);
    });
  }, []);

  // 应用暗色模式
  useEffect(() => {
    if (darkMode) {
      document.body.classList.add('dark');
    } else {
      document.body.classList.remove('dark');
    }
  }, [darkMode]);

  // 过滤待办事项
  const filteredTodos = todos.filter(todo => {
    if (filter === 'all') return true;
    if (filter === 'active') return !todo.isCompleted;
    if (filter === 'completed') return todo.isCompleted;
    return true;
  });

  // 计算完成度
  const completionRate = todos.length > 0
    ? Math.round((todos.filter(todo => todo.isCompleted).length / todos.length) * 100)
    : 0;

  return (
    <div className="min-h-screen pb-10">
      <header className="header">
        <div className="header-content">
          <h1 className="app-title">优雅待办</h1>
          <button
            onClick={() => setDarkMode(!darkMode)}
            className="theme-toggle"
            aria-label={darkMode ? "切换到亮色模式" : "切换到暗色模式"}
          >
            {darkMode ? <FiSun className="text-yellow-400 w-5 h-5" /> : <FiMoon className="text-gray-700 w-5 h-5" />}
          </button>
        </div>
      </header>

      <main className="todo-container">
        <div className="progress-card fade-in">
          <h2 className="progress-title">任务完成度</h2>
          <div className="progress-bar-bg">
            <div
              className="progress-bar"
              style={{ width: `${completionRate}%` }}
            />
          </div>
          <p className="text-sm font-medium">{completionRate}% 已完成</p>
        </div>

        <form onSubmit={addTodo} className="todo-form">
          <div className="form-container">
            <input
              type="text"
              className="todo-input"
              placeholder="添加新的待办事项..."
              value={newTodo}
              onChange={(e) => setNewTodo(e.target.value)}
            />
            <button
              type="submit"
              className="add-button"
              disabled={newTodo.trim() === ''}
            >
              <FiPlus className="w-5 h-5" />
              添加
            </button>
          </div>
        </form>

        <div className="filter-container">
          <h2 className="filter-title">我的待办</h2>
          <div className="filter-buttons">
            <button
              onClick={() => setFilter('all')}
              className={`filter-button ${filter === 'all' ? 'active' : ''}`}
            >
              全部
            </button>
            <button
              onClick={() => setFilter('active')}
              className={`filter-button ${filter === 'active' ? 'active' : ''}`}
            >
              未完成
            </button>
            <button
              onClick={() => setFilter('completed')}
              className={`filter-button ${filter === 'completed' ? 'active' : ''}`}
            >
              已完成
            </button>
          </div>
        </div>

        {loading ? (
          <div className="text-center py-4">
            <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto"></div>
            <p className="mt-2">加载中...</p>
          </div>
        ) : error ? (
          <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
            <span className="block sm:inline">{error}</span>
          </div>
        ) : filteredTodos.length === 0 ? (
          <div className="empty-state">
            <FiFilter className="empty-icon" />
            <p className="text-lg">没有{filter === 'all' ? '' : filter === 'active' ? '未完成的' : '已完成的'}待办事项</p>
          </div>
        ) : (
          <ul>
            {filteredTodos.map(todo => (
              <li
                key={todo.id}
                className="todo-item fade-in"
              >
                <div
                  className={`todo-checkbox ${todo.isCompleted ? 'checked' : ''}`}
                  onClick={() => toggleTodo(todo.id)}
                >
                  {todo.isCompleted && <FiCheck className="text-white w-3 h-3" />}
                </div>
                <span
                  className={`todo-text ${todo.isCompleted ? 'completed' : ''}`}
                >
                  {todo.value}
                </span>
                <button
                  onClick={() => deleteTodo(todo.id)}
                  className="delete-button"
                  aria-label="删除待办事项"
                >
                  <FiTrash2 className="w-4 h-4" />
                </button>
              </li>
            ))}
          </ul>
        )}
      </main>
    </div>
  );
}

export default App;

二、重构三部曲实践

1. 接口请求抽离(AI辅助的抽象艺术)​
通过三次Prompt迭代实现理想解耦:

原始Prompt:
"抽离接口请求到service目录"
→ 生成不合理的Hooks封装(未考虑鉴权需求)

优化Prompt:
"创建axiosInstance封装请求头鉴权,uri.js统一管理接口路径,service.js聚合业务请求"
→ 生成符合企业级规范的请求层架构

最终目录结构:

src/
  services/
    axiosInstance.js  // 带JWT自动注入的实例
    uri.js            // 接口路径常量
    todoAPI.js        // 业务请求方法

2. 组件拆分策略(AI的模块化思维)​
基于功能边界进行原子化拆分:

// 原始单体组件
function App() {
  // 包含表单、列表、筛选等所有逻辑
}

components/
  TodoForm/
    index.jsx       // 表单提交组件
    style.module.css
  TodoList/
    index.jsx       // 列表展示组件
  TodoFilter/
    index.jsx       // 筛选控制组件

 

3. 状态管理升级(AI推荐的Zustand实践)​
采用轻量级状态库替代useState:

// store/todoStore.js
import create from 'zustand';

const useTodoStore = create((set) => ({
  todos: [],
  addTodo: (newTodo) => set(state => ({todos: [...state.todos, newTodo]})),
  fetchTodos: async () => {
    const data = await todoAPI.getTodos(); // 调用service层
    set({ todos: data })
  }
}))

这种分层管理使业务逻辑与UI解耦

三、AI协作中的经验总结

  1. Prompt工程的艺术:需明确指定技术选型(如axios替代fetch)、目录规范等约束条件
  2. 代码审查的必要性:AI生成的示例代码需人工验证边界情况(如错误处理)
  3. 渐进式重构策略:通过Lerna实现模块化改造的平滑过渡
  4. 文档驱动开发:利用Cursor自动生成TypeScript类型定义和JSDoc注释

四、未来演进方向

计划引入AI驱动的以下能力:

  1. 自动化E2E测试用例生成(基于业务场景推导)
  2. 智能Bundle分析优化(Tree-shaking建议)
  3. 可视化架构守护(实时检测架构异味)

结语:这次重构不仅是代码结构的优化,更是人机协作模式的探索。AI不再是简单的代码生成器,而是架构设计的协作者——它提供可能性的边界,而开发者把握方向的选择。正如软件工程大师Martin Fowler所言:"优秀的架构往往诞生于持续的演进,而非初始的设计"

 

app.js
 import React, { useEffect } from 'react';
import './App.css';
import Header from './components/layout/Header';
import ProgressCard from './components/todo/ProgressCard';
import TodoForm from './components/todo/TodoForm';
import TodoFilter from './components/todo/TodoFilter';
import TodoList from './components/todo/TodoList';
import useTodoStore from './store/todoStore';

function App() {
  const {
    darkMode,
    setDarkMode,
    fetchTodos
  } = useTodoStore();

  // 组件加载时获取待办事项
  useEffect(() => {
    fetchTodos();
  }, [fetchTodos]);

  // 检测系统主题偏好
  useEffect(() => {
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
      setDarkMode(true);
    }

    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
      setDarkMode(e.matches);
    });
  }, [setDarkMode]);

  // 应用暗色模式
  useEffect(() => {
    if (darkMode) {
      document.body.classList.add('dark');
    } else {
      document.body.classList.remove('dark');
    }
  }, [darkMode]);

  return (
    <div className="min-h-screen pb-10">
      <Header
        darkMode={darkMode}
        toggleDarkMode={() => setDarkMode(!darkMode)}
      />

      <main className="todo-container">
        <ProgressCard />
        <TodoForm />
        <TodoFilter />
        <TodoList />
      </main>
    </div>
  );
}

export default App;
posted @   Yang9710  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示