跳转至

前端框架深度实战项目

📚 项目概述

本目录包含前端框架深度学习的实战项目,涵盖React、Vue、Angular等框架的深度应用。

项目列表

  1. React高级应用 - React Hooks + React Query + Zustand
  2. Vue全栈应用 - Vue 3 + Nuxt.js + Pinia
  3. Angular企业应用 - Angular + RxJS + NgRx
  4. 微前端应用 - qiankun微前端架构
  5. 性能优化项目 - 优化真实项目的性能

🎯 项目1:React高级应用

项目概述

使用React Hooks、React Query、Zustand构建一个高级Todo应用,展示React的深度应用。

技术栈

  • React 18
  • TypeScript
  • React Query
  • Zustand
  • Tailwind CSS
  • Vite

实现功能

  1. Todo CRUD操作
  2. 实时数据同步
  3. 离线支持
  4. 性能优化
  5. 错误处理

实现步骤

步骤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>
  );
}

部署指南

Bash
# 构建项目
npm run build

# 预览构建结果
npm run preview

# 部署到Vercel
vercel --prod

优化建议

  1. 代码分割:使用React.lazy和Suspense
  2. 性能优化:使用React.memo和useMemo
  3. 错误边界:实现Error Boundary
  4. 测试覆盖:添加单元测试和E2E测试
  5. 监控:集成Sentry错误监控

🎨 项目2:Vue全栈应用

项目概述

使用Vue 3、Nuxt.js、Pinia构建一个全栈博客应用,展示Vue的深度应用。

技术栈

  • Vue 3
  • Nuxt 3
  • Pinia
  • TypeScript
  • Tailwind CSS
  • Markdown

实现功能

  1. 博客文章CRUD
  2. Markdown渲染
  3. SEO优化
  4. 服务端渲染
  5. 评论功能

实现步骤

步骤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>

部署指南

Bash
# 构建项目
npm run build

# 预览构建结果
npm run preview

# 部署到Vercel
vercel --prod

优化建议

  1. SSR优化:使用Nuxt的SSR功能
  2. SEO优化:添加meta标签和结构化数据
  3. 性能优化:使用Nuxt的图片优化
  4. 缓存策略:实现ISR缓存
  5. 监控:集成性能监控

📝 总结

本目录包含前端框架深度学习的实战项目,涵盖React、Vue、Angular等框架的深度应用。每个项目都包含:

  1. 项目概述 - 项目背景和目标
  2. 技术栈 - 使用的技术和工具
  3. 实现功能 - 项目实现的功能
  4. 实现步骤 - 详细的实现步骤
  5. 部署指南 - 项目部署方法
  6. 优化建议 - 性能和代码优化建议

通过完成这些实战项目,你将能够: - 深入理解React、Vue、Angular的核心概念 - 掌握状态管理、数据获取、性能优化等高级技术 - 积累实际项目经验 - 为大厂面试做好准备