好的, 让我们来详细规划第 8 部分: 性能优化和测试。我们将这部分分为 3 天, 每天专注于特定的优化和测试任务。

第 8 部分:性能优化和测试 (3 天)

Day 1: 性能优化

  1. 实现虚拟滚动 使用 react-window 库来优化长列表的渲染性能。

    src/components/TodoList.tsx 中:

import React from 'react';
import { FixedSizeList as List } from 'react-window';
import { Box } from '@mantine/core';
 
// ... 其他导入
 
export const TodoList: React.FC = () => {
  // ... 其他代码
 
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => {
    const todo = filteredAndSortedTodos[index];
    return (
      <Box style={style}>
        {/* 渲染单个Todo项的内容 */}
      </Box>
    );
  };
 
  return (
    <>
      <TodoFilters /* ... */ />
      <List
        height={400}
        itemCount={filteredAndSortedTodos.length}
        itemSize={50}
        width="100%"
      >
        {Row}
      </List>
    </>
  );
};
  1. 实现延迟加载 使用 React. Lazy 和 Suspense 来延迟加载不立即需要的组件。

    src/App.tsx 中:

import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Loader } from '@mantine/core';
 
const Home = React.lazy(() => import('./pages/Home'));
const Settings = React.lazy(() => import('./pages/Settings'));
 
const App: React.FC = () => {
  return (
    <Router>
      <Suspense fallback={<Loader />}>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/settings" component={Settings} />
        </Switch>
      </Suspense>
    </Router>
  );
};
  1. 使用 useMemo 和 useCallback 优化 在适当的地方使用这些 hooks 来避免不必要的重新渲染。

    例如, 在 src/components/TodoList.tsx 中:

import React, { useMemo, useCallback } from 'react';
 
export const TodoList: React.FC = () => {
  // ... 其他代码
 
  const filteredAndSortedTodos = useMemo(() => {
    return todos
      .filter(/* ... */)
      .sort(/* ... */);
  }, [todos, filter, sort, search]);
 
  const handleToggleComplete = useCallback(async (id: string, completed: boolean) => {
    // ... 切换完成状态的逻辑
  }, []);
 
  // ... 其他代码
};

Day 2: 单元测试

  1. 设置测试环境 安装必要的测试库:
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
  1. 为 TodoForm 组件编写测试 在 src/components/__tests__/TodoForm.test.tsx 中:
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { TodoForm } from '../TodoForm';
import { createTodo } from '../../services/api';
 
jest.mock('../../services/api');
 
describe('TodoForm', () => {
  it('renders correctly', () => {
    const { getByLabelText, getByText } = render(<TodoForm onTodoCreated={() => {}} />);
    expect(getByLabelText('Title')).toBeInTheDocument();
    expect(getByLabelText('Description')).toBeInTheDocument();
    expect(getByText('Add Todo')).toBeInTheDocument();
  });
 
  it('submits form with correct data', async () => {
    const mockCreateTodo = createTodo as jest.MockedFunction<typeof createTodo>;
    const mockOnTodoCreated = jest.fn();
 
    const { getByLabelText, getByText } = render(<TodoForm onTodoCreated={mockOnTodoCreated} />);
 
    fireEvent.change(getByLabelText('Title'), { target: { value: 'Test Todo' } });
    fireEvent.change(getByLabelText('Description'), { target: { value: 'Test Description' } });
    fireEvent.click(getByText('Add Todo'));
 
    await waitFor(() => {
      expect(mockCreateTodo).toHaveBeenCalledWith({
        title: 'Test Todo',
        description: 'Test Description',
      });
      expect(mockOnTodoCreated).toHaveBeenCalled();
    });
  });
});
  1. 为 TodoList 组件编写测试 在 src/components/__tests__/TodoList.test.tsx 中:
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { TodoList } from '../TodoList';
import { getTodos, updateTodo, deleteTodo } from '../../services/api';
 
jest.mock('../../services/api');
 
describe('TodoList', () => {
  const mockTodos = [
    { id: '1', title: 'Todo 1', description: 'Description 1', completed: false },
    { id: '2', title: 'Todo 2', description: 'Description 2', completed: true },
  ];
 
  beforeEach(() => {
    (getTodos as jest.Mock).mockResolvedValue({ data: mockTodos });
  });
 
  it('renders todos correctly', async () => {
    const { findByText } = render(<TodoList />);
    
    await findByText('Todo 1');
    await findByText('Todo 2');
  });
 
  it('toggles todo completion', async () => {
    const { findByLabelText } = render(<TodoList />);
    
    const checkbox = await findByLabelText('Todo 1');
    fireEvent.click(checkbox);
 
    await waitFor(() => {
      expect(updateTodo).toHaveBeenCalledWith('1', { completed: true });
    });
  });
 
  it('deletes todo', async () => {
    const { findByText } = render(<TodoList />);
    
    const deleteButton = await findByText('Delete');
    fireEvent.click(deleteButton);
 
    await waitFor(() => {
      expect(deleteTodo).toHaveBeenCalledWith('1');
    });
  });
});

Day 3: 集成测试和端到端测试

  1. 设置 Cypress 进行端到端测试 安装 Cypress:
npm install --save-dev cypress
  1. 编写 Cypress 测试 在 cypress/integration/todo_app.spec.js 中:
describe('Todo App', () => {
  beforeEach(() => {
    cy.visit('/');
  });
 
  it('creates a new todo', () => {
    cy.get('input[name="title"]').type('New Todo');
    cy.get('textarea[name="description"]').type('New Description');
    cy.get('button').contains('Add Todo').click();
 
    cy.contains('New Todo').should('be.visible');
    cy.contains('New Description').should('be.visible');
  });
 
  it('completes a todo', () => {
    cy.get('input[type="checkbox"]').first().click();
    cy.get('tr').first().should('have.class', 'completed');
  });
 
  it('deletes a todo', () => {
    cy.get('button').contains('Delete').first().click();
    cy.contains('New Todo').should('not.exist');
  });
 
  it('filters todos', () => {
    cy.get('select').select('Completed');
    cy.get('tr').should('have.length', 1);
  });
});
  1. 实现集成测试 在 src/__tests__/integration.test.tsx 中:
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { App } from '../App';
import { getTodos, createTodo, updateTodo, deleteTodo } from '../services/api';
 
jest.mock('../services/api');
 
describe('Todo App Integration', () => {
  beforeEach(() => {
    (getTodos as jest.Mock).mockResolvedValue({ data: [] });
  });
 
  it('creates, completes, and deletes a todo', async () => {
    const { getByLabelText, getByText, queryByText } = render(<App />);
 
    // Create todo
    fireEvent.change(getByLabelText('Title'), { target: { value: 'New Todo' } });
    fireEvent.change(getByLabelText('Description'), { target: { value: 'New Description' } });
    fireEvent.click(getByText('Add Todo'));
 
    await waitFor(() => {
      expect(createTodo).toHaveBeenCalledWith({
        title: 'New Todo',
        description: 'New Description',
      });
    });
 
    // Complete todo
    (getTodos as jest.Mock).mockResolvedValue({
      data: [{ id: '1', title: 'New Todo', description: 'New Description', completed: false }],
    });
    fireEvent.click(getByLabelText('New Todo'));
 
    await waitFor(() => {
      expect(updateTodo).toHaveBeenCalledWith('1', { completed: true });
    });
 
    // Delete todo
    fireEvent.click(getByText('Delete'));
 
    await waitFor(() => {
      expect(deleteTodo).toHaveBeenCalledWith('1');
      expect(queryByText('New Todo')).not.toBeInTheDocument();
    });
  });
});
  1. 运行测试并修复问题 运行所有测试:
npm run test
npm run cypress:run

根据测试结果, 修复发现的任何问题或错误。

这个详细计划涵盖了第 8 部分的性能优化和测试, 包括实现虚拟滚动、延迟加载、使用 React hooks 优化性能, 以及编写单元测试、集成测试和端到端测试。通过这些优化和测试, 可以确保 LightweightTodo Pro 应用的性能和质量。