TypeScript Comprehensive Project Practice
This tutorial uses a complete project case to comprehensively apply various features of TypeScript.
From project setup to actual development, it fully demonstrates the application of TypeScript in real-world projects.
SVG Diagram: Project Architecture
Why Do We Need Comprehensive Project Practice?
After learning TypeScript syntax, you need to consolidate your knowledge through real projects.
This tutorial demonstrates a complete task management system, covering frontend, backend, and type definitions.
Through this project, you can master the best practices of TypeScript in real-world development.
Project Goals:Create a task management system with task creation, query, update, and deletion features.
Project Structure
Organize the project structure using a Monorepo style.
Directory Structure
├── src/
│ ├── types/# Type Definitions
│ │ ├── task.ts# Task Types
│ │ ├── api.ts# API Types
│ │ └── index.ts# Type Exports
│ │
│ ├── services/# Service Layer
│ │ ├── taskService.ts# Task Service
│ │ └── index.ts
│ │
│ ├── components/# React Components
│ │ ├── TaskList.tsx# Task List
│ │ ├── TaskItem.tsx# Task Item
│ │ ├── TaskForm.tsx# Task Form
│ │ └── index.ts
│ │
│ ├── hooks/# Custom Hooks
│ │ ├── useTasks.ts# Task State Management
│ │ └── index.ts
│ │
│ ├── App.tsx# Main App
│ ├── App.css# Styles
│ └── main.tsx# Entry File
│
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts
Directory Division:Directories are divided by function, separating type definitions, service layer, and components.
Type Definitions
First define the core types of the project.
src/types/task.ts
export type TaskStatus = "pending" | "in-progress" | "completed";
// Task priority enum
export type TaskPriority = "low" | "medium" | "high";
// Task interface definition
export interface Task {
id: string; // Task ID
title: string; // Task title
description?: string; // Task description (optional)
status: TaskStatus; // Task status
priority: TaskPriority; // Task priority
createdAt: string; // Creation time
updatedAt: string; // Update time
dueDate?: string; // Due date (optional)
tags?: string[]; // Tags (optional)
}
// Input type for creating a task
export interface CreateTaskInput {
title: string;
description?: string;
priority: TaskPriority;
dueDate?: string;
tags?: string[];
}
// Input type for updating a task
export interface UpdateTaskInput {
title?: string;
description?: string;
status?: TaskStatus;
priority?: TaskPriority;
dueDate?: string;
tags?: string[];
}
// Task filter options
export interface TaskFilter {
status?: TaskStatus;
priority?: TaskPriority;
search?: string;
}
Type Layering:Define input types, output types, and filter types separately for easier maintenance.
API Type Definitions
Define API-related types.
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
// Pagination metadata
export interface PaginationMeta {
total: number;
page: number;
pageSize: number;
totalPages: number;
}
// Pagination response type
export interface PaginatedResponse<T> {
items: T[];
meta: PaginationMeta;
}
// Request error type
export interface ApiError {
code: string;
message: string;
details?: Record<string, string>;
}
// HTTP method type
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
// Task-related API endpoints
export interface TaskEndpoints {
getAll: "/api/tasks";
getById: "/api/tasks/:id";
create: "/api/tasks";
update: "/api/tasks/:id";
delete: "/api/tasks/:id";
}
API types:Unified response format and error handling types facilitate frontend-backend integration.
Task Service Layer
Implement the business logic for task management.
src/services/taskService.ts
import {
Task,
CreateTaskInput,
UpdateTaskInput,
TaskFilter,
TaskStatus,
TaskPriority
} from "../types/task";
// Generate unique ID
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// Mock database (in-memory storage)
let tasks: Task[] = [
{
id: "1",
title: "Learn TypeScript",
description: "Master TypeScript basics and advanced features",
status: "completed",
priority: "high",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
tags: ["Learn", "TypeScript"]
},
{
id: "2",
title: "Develop a task management system",
description: "Develop with React + TypeScript",
status: "in-progress",
priority: "high",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
tags: ["Project", "Practice"]
}
];
// Task service class
class TaskService {
// Get all tasks
getAll(filter?: TaskFilter): Task[] {
let result = [...tasks];
if (filter) {
if (filter.status) {
result = result.filter(t => t.status === filter.status);
}
if (filter.priority) {
result = result.filter(t => t.priority === filter.priority);
}
if (filter.search) {
const search = filter.search.toLowerCase();
result = result.filter(t =>
t.title.toLowerCase().includes(search) ||
t.description?.toLowerCase().includes(search)
);
}
}
return result;
}
// Get task by ID
getById(id: string): Task | undefined {
return tasks.find(t => t.id === id);
}
// Create task
create(input: CreateTaskInput): Task {
const now = new Date().toISOString();
const task: Task = {
id: generateId(),
title: input.title,
description: input.description,
status: "pending",
priority: input.priority,
createdAt: now,
updatedAt: now,
dueDate: input.dueDate,
tags: input.tags
};
tasks.push(task);
return task;
}
// Update task
update(id: string, input: UpdateTaskInput): Task | null {
const index = tasks.findIndex(t => t.id === id);
if (index === -1) return null;
const task = tasks[index];
const updated: Task = {
...task,
...input,
updatedAt: new Date().toISOString()
};
tasks[index] = updated;
return updated;
}
// Delete task
delete(id: string): boolean {
const index = tasks.findIndex(t => t.id === id);
if (index === -1) return false;
tasks.splice(index, 1);
return true;
}
// Update task status
updateStatus(id: string, status: TaskStatus): Task | null {
return this.update(id, { status });
}
}
// Export service instance
export const taskService = new TaskService();
Service layer:Business logic is centralized in the service layer for easy testing and maintenance.
Custom Hook
Use Hook to manage task state.
import { useState, useEffect, useCallback } from "react";
import {
Task,
CreateTaskInput,
UpdateTaskInput,
TaskFilter,
TaskStatus,
TaskPriority
} from "../types/task";
import { taskService } from "../services/taskService";
// State type returned by the Hook
interface UseTasksReturn {
tasks: Task[];
loading: boolean;
error: string | null;
filter: TaskFilter;
// Operation methods
createTask: (input: CreateTaskInput) => Promise<void>;
updateTask: (id: string, input: UpdateTaskInput) => Promise<void>;
deleteTask: (id: string) => Promise<void>;
updateStatus: (id: string, status: TaskStatus) => Promise<void>;
setFilter: (filter: TaskFilter) => void;
refresh: () => void;
}
// Initialize default filter
const defaultFilter: TaskFilter = {};
export function useTasks(): UseTasksReturn {
// Task list state
const [tasks, setTasks] = useState<Task[]>([]);
// Loading state
const [loading, setLoading] = useState(true);
// Error state
const [error, setError] = useState<string | null>(null);
// Filter conditions
const [filter, setFilter] = useState<TaskFilter>(defaultFilter);
// Load task list
const loadTasks = useCallback(() => {
setLoading(true);
setError(null);
try {
const data = taskService.getAll(filter);
setTasks(data);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to load");
} finally {
setLoading(false);
}
}, [filter]);
// Reload on initial load and filter changes
useEffect(() => {
loadTasks();
}, [loadTasks]);
// Create task
const createTask = useCallback(async (input: CreateTaskInput) => {
try {
taskService.create(input);
loadTasks();
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to create");
}
}, [loadTasks]);
// Update task
const updateTask = useCallback(async (id: string, input: UpdateTaskInput) => {
try {
taskService.update(id, input);
loadTasks();
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to update");
}
}, [loadTasks]);
// Delete task
const deleteTask = useCallback(async (id: string) => {
try {
taskService.delete(id);
loadTasks();
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to delete");
}
}, [loadTasks]);
// Update task status
const updateStatus = useCallback(async (id: string, status: TaskStatus) => {
try {
taskService.updateStatus(id, status);
loadTasks();
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to update status");
}
}, [loadTasks]);
// Refresh task list
const refresh = useCallback(() => {
loadTasks();
}, [loadTasks]);
return {
tasks,
loading,
error,
filter,
createTask,
updateTask,
deleteTask,
updateStatus,
setFilter,
refresh
};
}
Custom Hook:Encapsulate state management and business logic in a Hook, making components more concise.
React Components
Use the Hook to create a task list component.
src/components/TaskList.tsx
import React from "react";
import { useTasks } from "../hooks/useTasks";
import { Task, TaskStatus, TaskPriority } from "../types/task";
// Task item component Props
interface TaskItemProps {
task: Task;
onStatusChange: (id: string, status: TaskStatus) => void;
onDelete: (id: string) => void;
}
// Task item component
const TaskItem: React.FC<TaskItemProps> = ({
task,
onStatusChange,
onDelete
}) => {
// Status style mapping
const statusStyles: Record<TaskStatus, string> = {
"pending": "status-pending",
"in-progress": "status-progress",
"completed": "status-completed"
};
// Priority style mapping
const priorityLabels: Record<TaskPriority, string> = {
"low": "Low",
"medium": "Medium",
"high": "High"
};
return (
<div className={`task-item ${statusStyles[task.status]}`}>
<div className="task-content">
<h3 className="task-title">{task.title}</h3>
{task.description && (
<p className="task-description">{task.description}</p>
)}
<div className="task-meta">
<span className={`priority priority-${task.priority}`}>
{priorityLabels[task.priority]}
</span>
<span className="task-date">
{new Date(task.createdAt).toLocaleDateString()}
</span>
</div>
</div>
<div className="task-actions">
<select
value={task.status}
onChange={(e) => onStatusChange(task.id, e.target.value as TaskStatus)}
className="status-select"
>
<option value="pending">Pending</option>
<option value="in-progress">In Progress</option>
<option value="completed">Completed</option>
</select>
<button
onClick={() => onDelete(task.id)}
className="delete-btn"
>
Delete
</button>
</div>
</div>
);
};
// Task list component
export const TaskList: React.FC = () => {
// Use custom Hook to get task state
const {
tasks,
loading,
error,
updateStatus,
deleteTask
} = useTasks();
// Render loading state
if (loading) {
return <div className="loading">Loading...</div>;
}
// Render error state
if (error) {
return <div className="error">Error: {error}</div>;
}
// Render empty state
if (tasks.length === 0) {
return (
<div className="empty">
<p>No tasks yet</p>
<p>Click the button above to create a new task</p>
</div>
);
}
// Render task list
return (
<div className="task-list">
{tasks.map(task => (
<TaskItem
key={task.id}
task={task}
onStatusChange={updateStatus}
onDelete={deleteTask}
/>
))}
</div>
);
};
export default TaskList;
Component Splitting:Split TaskItem into an independent component, making the code clearer.
Main App Component
Integrate all components to form a complete application.
src/App.tsx
import React, { useState } from "react";
import { TaskList } from "./components/TaskList";
import { useTasks } from "./hooks/useTasks";
import { CreateTaskInput, TaskPriority } from "./types/task";
import "./App.css";
// Task form component Props
interface TaskFormProps {
onSubmit: (input: CreateTaskInput) => void;
}
// Task form component
const TaskForm: React.FC<TaskFormProps> = ({ onSubmit }) => {
// Form state
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [priority, setPriority] = useState<TaskPriority>("medium");
// Submit handling
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!title.trim()) {
alert("Please enter a task title");
return;
}
onSubmit({
title: title.trim(),
description: description.trim() || undefined,
priority
});
// Reset form
setTitle("");
setDescription("");
setPriority("medium");
};
return (
<form onSubmit={handleSubmit} className="task-form">
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter task title"
className="form-input"
/>
<input
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Enter task description (optional)"
className="form-input"
/>
<select
value={priority}
onChange={(e) => setPriority(e.target.value as TaskPriority)}
className="form-select"
>
<option value="low">Low priority</option>
<option value="medium">Medium priority</option>
<option value="high">High priority</option>
</select>
<button type="submit" className="submit-btn">
Add Task
</button>
</form>
);
};
// Main app component
const App: React.FC = () => {
// Use custom Hook
const { createTask, error } = useTasks();
// Render error message
const renderError = () => {
if (!error) return null;
return <div className="app-error">{error}</div>;
};
return (
<div className="app">
<header className="app-header">
<h1>TypeScript Task Management System</h1>
<p>Comprehensive Practice Project</p>
</header>
{renderError()}
<main className="app-main">
<TaskForm onSubmit={createTask} />
<TaskList />
</main>
<footer className="app-footer">
<p>Powered by TypeScript + React</p>
</footer>
</div>
);
};
export default App;
Component Composition:Build complex UI by composing simple components.
Project Configuration
The project's TypeScript and build configuration.
tsconfig.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": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
Strict mode:Enable strict for the most comprehensive type checking.
Notes
- Types first:Define types first, then write implementation code.
- Interface vs type:Use interfaces for object types, type aliases for union types.
- Layered architecture:Organize types, services, and components into layers.
- Hook encapsulation:Encapsulate business logic in Hooks.
Best practices:Type definitions are the foundation of a TypeScript project; design them carefully.
Summary
Through this comprehensive project, we practiced the core concepts of TypeScript.
- Type definitions:Interfaces such as Task, CreateTaskInput, TaskFilter.
- Service layer:TaskService encapsulates business logic.
- Custom Hooks:useTasks manages state.
- React components:Type-safe component development.
- Project configuration:Strict TypeScript configuration.
Other extensionsSuggestions:Participate in more real projects to deepen your understanding of TypeScript through practice.