好的, 让我们来详细规划第 8 部分: 性能优化和测试。我们将这部分分为 3 天, 每天专注于特定的优化和测试任务。
第 8 部分:性能优化和测试 (3 天)
Day 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>
</>
);
};-
实现延迟加载 使用 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>
);
};-
使用 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: 单元测试
- 设置测试环境 安装必要的测试库:
npm install --save-dev jest @testing-library/react @testing-library/jest-dom- 为 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();
});
});
});- 为 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: 集成测试和端到端测试
- 设置 Cypress 进行端到端测试 安装 Cypress:
npm install --save-dev cypress- 编写 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);
});
});- 实现集成测试
在
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();
});
});
});- 运行测试并修复问题 运行所有测试:
npm run test
npm run cypress:run根据测试结果, 修复发现的任何问题或错误。
这个详细计划涵盖了第 8 部分的性能优化和测试, 包括实现虚拟滚动、延迟加载、使用 React hooks 优化性能, 以及编写单元测试、集成测试和端到端测试。通过这些优化和测试, 可以确保 LightweightTodo Pro 应用的性能和质量。