第15课: 实战项目
Vue 3 + TypeScript项目
项目初始化
# 使用Vite创建项目
npm create vite@latest my-vue-app -- --template vue-ts
# 安装依赖
cd my-vue-app
npm install
# 安装Vue Router和Pinia
npm install vue-router@4 pinia
类型定义
// src/types/user.ts
export interface User {
id: number;
name: string;
email: string;
avatar?: string;
}
export interface LoginForm {
email: string;
password: string;
}
export interface ApiResponse {
data: T;
message: string;
code: number;
}
API服务
// src/api/user.ts
import type { User, LoginForm, ApiResponse } from '@/types/user';
export const userApi = {
async login(form: LoginForm): Promise> {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form)
});
return response.json();
},
async getUser(id: number): Promise> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
};
Pinia Store
// src/stores/user.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { User } from '@/types/user';
export const useUserStore = defineStore('user', () => {
const user = ref(null);
const isLoggedIn = computed(() => user.value !== null);
function setUser(newUser: User) {
user.value = newUser;
}
function logout() {
user.value = null;
}
return { user, isLoggedIn, setUser, logout };
});
Vue组件
// src/components/UserCard.vue
<script setup lang="ts">
import type { User } from '@/types/user';
interface Props {
user: User;
showEmail?: boolean;
}
const props = withDefaults(defineProps(), {
showEmail: true
});
const emit = defineEmits<{
click: [user: User];
}>();
function handleClick() {
emit('click', props.user);
}
</script>
<template>
<div class="user-card" @click="handleClick">
<img :src="user.avatar" :alt="user.name" />
<h3>{{ user.name }}</h3>
<p v-if="showEmail">{{ user.email }}</p>
</div>
</template>
React + TypeScript项目
项目初始化
# 使用Vite创建项目
npm create vite@latest my-react-app -- --template react-ts
# 安装依赖
cd my-react-app
npm install
类型定义
// src/types/index.ts
export interface Todo {
id: number;
title: string;
completed: boolean;
createdAt: Date;
}
export type TodoFilter = 'all' | 'active' | 'completed';
自定义Hook
// src/hooks/useTodos.ts
import { useState, useCallback } from 'react';
import type { Todo } from '@/types';
export function useTodos() {
const [todos, setTodos] = useState([]);
const addTodo = useCallback((title: string) => {
const newTodo: Todo = {
id: Date.now(),
title,
completed: false,
createdAt: new Date()
};
setTodos(prev => [...prev, newTodo]);
}, []);
const toggleTodo = useCallback((id: number) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []);
const deleteTodo = useCallback((id: number) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
return { todos, addTodo, toggleTodo, deleteTodo };
}
React组件
// src/components/TodoItem.tsx
import React from 'react';
import type { Todo } from '@/types';
interface TodoItemProps {
todo: Todo;
onToggle: (id: number) => void;
onDelete: (id: number) => void;
}
export const TodoItem: React.FC = ({
todo,
onToggle,
onDelete
}) => {
return (
<div className="todo-item">
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span className={todo.completed ? 'completed' : ''}>
{todo.title}
</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</div>
);
};
Node.js + TypeScript项目
项目初始化
# 初始化项目
npm init -y
# 安装依赖
npm install express
npm install -D typescript @types/node @types/express ts-node nodemon
# 初始化TypeScript配置
npx tsc --init
Express服务器
// src/index.ts
import express, { Request, Response, NextFunction } from 'express';
const app = express();
const PORT = 3000;
app.use(express.json());
// 类型定义
interface User {
id: number;
name: string;
email: string;
}
// 扩展Request类型
interface TypedRequest extends Request {
body: T;
}
// 路由处理
app.get('/api/users', (req: Request, res: Response) => {
const users: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com' }
];
res.json(users);
});
app.post('/api/users', (req: TypedRequest, res: Response) => {
const newUser = req.body;
res.status(201).json(newUser);
});
// 错误处理中间件
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
数据库模型
// src/models/User.ts
export class User {
constructor(
public id: number,
public name: string,
public email: string,
private password: string
) {}
public validatePassword(password: string): boolean {
return this.password === password;
}
public toJSON() {
return {
id: this.id,
name: this.name,
email: this.email
};
}
}
TypeScript最佳实践
1. 避免使用any
// 不好
function process(data: any) {
return data.value;
}
// 好
function process(data: T) {
return data.value;
}
2. 使用类型守卫
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function process(value: unknown) {
if (isString(value)) {
console.log(value.toUpperCase());
}
}
3. 使用readonly
interface Config {
readonly apiUrl: string;
readonly timeout: number;
}
const config: Readonly = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
4. 使用联合类型而不是枚举
// 推荐
type Status = 'pending' | 'success' | 'error';
// 而不是
enum Status {
Pending = 'pending',
Success = 'success',
Error = 'error'
}
5. 使用工具类型
interface User {
id: number;
name: string;
email: string;
password: string;
}
// 创建用户时不需要id
type CreateUserDTO = Omit;
// 更新用户时所有字段可选
type UpdateUserDTO = Partial;
// 公开用户信息
type PublicUser = Omit;
项目开发流程:
- 设计类型系统和接口定义
- 配置TypeScript和构建工具
- 实现核心功能和业务逻辑
- 编写单元测试和集成测试
- 配置代码检查和格式化
- 优化性能和打包配置
- 部署和持续集成
实战练习:
- 创建一个Todo应用,使用Vue 3或React + TypeScript
- 实现用户认证系统,包含登录、注册、权限管理
- 构建RESTful API服务,使用Node.js + Express + TypeScript
- 集成数据库(如MongoDB或PostgreSQL)并定义类型
- 添加单元测试和端到端测试
- 配置CI/CD流程
学习资源
- 官方文档: https://www.typescriptlang.org/docs/
- TypeScript Playground: https://www.typescriptlang.org/play
- DefinitelyTyped: https://github.com/DefinitelyTyped/DefinitelyTyped
- TypeScript Deep Dive: https://basarat.gitbook.io/typescript/