前端框架深度实战项目¶
📚 项目概述¶
本目录包含前端框架深度学习的实战项目,涵盖React、Vue、Angular等框架的深度应用。
项目列表¶
- React高级应用 - React Hooks + React Query + Zustand
- Vue全栈应用 - Vue 3 + Nuxt.js + Pinia
- Angular企业应用 - Angular + RxJS + NgRx
- 微前端应用 - qiankun微前端架构
- 性能优化项目 - 优化真实项目的性能
🎯 项目1:React高级应用¶
项目概述¶
使用React Hooks、React Query、Zustand构建一个高级Todo应用,展示React的深度应用。
技术栈¶
- React 18
- TypeScript
- React Query
- Zustand
- Tailwind CSS
- Vite
实现功能¶
- Todo CRUD操作
- 实时数据同步
- 离线支持
- 性能优化
- 错误处理
实现步骤¶
步骤1:项目初始化¶
Bash
# 创建项目
npm create vite@latest react-todo-app -- --template react-ts
cd react-todo-app
# 安装依赖
npm install @tanstack/react-query zustand
npm install -D tailwindcss postcss autoprefixer
npm install -D @types/node
# 初始化Tailwind CSS
npx tailwindcss init -p
步骤2:配置Tailwind CSS¶
JavaScript
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
// 指定Tailwind需要扫描的文件路径,用于生成按需CSS(Tree-shaking)
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}", // 扫描src下所有JS/TS/JSX/TSX文件中使用的类名
],
theme: {
extend: {}, // 在此扩展默认主题(如自定义颜色、字体、间距等)
},
plugins: [], // Tailwind插件列表(如表单、排版插件等)
}
步骤3:创建Zustand Store¶
TypeScript
// src/store/todoStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: Date;
}
interface TodoStore {
todos: Todo[];
filter: 'all' | 'active' | 'completed';
addTodo: (text: string) => void;
toggleTodo: (id: string) => void;
deleteTodo: (id: string) => void;
setFilter: (filter: 'all' | 'active' | 'completed') => void;
}
// 创建Zustand Store,使用persist中间件实现本地持久化
export const useTodoStore = create<TodoStore>()(
persist(
(set) => ({
todos: [], // 待办事项列表
filter: 'all', // 当前过滤条件
// 添加待办:使用展开运算符创建新数组(不可变更新模式)
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{
id: Date.now().toString(), // 使用时间戳作为唯一ID
text,
completed: false,
createdAt: new Date(),
},
],
})),
// 切换完成状态:通过map遍历找到目标项并取反completed
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
})),
// 删除待办:通过filter过滤掉目标项
deleteTodo: (id) =>
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
})),
// 设置过滤条件(all/active/completed)
setFilter: (filter) => set({ filter }),
}),
{
name: 'todo-storage', // localStorage中的存储键名
}
)
);
步骤4:创建React Query Hooks¶
TypeScript
// src/hooks/useTodos.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
interface Todo {
id: string;
text: string;
completed: boolean;
}
// 获取Todos列表 - 使用useQuery进行数据获取和缓存管理
export function useTodos() {
return useQuery({
queryKey: ['todos'], // 缓存键,React Query基于此键管理缓存和自动刷新
queryFn: async () => {
const response = await fetch('/api/todos');
if (!response.ok) {
throw new Error('Network response was not ok'); // 抛出错误触发React Query的错误处理
}
return response.json();
},
});
}
// 添加Todo - 使用useMutation处理数据变更操作
export function useAddTodo() {
const queryClient = useQueryClient(); // 获取查询客户端实例,用于操作缓存
return useMutation({
mutationFn: async (text: string) => {
const response = await fetch('/api/todos', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // 告知服务器请求体为JSON格式
},
body: JSON.stringify({ text }),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
},
// 变更成功后,使todos查询缓存失效,触发自动重新获取最新数据
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
}
// 切换Todo完成状态 - PATCH请求只更新部分字段
export function useToggleTodo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (id: string) => {
const response = await fetch(`/api/todos/${id}/toggle`, {
method: 'PATCH', // 使用PATCH表示部分更新
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] }); // 刷新列表缓存
},
});
}
// 删除Todo - DELETE请求删除指定资源
export function useDeleteTodo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (id: string) => {
const response = await fetch(`/api/todos/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] }); // 删除后刷新列表
},
});
}
步骤5:创建Todo组件¶
TypeScript
// src/components/TodoItem.tsx
import { useTodoStore } from '../store/todoStore';
interface TodoItemProps {
todo: {
id: string;
text: string;
completed: boolean;
};
}
export function TodoItem({ todo }: TodoItemProps) {
// 使用选择器函数从Store中精确提取所需方法,避免不必要的重渲染
const toggleTodo = useTodoStore((state) => state.toggleTodo);
const deleteTodo = useTodoStore((state) => state.deleteTodo);
return (
// Flex布局:左侧checkbox+文本,右侧删除按钮,两端对齐
<div className="flex items-center justify-between p-4 bg-white rounded-lg shadow mb-2">
<div className="flex items-center">
{/* 复选框:点击时切换完成状态 */}
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
className="mr-3 h-5 w-5"
/>
{/* 已完成项显示删除线样式 */}
<span
className={`${
todo.completed ? 'line-through text-gray-500' : 'text-gray-900'
}`}
>
{todo.text}
</span>
</div>
{/* 删除按钮 */}
<button
onClick={() => deleteTodo(todo.id)}
className="text-red-500 hover:text-red-700"
>
Delete
</button>
</div>
);
}
步骤6:创建TodoList组件¶
TypeScript
// src/components/TodoList.tsx
import { useTodoStore } from '../store/todoStore';
import { TodoItem } from './TodoItem';
export function TodoList() {
// 解构获取全部待办数据和当前过滤条件
const { todos, filter } = useTodoStore();
// 根据过滤条件筛选显示的待办项
const filteredTodos = todos.filter((todo) => {
if (filter === 'active') return !todo.completed; // 仅显示未完成
if (filter === 'completed') return todo.completed; // 仅显示已完成
return true; // 显示全部
});
return (
// space-y-2: Tailwind工具类,子元素之间添加垂直间距
<div className="space-y-2">
{/* 遍历渲染每个待办项,key使用唯一id确保React高效diff */}
{filteredTodos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</div>
);
}
步骤7:创建App组件¶
TypeScript
// src/App.tsx
import { useState } from 'react';
import { useTodoStore } from './store/todoStore';
import { TodoList } from './components/TodoList';
import { useAddTodo } from './hooks/useTodos';
export function App() {
const [inputValue, setInputValue] = useState(''); // 输入框受控组件状态
const { filter, setFilter } = useTodoStore(); // 从Store获取过滤状态和设置方法
const addTodo = useTodoStore((state) => state.addTodo); // 本地添加待办方法
const { mutate: addTodoMutation, isPending } = useAddTodo(); // 服务端添加,isPending跟踪请求状态
// 表单提交处理:阻止默认刷新行为,添加待办后清空输入框
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); // 阻止表单默认提交行为
if (inputValue.trim()) { // 防止添加空白内容
addTodo(inputValue.trim());
setInputValue(''); // 清空输入框
}
};
return (
<div className="min-h-screen bg-gray-100 p-8">
<div className="max-w-2xl mx-auto">
<h1 className="text-3xl font-bold mb-8 text-center">
React Todo App
</h1>
<form onSubmit={handleSubmit} className="mb-6">
<div className="flex gap-2">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Add a new todo..."
className="flex-1 p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isPending}
/>
<button
type="submit"
disabled={isPending || !inputValue.trim()}
className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-400 disabled:cursor-not-allowed"
>
{isPending ? 'Adding...' : 'Add'}
</button>
</div>
</form>
<div className="flex gap-2 mb-6">
<button
onClick={() => setFilter('all')}
className={`px-4 py-2 rounded-lg ${
filter === 'all'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-700 hover:bg-gray-50'
}`}
>
All
</button>
<button
onClick={() => setFilter('active')}
className={`px-4 py-2 rounded-lg ${
filter === 'active'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-700 hover:bg-gray-50'
}`}
>
Active
</button>
<button
onClick={() => setFilter('completed')}
className={`px-4 py-2 rounded-lg ${
filter === 'completed'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-700 hover:bg-gray-50'
}`}
>
Completed
</button>
</div>
<TodoList />
</div>
</div>
);
}
部署指南¶
优化建议¶
- 代码分割:使用React.lazy和Suspense
- 性能优化:使用React.memo和useMemo
- 错误边界:实现Error Boundary
- 测试覆盖:添加单元测试和E2E测试
- 监控:集成Sentry错误监控
🎨 项目2:Vue全栈应用¶
项目概述¶
使用Vue 3、Nuxt.js、Pinia构建一个全栈博客应用,展示Vue的深度应用。
技术栈¶
- Vue 3
- Nuxt 3
- Pinia
- TypeScript
- Tailwind CSS
- Markdown
实现功能¶
- 博客文章CRUD
- Markdown渲染
- SEO优化
- 服务端渲染
- 评论功能
实现步骤¶
步骤1:项目初始化¶
Bash
# 创建Nuxt项目
npx nuxi init blog-app
# 安装依赖
cd blog-app
npm install @pinia/nuxt
npm install -D @types/markdown-it
步骤2:配置Nuxt¶
TypeScript
// nuxt.config.ts - Nuxt框架核心配置文件
export default defineNuxtConfig({
// 注册Nuxt模块,@pinia/nuxt自动集成Pinia状态管理
modules: [
'@pinia/nuxt',
],
// 全局CSS样式文件,~代表项目根目录别名
css: ['~/assets/css/main.css'],
// 应用级配置:设置页面<head>中的默认元信息
app: {
head: {
title: 'Vue Blog', // 默认页面标题
meta: [
{ charset: 'utf-8' }, // 字符编码
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }, // 响应式视口
],
},
},
})
步骤3:创建Pinia Store¶
TypeScript
// stores/blog.ts
import { defineStore } from 'pinia';
interface Post {
id: string;
title: string;
content: string;
createdAt: Date;
}
// 使用defineStore定义Pinia Store,'blog'为Store的唯一标识
export const useBlogStore = defineStore('blog', {
// state: Store的响应式状态(类似Vue组件的data)
state: () => ({
posts: [] as Post[], // 文章列表
currentPost: null as Post | null, // 当前查看的文章
loading: false, // 加载状态标记
error: null as string | null, // 错误信息
}),
// actions: 定义修改状态的方法,支持异步操作
actions: {
// 获取所有文章列表
async fetchPosts() {
this.loading = true; // 开始加载
this.error = null; // 清除之前的错误
try {
// $fetch是Nuxt内置的请求工具,支持SSR和类型推断
const response = await $fetch<Post[]>('/api/posts');
this.posts = response;
} catch (err: unknown) {
// 类型安全的错误处理
this.error = err instanceof Error ? err.message : String(err);
} finally {
this.loading = false; // 无论成功失败,结束加载状态
}
},
// 根据ID获取单篇文章详情
async fetchPost(id: string) {
this.loading = true;
this.error = null;
try {
const response = await $fetch<Post>(`/api/posts/${id}`);
this.currentPost = response; // 存储到currentPost供详情页使用
} catch (err: unknown) {
this.error = err instanceof Error ? err.message : String(err);
} finally {
this.loading = false;
}
},
// 创建新文章 - Omit类型排除id和createdAt(由服务端生成)
async createPost(post: Omit<Post, 'id' | 'createdAt'>) {
this.loading = true;
this.error = null;
try {
const response = await $fetch<Post>('/api/posts', {
method: 'POST',
body: post, // Nuxt的$fetch会自动序列化body为JSON
});
this.posts.push(response); // 将新文章添加到本地列表,避免重新请求
} catch (err: unknown) {
this.error = err instanceof Error ? err.message : String(err);
} finally {
this.loading = false;
}
},
},
// getters: 计算属性,类似Vue的computed,基于state派生数据
getters: {
// 按创建时间倒序排列文章(新文章在前)
sortedPosts: (state) => {
return [...state.posts].sort(
(a, b) => b.createdAt.getTime() - a.createdAt.getTime()
);
},
},
})
步骤4:创建博客页面¶
Vue
<!-- pages/index.vue -->
<template>
<div class="min-h-screen bg-gray-100">
<div class="max-w-4xl mx-auto p-8">
<h1 class="text-4xl font-bold mb-8 text-center">
Vue Blog
</h1>
<div v-if="loading" class="text-center">
Loading...
</div>
<div v-else-if="error" class="text-red-500 text-center">
{{ error }}
</div>
<div v-else class="space-y-6">
<div
v-for="post in sortedPosts"
:key="post.id"
class="bg-white rounded-lg shadow p-6 hover:shadow-lg transition-shadow"
>
<h2 class="text-2xl font-bold mb-2">
{{ post.title }}
</h2>
<p class="text-gray-600 mb-4">
{{ post.content.substring(0, 150) }}...
</p>
<NuxtLink
:to="`/posts/${post.id}`"
class="text-blue-500 hover:text-blue-700"
>
Read more
</NuxtLink>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const blogStore = useBlogStore();
// storeToRefs: 将Store中的state/getters转换为响应式ref,保持解构后的响应性
const { posts, loading, error } = storeToRefs(blogStore);
const { sortedPosts } = blogStore; // getter可以直接解构(已是计算属性)
// 组件挂载后获取文章列表数据
onMounted(() => {
blogStore.fetchPosts();
});
</script>
步骤5:创建文章详情页¶
Vue
<!-- pages/posts/[id].vue -->
<template>
<div class="min-h-screen bg-gray-100">
<div class="max-w-4xl mx-auto p-8">
<div v-if="loading" class="text-center">
Loading...
</div>
<div v-else-if="error" class="text-red-500 text-center">
{{ error }}
</div>
<article v-else-if="currentPost" class="bg-white rounded-lg shadow p-8">
<h1 class="text-4xl font-bold mb-4">
{{ currentPost.title }}
</h1>
<p class="text-gray-600 mb-8">
{{ formatDate(currentPost.createdAt) }}
</p>
<div class="prose max-w-none">
{{ currentPost.content }}
</div>
</article>
</div>
</div>
</template>
<script setup lang="ts">
const route = useRoute(); // 获取当前路由信息,包含URL参数
const blogStore = useBlogStore();
const { currentPost, loading, error } = storeToRefs(blogStore);
// 页面挂载时根据路由参数id获取文章详情
onMounted(() => {
blogStore.fetchPost(route.params.id as string); // 动态路由参数[id]
});
// 日期格式化:将Date对象转换为中文日期(如:2026年2月18日)
function formatDate(date: Date) {
return new Date(date).toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}
</script>
部署指南¶
优化建议¶
- SSR优化:使用Nuxt的SSR功能
- SEO优化:添加meta标签和结构化数据
- 性能优化:使用Nuxt的图片优化
- 缓存策略:实现ISR缓存
- 监控:集成性能监控
📝 总结¶
本目录包含前端框架深度学习的实战项目,涵盖React、Vue、Angular等框架的深度应用。每个项目都包含:
- 项目概述 - 项目背景和目标
- 技术栈 - 使用的技术和工具
- 实现功能 - 项目实现的功能
- 实现步骤 - 详细的实现步骤
- 部署指南 - 项目部署方法
- 优化建议 - 性能和代码优化建议
通过完成这些实战项目,你将能够: - 深入理解React、Vue、Angular的核心概念 - 掌握状态管理、数据获取、性能优化等高级技术 - 积累实际项目经验 - 为大厂面试做好准备