1.08 性能优化和测试

好的, 让我们来详细规划第 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 应用的性能和质量。