引言:为什么选择SPA开发?

单页面应用(Single Page Application, SPA)是现代Web开发的主流架构模式。与传统的多页面应用不同,SPA通过JavaScript动态更新页面内容,无需每次操作都向服务器请求新页面,从而提供更流畅的用户体验。Vue和React作为目前最流行的两大前端框架,正是构建SPA的核心技术。

掌握Vue和React不仅能让你具备构建现代化Web应用的能力,更是通往高薪前端岗位的敲门砖。根据2023年前端开发薪资报告显示,熟练掌握Vue/React的开发者平均薪资比普通前端开发者高出30%-50%。

第一部分:零基础入门阶段(1-2个月)

1.1 必备基础技能储备

在开始学习框架之前,你需要掌握以下基础知识:

  • HTML5/CSS3:语义化标签、Flexbox布局、Grid布局、CSS变量
  • JavaScript ES6+:这是重中之重,包括:
    • let/const变量声明
    • 箭头函数
    • 解构赋值
    • 模板字符串
    • Promise和async/await
    • 模块化(import/export)
    • 数组方法(map/filter/reduce)

示例代码:ES6+核心语法

// 解构赋值与模板字符串
const user = { name: '张三', age: 25 };
const { name, age } = user;
console.log(`用户${name},今年${age}岁`); // 输出:用户张三,今年25岁

// Promise与async/await
function fetchData() {
    return new Promise((resolve) => {
        setTimeout(() => resolve('数据加载完成'), 1000);
    });
}

async function loadData() {
    const result = await fetchData();
    console.log(result); // 1秒后输出:数据加载完成
}

1.2 第一个SPA应用体验

在学习框架前,先用原生JavaScript实现一个简单的SPA,理解核心概念:

<!DOCTYPE html>
<html>
<head>
    <title>简易SPA</title>
    <style>
        .page { display: none; padding: 20px; }
        .active { display: block; }
        nav a { margin-right: 15px; text-decoration: none; }
    </style>
</head>
<body>
    <nav>
        <a href="#home" onclick="showPage('home')">首页</a>
        <a href="#about" onclick="showPage('about')">关于</a>
    </nav>
    
    <div id="home" class="page active">
        <h1>欢迎来到首页</h1>
        <p>这是SPA的简单示例</p>
    </div>
    
    <div id="about" class="page">
        <h1>关于我们</h1>
        <p>学习Vue和React的最佳起点</p>
    </div>

    <script>
        function showPage(pageId) {
            // 隐藏所有页面
            document.querySelectorAll('.page').forEach(page => {
                page.classList.remove('active');
            });
            // 显示目标页面
            document.getElementById(pageId).classList.add('active');
        }
        
        // 监听浏览器前进后退
        window.addEventListener('hashchange', () => {
            const hash = location.hash.substring(1) || 'home';
            showPage(hash);
        });
    </script>
</body>
</html>

这个例子展示了SPA的核心思想:通过JavaScript动态切换页面内容,而不是刷新整个页面。

第二部分:Vue.js核心技术掌握(2-3个月)

2.1 Vue基础语法与核心概念

Vue以易学易用著称,采用自底向上增量开发的设计。从Vue 3开始,推荐使用Composition API。

Vue 3基础示例:

<!DOCTYPE html>
<html>
<head>
    <title>Vue 3基础</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
    <div id="app">
        <h1>{{ title }}</h1>
        <p>计数器:{{ count }}</p>
        <button @click="increment">增加</button>
        <button @click="decrement">减少</button>
        <p v-if="count > 5">计数器超过5了!</p>
        <ul>
            <li v-for="item in items" :key="item.id">
                {{ item.name }} - 价格:{{ item.price }}
            </li>
        </ul>
    </div>

    <script>
        const { createApp, ref, reactive, computed } = Vue;
        
        createApp({
            setup() {
                // 响应式数据
                const count = ref(0);
                const title = ref('Vue 3 计数器示例');
                
                // 响应式对象
                const items = reactive([
                    { id: 1, name: '苹果', price: 5 },
                    { id: 2, name: '香蕉', price: 3 },
                    { id: 3, name: '橙子', price: 4 }
                ]);
                
                // 计算属性
                const totalPrice = computed(() => {
                    return items.reduce((sum, item) => sum + item.price, 0);
                });
                
                // 方法
                const increment = () => {
                    count.value++;
                    console.log(`当前计数:${count.value}`);
                };
                
                const decrement = () => {
                    if (count.value > 0) count.value--;
                };
                
                return {
                    count,
                    title,
                    items,
                    totalPrice,
                    increment,
                    decrement
                };
            }
        }).mount('#app');
    </script>
</body>
</head>
</html>

2.2 Vue组件化开发

组件化是Vue的核心思想之一。下面是一个完整的组件示例:

// TodoItem.vue - 单文件组件示例(伪代码)
<template>
  <div class="todo-item" :class="{ completed: todo.completed }">
    <input type="checkbox" v-model="todo.completed">
    <span>{{ todo.text }}</span>
    <button @click="$emit('delete', todo.id)">删除</button>
  </div>
</template>

<script>
export default {
  name: 'TodoItem',
  props: {
    todo: {
      type: Object,
      required: true
    }
  }
}
</script>

<style scoped>
.todo-item {
  padding: 10px;
  border-bottom: 1px solid #eee;
}
.completed {
  text-decoration: line-through;
  color: #999;
}
</style>

父组件使用:

// App.vue
<template>
  <div id="app">
    <h1>待办事项列表</h1>
    <input v-model="newTodo" placeholder="输入新事项" @keyup.enter="addTodo">
    <button @click="addTodo">添加</button>
    
    <div v-for="todo in todos" :key="todo.id">
      <TodoItem 
        :todo="todo" 
        @delete="deleteTodo"
      />
    </div>
  </div>
</template>

<script>
import TodoItem from './TodoItem.vue';

export default {
  components: { TodoItem },
  data() {
    return {
      newTodo: '',
      todos: [
        { id: 1, text: '学习Vue', completed: false },
        { id: 2, text: '完成项目', completed: true }
      ]
    }
  },
  methods: {
    addTodo() {
      if (this.newTodo.trim()) {
        this.todos.push({
          id: Date.now(),
          text: this.newTodo,
          completed: false
        });
        this.newTodo = '';
      }
    },
    deleteTodo(id) {
      this.todos = this.todos.filter(todo => todo.id !== id);
    }
  }
}
</script>
</code>

2.3 Vue路由与状态管理

Vue Router配置:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
import User from '../views/User.vue';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  },
  {
    // 动态路由参数
    path: '/user/:id',
    name: 'User',
    component: User,
    props: true
  }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

export default router;

Pinia状态管理(Vue推荐):

// store/user.js
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    currentUser: null,
    isLoggedIn: false
  }),
  
  actions: {
    async login(credentials) {
      // 模拟API调用
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials)
      });
      const user = await response.json();
      
      this.currentUser = user;
      this.isLoggedIn = true;
      localStorage.setItem('token', user.token);
    },
    
    logout() {
      this.currentUser = null;
      this.isLoggedIn = false;
      localStorage.removeItem('token');
    }
  },
  
  getters: {
    username: (state) => state.currentUser?.name || '访客',
    isAdmin: (state) => state.currentUser?.role === 'admin'
  }
});

第三部分:React核心技术掌握(2-3个月)

3.1 React基础与JSX语法

React采用声明式编程范式,JSX是其核心特性之一。

React基础示例:

// index.js
import React from 'react';
import ReactDOM from '18/react-client';
import './index.css';

// 函数组件与Hooks
function Counter() {
  const [count, setCount] = React.useState(0);
  const [name, setName] = React.useState('');
  
  // 计算属性
  const message = React.useMemo(() => {
    return `当前计数:${count}`;
  }, [count]);
  
  // 副作用
  React.useEffect(() => {
    document.title = `计数器:${count}`;
    console.log('计数器已更新');
    
    // 清理函数
    return () => {
      console.log('组件卸载');
    };
  }, [count]);
  
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  
  return (
    <div style={{ padding: '20px' }}>
      <h1>React 计数器</h1>
      <p>{message}</p>
      <div>
        <button onClick={increment}>增加</button>
        <button onClick={decrement}>减少</button>
      </div>
      <input 
        type="text" 
        value={name} 
        onChange={(e) => setName(e.target.value)}
        placeholder="输入你的名字"
      />
      {name && <p>你好,{name}!</p>}
    </div>
  );
}

// 渲染到DOM
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);

3.2 React组件化与Props传递

组件拆分示例:

// UserCard.jsx - 用户卡片组件
import React from 'react';
import PropTypes from 'prop-types';

function UserCard({ user, onFollow, isFollowing }) {
  return (
    <div className="user-card">
      <img src={user.avatar} alt={user.name} />
      <h3>{user.name}</h3>
      <p>{user.bio}</p>
      <p>粉丝:{user.followers}</p>
      <button 
        onClick={() => onFollow(user.id)}
        style={{ 
          backgroundColor: isFollowing ? '#ccc' : '#007bff',
          color: 'white',
          padding: '8px 16px',
          border: 'none',
          borderRadius: '4px'
        }}
      >
        {isFollowing ? '已关注' : '关注'}
      </button>
    </div>
  );
}

UserCard.propTypes = {
  user: PropTypes.shape({
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
    avatar: PropTypes.string,
    bio: PropTypes.string,
    followers: PropTypes.number
  }).isRequired,
  onFollow: PropTypes.func.isRequired,
  isFollowing: PropTypes.bool
};

// 父组件
function UserList() {
  const [users, setUsers] = React.useState([
    { id: 1, name: 'Alice', bio: '前端开发者', followers: 100, avatar: 'avatar1.png' },
    { id: 2, name: 'Bob', bio: '全栈工程师', followers: 200, avatar: 'avatar2.png' }
  ]);
  
  const [followingIds, setFollowingIds] = React.useState([]);
  
  const handleFollow = (userId) => {
    setFollowingIds(prev => {
      if (prev.includes(userId)) {
        return prev.filter(id => id !== userId);
      } else {
        return [...prev, userId];
      }
    });
  };
  
  return (
    <div>
      <h2>用户列表</h2>
      <div style={{ display: 'grid', gap: '20px' }}>
        {users.map(user => (
          <UserCard 
            key={user.id}
            user={user}
            onFollow={handleFollow}
            isFollowing={followingIds.includes(user.id)}
          />
        ))}
      </div>
    </div>
  );
}

3.3 React Router与Context API

路由配置:

// App.jsx
import { BrowserRouter as Router, Routes, Route, Link, Navigate } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Profile from './pages/Profile';
import NotFound from './pages/NotFound';

function Navigation() {
  return (
    <nav>
      <Link to="/">首页</Link> | 
      <Link to="/about">关于</Link> | 
      <Link to="/profile">个人中心</Link>
    </nav>
  );
}

function App() {
  return (
    <Router>
      <div className="app">
        <Navigation />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/profile" element={<Profile />} />
          <Route path="/user/:id" element={<Profile />} />
          <Route path="/login" element={<Navigate to="/" replace />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </div>
    </Router>
  );
}

Context API状态管理:

// AuthContext.jsx
import React, { createContext, useContext, useReducer } from 'react';

const AuthContext = createContext();

// Reducer函数
const authReducer = (state, action) => {
  switch (action.type) {
    case 'LOGIN':
      return { user: action.payload, isLoggedIn: true };
    case 'LOGOUT':
      return { user: null, isLoggedIn: false };
    case 'UPDATE_PROFILE':
      return { ...state, user: { ...state.user, ...action.payload } };
    default:
      return state;
  }
};

// Provider组件
export function AuthProvider({ children }) {
  const [state, dispatch] = useReducer(authReducer, {
    user: null,
    isLoggedIn: false
  });
  
  // 异步登录
  const login = async (credentials) => {
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
      });
      const user = await response.json();
      dispatch({ type: 'LOGIN', payload: user });
    } catch (error) {
      console.error('登录失败:', error);
    }
  };
  
  const logout = () => {
    dispatch({ type: 'LOGOUT' });
  };
  
  const updateProfile = (profileData) => {
    dispatch({ type: 'UPDATE_PROFILE', payload: profileData });
  };
  
  return (
    <AuthContext.Provider value={{ 
      state, 
      login, 
      logout, 
      updateProfile 
    }}>
      {children}
    </AuthContext.Provider>
  );
}

// 自定义Hook
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth必须在AuthProvider内使用');
    }
  return context;
}

第四部分:进阶技能与工程化(1-2个月)

4.1 现代化构建工具

Vite配置(Vue/React通用):

// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import react from '@vitejs/plugin-react';

export default defineConfig({
  // 开发服务器配置
  server: {
    port: 3000,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  
  // 路径别名
  resolve: {
    alias: {
      '@': '/src',
      '@components': '/src/components'
    }
  },
  
  // CSS预处理
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      }
    }
  },
  
  // 生产环境配置
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'react', 'react-dom'],
          ui: ['element-plus', 'antd']
        }
      }
    }
  }
});

4.2 TypeScript集成

Vue 3 + TypeScript示例:

// components/UserProfile.vue
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';

// 接口定义
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

interface UserProfile extends User {
  bio?: string;
  avatar?: string;
}

// 响应式数据
const user = ref<UserProfile | null>(null);
const loading = ref<boolean>(false);
const error = ref<string | null>(null);

// 计算属性
const displayName = computed<string>(() => {
  return user.value?.name || '未登录';
});

// 方法
const fetchUser = async (id: number): Promise<void> => {
  loading.value = true;
  error.value = null;
  
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) throw new Error('获取用户失败');
    user.value = await response.json();
  } catch (e) {
    error.value = e instanceof Error ? e.message : '未知错误';
  } finally {
    loading.value = false;
  }
};

// 生命周期
onMounted(() => {
  fetchUser(1);
});
</script>

<template>
  <div class="user-profile">
    <div v-if="loading">加载中...</div>
    <div v-else-if="error" class="error">{{ error }}</div>
    <div v-else-if="user">
      <h2>{{ displayName }}</h2>
      <p>{{ user.email }}</p>
      <p v-if="user.bio">{{ user.bio }}</p>
    </div>
  </div>
</template>

React + TypeScript示例:

// components/TaskList.tsx
import React, { useState, useEffect, useCallback } from 'react';

// 类型定义
interface Task {
  id: number;
  title: string;
  completed: boolean;
  priority: 'low' | 'medium' | 'high';
}

interface TaskListProps {
  initialTasks?: Task[];
}

// 组件
const TaskList: React.FC<TaskListProps> = ({ initialTasks = [] }) => {
  const [tasks, setTasks] = useState<Task[]>(initialTasks);
  const [newTask, setNewTask] = useState<string>('');
  const [filter, setFilter] = useState<'all' | 'completed' | 'active'>('all');

  // 使用useCallback优化性能
  const addTask = useCallback(() => {
    if (newTask.trim()) {
      const task: Task = {
        id: Date.now(),
        title: newTask.trim(),
        completed: false,
        priority: 'medium'
      };
      setTasks(prev => [...prev, task]);
      setNewTask('');
    }
  }, [newTask]);

  // 过滤任务
  const filteredTasks = React.useMemo(() => {
    return tasks.filter(task => {
      if (filter === 'completed') return task.completed;
      if (filter === 'active') return !task.completed;
      return true;
    });
  }, [tasks, filter]);

  // 获取优先级颜色
  const getPriorityColor = (priority: string): string => {
    const colors = { low: 'green', medium: 'orange', high: 'red' };
    return colors[priority as keyof typeof colors];
  };

  return (
    <div className="task-list">
      <h2>任务管理器</h2>
      
      <div>
        <input
          type="text"
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && addTask()}
          placeholder="输入新任务"
        />
        <button onClick={addTask}>添加</button>
      </div>

      <div>
        <button onClick={() => setFilter('all')}>全部</button>
        <button onClick={() => setFilter('active')}>进行中</button>
        <button onClick={() => setFilter('completed')}>已完成</button>
      </div>

      <ul>
        {filteredTasks.map(task => (
          <li key={task.id} style={{ textDecoration: task.completed ? 'line-through' : 'none' }}>
            <input
              type="checkbox"
              checked={task.completed}
              onChange={() => {
                setTasks(prev => prev.map(t => 
                  t.id === task.id ? { ...t, completed: !t.completed } : t
                ));
              }}
            />
            <span style={{ color: getPriorityColor(task.priority) }}>
              [{task.priority.toUpperCase()}]
            </span>
            {task.title}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TaskList;

4.3 测试与部署

单元测试示例(Vue Test Utils):

// components/__tests__/Counter.spec.js
import { mount } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import Counter from '../Counter.vue';

describe('Counter组件', () => {
  it('初始计数为0', () => {
    const wrapper = mount(Counter);
    expect(wrapper.text()).toContain('计数:0');
  });

  it('点击增加按钮计数+1', async () => {
    const wrapper = mount(Counter);
    const button = wrapper.find('button');
    await button.trigger('click');
    expect(wrapper.text()).toContain('计数:1');
  });

  it('计数超过5显示警告', async () => {
    const wrapper = mount(Counter);
    for (let i = 0; i < 6; i++) {
      await wrapper.find('button').trigger('click');
    }
    expect(wrapper.text()).toContain('计数器超过5了!');
  });
});

React Testing Library测试:

// components/__tests__/TaskList.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import TaskList from '../TaskList';

describe('TaskList组件', () => {
  const mockTasks = [
    { id: 1, title: '学习React', completed: false, priority: 'high' },
    { id: 2, title: '完成项目', completed: true, priority: 'medium' }
  ];

  it('渲染初始任务', () => {
    render(<TaskList initialTasks={mockTasks} />);
    expect(screen.getByText('学习React')).toBeInTheDocument();
    expect(screen.getByText('完成项目')).toBeInTheDocument();
  });

  it('添加新任务', () => {
    render(<TaskList />);
    const input = screen.getByPlaceholderText('输入新任务');
    const button = screen.getByText('添加');
    
    fireEvent.change(input, { target: { value: '新任务' } });
    fireEvent.click(button);
    
    expect(screen.getByText('新任务')).toBeInTheDocument();
  });

  it('过滤已完成任务', () => {
    render(<TaskList initialTasks={mockTasks} />);
    const completedButton = screen.getByText('已完成');
    fireEvent.click(completedButton);
    
    expect(screen.queryByText('学习React')).not.toBeInTheDocument();
    expect(screen.getByText('完成项目')).toBeInTheDocument();
  });
});

第五部分:高薪就业实战项目(1个月)

5.1 项目一:电商后台管理系统(Vue版)

项目结构:

admin-dashboard/
├── src/
│   ├── api/
│   │   ├── product.js
│   │   └── order.js
│   ├── components/
│   │   ├── ProductTable.vue
│   │   └── OrderChart.vue
│   ├── store/
│   │   └── modules/
│   │       ├── product.js
│   │       └── order.js
│   ├── router/
│   │   └── index.js
│   ├── views/
│   │   ├── Login.vue
│   │   ├── Dashboard.vue
│   │   ├── ProductList.vue
│   │   └── OrderList.vue
│   ├── App.vue
│   └── main.js
├── package.json
└── vite.config.js

核心功能实现:

// store/modules/product.js (Pinia)
import { defineStore } from 'pinia';
import { getProductList, createProduct, updateProduct, deleteProduct } from '@/api/product';

export const useProductStore = defineStore('product', {
  state: () => ({
    products: [],
    total: 0,
    loading: false,
    currentProduct: null
  }),
  
  actions: {
    async fetchProducts(params = {}) {
      this.loading = true;
      try {
        const response = await getProductList(params);
        this.products = response.data;
        this.total = response.total;
      } catch (error) {
        console.error('获取产品列表失败:', error);
        throw error;
      } finally {
        this.loading = false;
      }
    },
    
    async createProduct(data) {
      const response = await createProduct(data);
      this.products.unshift(response.data);
      this.total++;
      return response;
    },
    
    async updateProduct(id, data) {
      const response = await updateProduct(id, data);
      const index = this.products.findIndex(p => p.id === id);
      if (index !== -1) {
        this.products[index] = response.data;
      }
      return response;
    },
    
    async removeProduct(id) {
      await deleteProduct(id);
      this.products = this.products.filter(p => p.id !== id);
      this.total--;
    }
  },
  
  getters: {
    activeProducts: (state) => state.products.filter(p => p.status === 'active'),
    productNames: (state) => state.products.map(p => p.name)
  }
});

API封装:

// api/product.js
import request from '@/utils/request';

export function getProductList(params) {
  return request({
    url: '/api/products',
    method: 'get',
    params
  });
}

export function createProduct(data) {
  return request({
    url: '/api/products',
    method: 'post',
    data
  });
}

export function updateProduct(id, data) {
  return request({
    url: `/api/products/${id}`,
    method: 'put',
    data
  });
}

export function deleteProduct(id) {
  return request({
    url: `/api/products/${id}`,
    method: 'delete'
  });
}

5.2 项目二:社交聊天应用(React版)

核心组件:

// ChatApp.jsx
import React, { useState, useEffect, useRef } from 'react';
import { useAuth } from './AuthContext';

function ChatApp() {
  const { state: { user } } = useAuth();
  const [messages, setMessages] = useState([]);
  const [inputMessage, setInputMessage] = useState('');
  const [ws, setWs] = useState(null);
  const messagesEndRef = useRef(null);

  // WebSocket连接
  useEffect(() => {
    const websocket = new WebSocket('ws://localhost:8080/chat');
    
    websocket.onopen = () => {
      console.log('WebSocket连接已建立');
      websocket.send(JSON.stringify({ type: 'join', userId: user.id }));
    };
    
    websocket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.type === 'message') {
        setMessages(prev => [...prev, data.message]);
      }
    };
    
    websocket.onclose = () => {
      console.log('WebSocket连接已关闭');
    };
    
    setWs(websocket);
    
    return () => {
      websocket.close();
    };
  }, [user.id]);

  // 自动滚动到底部
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  const sendMessage = () => {
    if (inputMessage.trim() && ws) {
      const message = {
        id: Date.now(),
        userId: user.id,
        userName: user.name,
        text: inputMessage,
        timestamp: new Date().toISOString()
      };
      
      ws.send(JSON.stringify({
        type: 'message',
        message
      }));
      
      setInputMessage('');
    }
  };

  return (
    <div className="chat-app">
      <div className="chat-header">
        <h2>聊天室 ({user.name})</h2>
      </div>
      
      <div className="messages-container">
        {messages.map(msg => (
          <div key={msg.id} className={`message ${msg.userId === user.id ? 'own' : ''}`}>
            <div className="message-author">{msg.userName}</div>
            <div className="message-text">{msg.text}</div>
            <div className="message-time">
              {new Date(msg.timestamp).toLocaleTimeString()}
            </div>
          </div>
        ))}
        <div ref={messagesEndRef} />
      </div>
      
      <div className="message-input">
        <input
          type="text"
          value={inputMessage}
          onChange={(e) => setInputMessage(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
          placeholder="输入消息..."
        />
        <button onClick={sendMessage}>发送</button>
      </div>
    </div>
  );
}

第六部分:高薪就业准备(2周)

6.1 简历优化与面试准备

简历模板要点:

个人技能:
- 熟练掌握Vue 3 + TypeScript,了解Vue 2
- 熟练掌握React 18 + Hooks,了解React Native
- 掌握状态管理:Pinia、Redux Toolkit、Zustand
- 掌握路由:Vue Router、React Router
- 掌握构建工具:Vite、Webpack
- 掌握测试:Vitest、Jest、React Testing Library
- 掌握UI库:Element Plus、Ant Design、Tailwind CSS
- 掌握TypeScript,能编写类型安全的代码
- 了解Node.js、Express、MongoDB

项目经验:
1. 电商后台管理系统(Vue 3 + TypeScript)
   - 负责商品管理、订单管理模块开发
   - 使用Pinia进行状态管理,实现数据缓存
   - 使用Axios封装请求,实现请求拦截和错误处理
   - 使用Element Plus实现响应式布局
   - 项目成果:提升开发效率40%,减少bug率30%

2. 社交聊天应用(React 18 + TypeScript)
   - 使用WebSocket实现实时通信
   - 使用Context API + useReducer管理全局状态
   - 实现消息已读/未读状态、文件上传功能
   - 使用React Testing Library编写单元测试
   - 项目成果:支持1000+并发用户,响应时间<100ms

6.2 面试高频问题

Vue相关问题:

  1. Vue 2和Vue 3的区别?

    • 响应式原理:Vue 2使用Object.defineProperty,Vue 3使用Proxy
    • 组件API:Vue 2使用Options API,Vue 3推荐Composition API
    • 性能:Vue 3编译优化更好,体积更小
    • TypeScript支持:Vue 3原生支持更好
  2. Vue的生命周期钩子有哪些?

    • beforeCreate, created
    • beforeMount, mounted
    • beforeUpdate, updated
    • beforeDestroy, destroyed (Vue 2) / beforeUnmount, unmounted (Vue 3)
    • activated, deactivated (keep-alive)
    • errorCaptured
  3. Vue的响应式原理是什么?

    // 简化版Proxy实现
    function reactive(obj) {
     return new Proxy(obj, {
       get(target, key) {
         track(target, key); // 依赖收集
         return target[key];
       },
       set(target, key, value) {
         target[key] = value;
         trigger(target, key); // 触发更新
         return true;
       }
     });
    }
    

React相关问题:

  1. React Hooks解决了什么问题?

    • 让函数组件也能拥有状态和生命周期
    • 避免类组件的this指向问题
    • 更好的逻辑复用(自定义Hook)
    • 更容易拆分和测试
  2. useEffect和useLayoutEffect的区别?

    • useEffect在浏览器绘制后执行
    • useLayoutEffect在浏览器绘制前执行
    • useLayoutEffect可以同步读取DOM布局,避免闪烁
  3. React的合成事件是什么?

    • React封装的跨浏览器原生事件对象
    • 通过事件委托机制,所有事件绑定在document上
    • 解决浏览器兼容性问题
    • 自动进行事件池优化

通用问题:

  1. Vue和React的选择标准?

    • 项目规模:小项目Vue更易上手,大项目React生态更完善
    • 团队背景:熟悉JSX选React,熟悉模板语法选Vue
    • 需求:需要移动端选React Native,需要丰富UI组件库选Vue
    • 学习曲线:Vue更平缓,React更灵活
  2. 如何优化SPA性能?

    • 路由懒加载
    • 组件按需加载
    • 图片懒加载
    • 使用Web Workers处理复杂计算
    • 合理使用缓存(localStorage、IndexedDB)
    • 代码分割和Tree Shaking
    • 使用CDN加速

第七部分:持续学习与职业发展

7.1 推荐学习资源

官方文档(必读):

优质社区:

进阶书籍:

  • 《Vue.js设计与实现》- 霍春阳
  • 《React设计原理》- 钟灵
  • 《JavaScript高级程序设计》- Nicholas C. Zakas

7.2 职业发展路径

初级前端工程师(0-2年)

  • 熟练掌握Vue/React基础
  • 能独立完成模块开发
  • 了解基本的工程化概念
  • 薪资范围:8k-15k

中级前端工程师(2-5年)

  • 精通Vue/React,能进行架构设计
  • 掌握TypeScript和测试
  • 有性能优化经验
  • 能指导初级工程师
  • 薪资范围:15k-25k

高级前端工程师(5年以上)

  • 前端技术专家,能制定技术方案
  • 有大型项目架构经验
  • 熟悉Node.js和后端技术
  • 能进行技术选型和团队管理
  • 薪资范围:25k-40k+

7.3 持续学习建议

  1. 每周投入10小时学习新技术

    • 关注Vue和React的版本更新
    • 学习新的状态管理方案(如Zustand、Pinia)
    • 了解新的构建工具(如Turbopack)
  2. 参与开源项目

    • 在GitHub上贡献代码
    • 阅读优秀开源项目的源码
    • 尝试提交PR
  3. 建立个人品牌

    • 写技术博客
    • 在GitHub上展示项目
    • 参加技术分享会
  4. 关注行业趋势

    • WebAssembly
    • 微前端架构
    • Server Components
    • 边缘计算

结语

掌握Vue和React需要持续的学习和实践。从基础语法到工程化,从个人项目到团队协作,每一步都是通向高薪的必经之路。记住,框架只是工具,核心是解决问题的能力。保持好奇心,持续学习,你一定能在这个行业获得成功。

最后建议:

  • 不要急于求成,扎实基础
  • 多写代码,多做项目
  • 建立自己的知识体系
  • 保持对新技术的热情
  • 善于总结和分享

祝你学习顺利,早日拿到心仪的offer!