深入探索现代前端开发:从基础到架构的完整指南

前端开发已经从一个简单的页面美化角色,演变为构建复杂用户界面的关键技术。随着Web技术的飞速发展,现代前端开发涵盖了从基础HTML/CSS/JavaScript到复杂架构设计的广泛领域。本文将带您深入探索现代前端开发的各个方面,并通过丰富的代码实例展示最佳实践。

一、现代前端开发基础

1.1 语义化HTML5

语义化HTML不仅有助于SEO和可访问性,还能提高代码的可维护性。让我们看一个现代网页结构的例子:

html

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="现代前端开发实践">
    <title>现代前端开发指南</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <header role="banner">
        <nav aria-label="主导航">
            <ul>
                <li><a href="#home">首页</a></li>
                <li><a href="#about">关于</a></li>
                <li><a href="#contact">联系</a></li>
            </ul>
        </nav>
    </header>

    <main>
        <article>
            <header>
                <h1>现代前端开发的核心概念</h1>
                <time datetime="2024-01-15">2024年1月15日</time>
            </header>
            
            <section aria-labelledby="section1">
                <h2 id="section1">响应式设计</h2>
                <p>响应式设计是现代前端开发的基石...</p>
            </section>
            
            <figure>
                <img src="web-development.jpg" alt="前端开发示意图">
                <figcaption>现代前端开发工作流程</figcaption>
            </figure>
        </article>
    </main>

    <aside aria-label="相关链接">
        <h3>相关文章</h3>
        <ul>
            <li><a href="#">JavaScript ES6+ 特性</a></li>
            <li><a href="#">React 最佳实践</a></li>
        </ul>
    </aside>

    <footer role="contentinfo">
        <p>&copy; 2024 前端开发博客</p>
    </footer>
</body>
</html>
1.2 现代CSS架构

现代CSS开发强调可维护性和可扩展性。让我们看看如何使用CSS Grid、Flexbox和CSS变量创建响应式布局:

css

复制代码
/* 定义CSS变量 */
:root {
    --primary-color: #2563eb;
    --secondary-color: #64748b;
    --background-color: #f8fafc;
    --text-color: #334155;
    --border-radius: 8px;
    --spacing-xs: 0.5rem;
    --spacing-sm: 1rem;
    --spacing-md: 1.5rem;
    --spacing-lg: 2rem;
    --spacing-xl: 3rem;
}

/* 重置和基础样式 */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
    line-height: 1.6;
    color: var(--text-color);
    background-color: var(--background-color);
}

/* 容器布局 */
.container {
    display: grid;
    grid-template-areas:
        "header header header"
        "main main sidebar"
        "footer footer footer";
    grid-template-columns: 1fr 3fr 1fr;
    grid-template-rows: auto 1fr auto;
    min-height: 100vh;
    gap: var(--spacing-md);
    padding: var(--spacing-md);
}

/* 响应式设计 */
@media (max-width: 768px) {
    .container {
        grid-template-areas:
            "header"
            "main"
            "sidebar"
            "footer";
        grid-template-columns: 1fr;
    }
}

/* 组件样式 */
.card {
    background: white;
    border-radius: var(--border-radius);
    padding: var(--spacing-lg);
    box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
    transition: transform 0.2s, box-shadow 0.2s;
}

.card:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}

/* 实用工具类 */
.text-center { text-align: center; }
.mb-1 { margin-bottom: var(--spacing-xs); }
.mb-2 { margin-bottom: var(--spacing-sm); }
.mb-3 { margin-bottom: var(--spacing-md); }

.flex {
    display: flex;
}

.flex-center {
    display: flex;
    justify-content: center;
    align-items: center;
}

.grid {
    display: grid;
    gap: var(--spacing-md);
}

.grid-2 {
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}

二、现代JavaScript开发

2.1 ES6+ 特性详解

现代JavaScript引入了许多强大的特性,让代码更简洁、更易读:

javascript

复制代码
// 类与模块化
class ApiService {
    #baseUrl; // 私有字段

    constructor(baseUrl) {
        this.#baseUrl = baseUrl;
    }

    // 异步函数
    async fetchData(endpoint, options = {}) {
        try {
            const url = `${this.#baseUrl}/${endpoint}`;
            const response = await fetch(url, {
                headers: {
                    'Content-Type': 'application/json',
                    ...options.headers
                },
                ...options
            });

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            return await response.json();
        } catch (error) {
            console.error('API请求失败:', error);
            throw error;
        }
    }

    // 静态方法
    static create(baseUrl) {
        return new ApiService(baseUrl);
    }
}

// 解构赋值和默认参数
const { createApp } = Vue;

const user = {
    id: 1,
    name: '张三',
    email: 'zhangsan@example.com',
    address: {
        city: '北京',
        zipCode: '100000'
    }
};

// 对象解构
const { name, email, address: { city } } = user;

// 数组解构
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;

// 箭头函数和模板字符串
const formatUserInfo = ({ name, email, city }) => {
    return `
        <div class="user-info">
            <h3>${name}</h3>
            <p>邮箱: ${email}</p>
            <p>城市: ${city}</p>
        </div>
    `;
};

// Promise和异步处理
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

class DataProcessor {
    constructor() {
        this.cache = new Map();
    }

    async processUserData(userId) {
        // 检查缓存
        if (this.cache.has(userId)) {
            return this.cache.get(userId);
        }

        try {
            // 模拟API调用
            await delay(1000);
            const apiService = ApiService.create('https://api.example.com');
            const userData = await apiService.fetchData(`users/${userId}`);
            
            // 数据处理
            const processedData = this.#transformData(userData);
            
            // 缓存结果
            this.cache.set(userId, processedData);
            
            return processedData;
        } catch (error) {
            throw new Error(`处理用户数据失败: ${error.message}`);
        }
    }

    #transformData(data) {
        // 使用Map和Set进行数据处理
        const dataMap = new Map(
            Object.entries(data).map(([key, value]) => [key, value])
        );

        return {
            ...Object.fromEntries(dataMap),
            processedAt: new Date().toISOString(),
            fullName: `${data.firstName} ${data.lastName}`.trim()
        };
    }
}

// 使用示例
const processor = new DataProcessor();

// 并行处理多个请求
Promise.allSettled([
    processor.processUserData(1),
    processor.processUserData(2),
    processor.processUserData(3)
]).then(results => {
    results.forEach((result, index) => {
        if (result.status === 'fulfilled') {
            console.log(`用户${index + 1}数据:`, result.value);
        } else {
            console.error(`用户${index + 1}处理失败:`, result.reason);
        }
    });
});

// 生成器函数
function* idGenerator() {
    let id = 1;
    while (true) {
        yield id++;
    }
}

const userIdGenerator = idGenerator();
console.log(userIdGenerator.next().value); // 1
console.log(userIdGenerator.next().value); // 2
2.2 函数式编程实践

函数式编程在前端开发中越来越受欢迎,它让代码更可预测、更易测试:

javascript

复制代码
// 纯函数
const calculateTotal = (items, taxRate) => {
    const subtotal = items.reduce((sum, item) => sum + item.price, 0);
    const tax = subtotal * taxRate;
    return {
        subtotal: Math.round(subtotal * 100) / 100,
        tax: Math.round(tax * 100) / 100,
        total: Math.round((subtotal + tax) * 100) / 100
    };
};

// 高阶函数
const withLogging = (fn) => (...args) => {
    console.log(`调用函数: ${fn.name}`, args);
    const result = fn(...args);
    console.log(`函数结果:`, result);
    return result;
};

const createValidator = (rules) => (data) => {
    return Object.entries(rules).reduce((errors, [field, validate]) => {
        const value = data[field];
        const error = validate(value, data);
        if (error) {
            errors[field] = error;
        }
        return errors;
    }, {});
};

// 柯里化
const curry = (fn) => {
    const curried = (...args) => {
        if (args.length >= fn.length) {
            return fn(...args);
        }
        return (...moreArgs) => curried(...args, ...moreArgs);
    };
    return curried;
};

// 组合函数
const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x);
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);

// 实际应用示例
const validationRules = {
    email: (value) => {
        if (!value) return '邮箱不能为空';
        if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
            return '邮箱格式不正确';
        }
        return null;
    },
    password: (value) => {
        if (!value) return '密码不能为空';
        if (value.length < 8) return '密码至少8位';
        return null;
    }
};

const validateUser = createValidator(validationRules);

// 使用函数组合处理数据
const processUserInput = pipe(
    (data) => ({ ...data, email: data.email.trim().toLowerCase() }),
    withLogging(validateUser),
    (errors) => Object.keys(errors).length === 0 ? '验证通过' : errors
);

// 测试
const userInput = {
    email: '  TEST@Example.COM  ',
    password: '12345678'
};

console.log(processUserInput(userInput));

三、前端框架深度实践

3.1 React Hooks与状态管理

React Hooks彻底改变了我们编写React组件的方式,让函数组件具有了类组件的能力:

jsx

复制代码
import React, { useState, useEffect, useReducer, useCallback, useMemo, useRef } from 'react';
import { createPortal } from 'react-dom';

// 自定义Hook - 数据获取
const useApi = (url, options = {}) => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    const fetchData = useCallback(async () => {
        try {
            setLoading(true);
            setError(null);
            
            const response = await fetch(url, options);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            
            const result = await response.json();
            setData(result);
        } catch (err) {
            setError(err.message);
        } finally {
            setLoading(false);
        }
    }, [url, JSON.stringify(options)]);

    useEffect(() => {
        fetchData();
    }, [fetchData]);

    return { data, loading, error, refetch: fetchData };
};

// 自定义Hook - 本地存储
const useLocalStorage = (key, initialValue) => {
    const [storedValue, setStoredValue] = useState(() => {
        try {
            const item = window.localStorage.getItem(key);
            return item ? JSON.parse(item) : initialValue;
        } catch (error) {
            console.error(`Error reading localStorage key "${key}":`, error);
            return initialValue;
        }
    });

    const setValue = useCallback((value) => {
        try {
            const valueToStore = value instanceof Function ? value(storedValue) : value;
            setStoredValue(valueToStore);
            window.localStorage.setItem(key, JSON.stringify(valueToStore));
        } catch (error) {
            console.error(`Error setting localStorage key "${key}":`, error);
        }
    }, [key, storedValue]);

    return [storedValue, setValue];
};

// Reducer for complex state
const todoReducer = (state, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return {
                ...state,
                todos: [...state.todos, {
                    id: Date.now(),
                    text: action.payload,
                    completed: false,
                    createdAt: new Date().toISOString()
                }]
            };
        
        case 'TOGGLE_TODO':
            return {
                ...state,
                todos: state.todos.map(todo =>
                    todo.id === action.payload
                        ? { ...todo, completed: !todo.completed }
                        : todo
                )
            };
        
        case 'DELETE_TODO':
            return {
                ...state,
                todos: state.todos.filter(todo => todo.id !== action.payload)
            };
        
        case 'SET_FILTER':
            return {
                ...state,
                filter: action.payload
            };
        
        default:
            return state;
    }
};

// 主组件
const TodoApp = () => {
    const [state, dispatch] = useReducer(todoReducer, {
        todos: [],
        filter: 'all'
    });
    
    const [theme, setTheme] = useLocalStorage('theme', 'light');
    const inputRef = useRef(null);

    // 使用自定义Hook
    const { data: userData, loading: userLoading } = useApi('/api/user');

    // 计算属性
    const filteredTodos = useMemo(() => {
        switch (state.filter) {
            case 'active':
                return state.todos.filter(todo => !todo.completed);
            case 'completed':
                return state.todos.filter(todo => todo.completed);
            default:
                return state.todos;
        }
    }, [state.todos, state.filter]);

    const todoStats = useMemo(() => {
        const total = state.todos.length;
        const completed = state.todos.filter(todo => todo.completed).length;
        const active = total - completed;
        
        return { total, completed, active };
    }, [state.todos]);

    // 事件处理
    const handleAddTodo = useCallback((text) => {
        if (text.trim()) {
            dispatch({ type: 'ADD_TODO', payload: text.trim() });
        }
    }, []);

    const handleToggleTodo = useCallback((id) => {
        dispatch({ type: 'TOGGLE_TODO', payload: id });
    }, []);

    const handleDeleteTodo = useCallback((id) => {
        dispatch({ type: 'DELETE_TODO', payload: id });
    }, []);

    const handleSubmit = (e) => {
        e.preventDefault();
        const formData = new FormData(e.target);
        const text = formData.get('todoText');
        handleAddTodo(text);
        e.target.reset();
        inputRef.current?.focus();
    };

    // 切换主题
    const toggleTheme = useCallback(() => {
        setTheme(prev => prev === 'light' ? 'dark' : 'light');
    }, [setTheme]);

    // 效果
    useEffect(() => {
        document.documentElement.setAttribute('data-theme', theme);
    }, [theme]);

    if (userLoading) {
        return <div className="loading">加载用户数据...</div>;
    }

    return (
        <div className={`todo-app ${theme}`}>
            <header className="app-header">
                <h1>待办事项</h1>
                <button onClick={toggleTheme} className="theme-toggle">
                    切换到{theme === 'light' ? '暗色' : '亮色'}主题
                </button>
            </header>

            <TodoStats stats={todoStats} />
            
            <TodoForm 
                onSubmit={handleSubmit}
                inputRef={inputRef}
            />
            
            <TodoFilter
                currentFilter={state.filter}
                onFilterChange={(filter) => dispatch({ type: 'SET_FILTER', payload: filter })}
            />
            
            <TodoList
                todos={filteredTodos}
                onToggleTodo={handleToggleTodo}
                onDeleteTodo={handleDeleteTodo}
            />

            {/* Portal示例 */}
            <TodoModal />
        </div>
    );
};

// 子组件
const TodoStats = React.memo(({ stats }) => (
    <div className="todo-stats">
        <div className="stat">
            <span className="stat-label">总计</span>
            <span className="stat-value">{stats.total}</span>
        </div>
        <div className="stat">
            <span className="stat-label">进行中</span>
            <span className="stat-value">{stats.active}</span>
        </div>
        <div className="stat">
            <span className="stat-label">已完成</span>
            <span className="stat-value">{stats.completed}</span>
        </div>
    </div>
));

const TodoForm = React.memo(({ onSubmit, inputRef }) => (
    <form onSubmit={onSubmit} className="todo-form">
        <input
            ref={inputRef}
            type="text"
            name="todoText"
            placeholder="添加新的待办事项..."
            className="todo-input"
            required
            minLength={1}
        />
        <button type="submit" className="add-button">
            添加
        </button>
    </form>
));

const TodoFilter = React.memo(({ currentFilter, onFilterChange }) => (
    <div className="todo-filter">
        {['all', 'active', 'completed'].map(filter => (
            <button
                key={filter}
                className={`filter-button ${currentFilter === filter ? 'active' : ''}`}
                onClick={() => onFilterChange(filter)}
            >
                {filter === 'all' ? '全部' : filter === 'active' ? '进行中' : '已完成'}
            </button>
        ))}
    </div>
));

const TodoList = React.memo(({ todos, onToggleTodo, onDeleteTodo }) => (
    <div className="todo-list">
        {todos.map(todo => (
            <TodoItem
                key={todo.id}
                todo={todo}
                onToggle={onToggleTodo}
                onDelete={onDeleteTodo}
            />
        ))}
    </div>
));

const TodoItem = React.memo(({ todo, onToggle, onDelete }) => (
    <div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
        <input
            type="checkbox"
            checked={todo.completed}
            onChange={() => onToggle(todo.id)}
            className="todo-checkbox"
        />
        <span className="todo-text">{todo.text}</span>
        <button
            onClick={() => onDelete(todo.id)}
            className="delete-button"
            aria-label="删除"
        >
            ×
        </button>
    </div>
));

// Portal组件
const TodoModal = () => {
    const [isOpen, setIsOpen] = useState(false);
    
    if (!isOpen) {
        return (
            <button onClick={() => setIsOpen(true)} className="help-button">
                需要帮助?
            </button>
        );
    }

    return createPortal(
        <div className="modal-overlay">
            <div className="modal-content">
                <h2>使用帮助</h2>
                <p>这里是待办事项应用的使用说明...</p>
                <button onClick={() => setIsOpen(false)} className="close-button">
                    关闭
                </button>
            </div>
        </div>,
        document.body
    );
};

export default TodoApp;
3.2 Vue 3组合式API

Vue 3的组合式API提供了更灵活的逻辑组织和复用方式:

vue

复制代码
<template>
    <div :class="['user-dashboard', theme]">
        <header class="dashboard-header">
            <h1>用户仪表板</h1>
            <div class="controls">
                <button @click="toggleTheme" class="theme-btn">
                    {{ theme === 'light' ? '暗色' : '亮色' }}模式
                </button>
                <button @click="exportData" class="export-btn">
                    导出数据
                </button>
            </div>
        </header>

        <div class="dashboard-content">
            <!-- 用户信息卡片 -->
            <UserCard
                :user="userData"
                :loading="userLoading"
                @update-user="handleUserUpdate"
            />

            <!-- 数据统计 -->
            <div class="stats-grid">
                <StatCard
                    v-for="stat in statistics"
                    :key="stat.title"
                    :title="stat.title"
                    :value="stat.value"
                    :trend="stat.trend"
                    :icon="stat.icon"
                />
            </div>

            <!-- 活动列表 -->
            <ActivityList
                :activities="activities"
                :loading="activitiesLoading"
                @load-more="loadMoreActivities"
            />

            <!-- 设置面板 -->
            <SettingsPanel
                v-model:visible="showSettings"
                :settings="userSettings"
                @save-settings="saveSettings"
            />
        </div>
    </div>
</template>

<script>
import { ref, reactive, computed, watch, onMounted, onUnmounted, provide } from 'vue';
import { useStorage, useFetch, useEventListener } from '@vueuse/core';

// 组合式函数 - 用户管理
const useUserManager = () => {
    const user = ref(null);
    const loading = ref(false);
    const error = ref(null);

    const fetchUser = async (userId) => {
        loading.value = true;
        error.value = null;
        
        try {
            const response = await fetch(`/api/users/${userId}`);
            if (!response.ok) throw new Error('用户数据获取失败');
            user.value = await response.json();
        } catch (err) {
            error.value = err.message;
        } finally {
            loading.value = false;
        }
    };

    const updateUser = async (updates) => {
        try {
            const response = await fetch(`/api/users/${user.value.id}`, {
                method: 'PATCH',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(updates)
            });
            
            if (!response.ok) throw new Error('更新失败');
            user.value = { ...user.value, ...updates };
        } catch (err) {
            throw err;
        }
    };

    return {
        user,
        loading,
        error,
        fetchUser,
        updateUser
    };
};

// 组合式函数 - 主题管理
const useTheme = () => {
    const theme = useStorage('theme', 'light');
    
    const toggleTheme = () => {
        theme.value = theme.value === 'light' ? 'dark' : 'light';
    };

    watch(theme, (newTheme) => {
        document.documentElement.setAttribute('data-theme', newTheme);
    }, { immediate: true });

    return { theme, toggleTheme };
};

export default {
    name: 'UserDashboard',
    
    setup() {
        // 状态管理
        const { theme, toggleTheme } = useTheme();
        const showSettings = ref(false);
        const activitiesPage = ref(1);
        
        // 用户数据
        const { user: userData, loading: userLoading, updateUser } = useUserManager();
        
        // 活动数据
        const { data: activities, loading: activitiesLoading, execute: fetchActivities } = useFetch(
            () => `/api/activities?page=${activitiesPage.value}`,
            { immediate: false }
        ).json();

        // 用户设置
        const userSettings = useStorage('user-settings', {
            notifications: true,
            emailUpdates: false,
            language: 'zh-CN',
            timezone: 'Asia/Shanghai'
        });

        // 计算属性
        const statistics = computed(() => [
            {
                title: '总访问量',
                value: '1,234',
                trend: 12.5,
                icon: '👥'
            },
            {
                title: '完成率',
                value: '89%',
                trend: 5.2,
                icon: '✅'
            },
            {
                title: '活跃用户',
                value: '567',
                trend: -2.1,
                icon: '🔥'
            },
            {
                title: '平均时长',
                value: '12:34',
                trend: 8.7,
                icon: '⏱️'
            }
        ]);

        // 方法
        const handleUserUpdate = async (updates) => {
            try {
                await updateUser(updates);
                // 可以添加成功提示
            } catch (error) {
                // 处理错误
                console.error('用户更新失败:', error);
            }
        };

        const loadMoreActivities = () => {
            activitiesPage.value++;
            fetchActivities();
        };

        const saveSettings = (newSettings) => {
            userSettings.value = { ...userSettings.value, ...newSettings };
            showSettings.value = false;
        };

        const exportData = () => {
            const data = {
                user: userData.value,
                settings: userSettings.value,
                exportTime: new Date().toISOString()
            };
            
            const blob = new Blob([JSON.stringify(data, null, 2)], {
                type: 'application/json'
            });
            
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `user-data-${Date.now()}.json`;
            a.click();
            URL.revokeObjectURL(url);
        };

        // 生命周期
        onMounted(() => {
            // 初始化数据加载
            fetchUser(1);
            fetchActivities();
        });

        // 事件监听
        useEventListener('keydown', (event) => {
            if (event.ctrlKey && event.key === 's') {
                event.preventDefault();
                exportData();
            }
        });

        // 提供全局状态
        provide('theme', theme);

        return {
            // 状态
            theme,
            userData,
            userLoading,
            activities,
            activitiesLoading,
            showSettings,
            userSettings,
            
            // 计算属性
            statistics,
            
            // 方法
            toggleTheme,
            handleUserUpdate,
            loadMoreActivities,
            saveSettings,
            exportData
        };
    }
};
</script>

<script>
// 子组件 - 用户卡片
const UserCard = {
    name: 'UserCard',
    
    props: {
        user: Object,
        loading: Boolean
    },
    
    emits: ['update-user'],
    
    setup(props, { emit }) {
        const editing = ref(false);
        const form = reactive({
            name: '',
            email: '',
            bio: ''
        });

        const startEditing = () => {
            if (props.user) {
                form.name = props.user.name || '';
                form.email = props.user.email || '';
                form.bio = props.user.bio || '';
                editing.value = true;
            }
        };

        const saveEdit = async () => {
            try {
                await emit('update-user', { ...form });
                editing.value = false;
            } catch (error) {
                // 错误处理
            }
        };

        const cancelEdit = () => {
            editing.value = false;
        };

        return {
            editing,
            form,
            startEditing,
            saveEdit,
            cancelEdit
        };
    },
    
    template: `
        <div class="user-card">
            <div v-if="loading" class="loading">加载中...</div>
            <div v-else-if="user" class="user-content">
                <div v-if="!editing" class="view-mode">
                    <img :src="user.avatar" :alt="user.name" class="user-avatar">
                    <div class="user-info">
                        <h2>{{ user.name }}</h2>
                        <p>{{ user.email }}</p>
                        <p v-if="user.bio" class="user-bio">{{ user.bio }}</p>
                    </div>
                    <button @click="startEditing" class="edit-btn">编辑</button>
                </div>
                
                <div v-else class="edit-mode">
                    <form @submit.prevent="saveEdit">
                        <div class="form-group">
                            <label>姓名:</label>
                            <input v-model="form.name" type="text" required>
                        </div>
                        <div class="form-group">
                            <label>邮箱:</label>
                            <input v-model="form.email" type="email" required>
                        </div>
                        <div class="form-group">
                            <label>简介:</label>
                            <textarea v-model="form.bio" rows="3"></textarea>
                        </div>
                        <div class="form-actions">
                            <button type="submit" class="save-btn">保存</button>
                            <button type="button" @click="cancelEdit" class="cancel-btn">取消</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    `
};

// 其他子组件...
const StatCard = {
    name: 'StatCard',
    props: ['title', 'value', 'trend', 'icon'],
    template: `
        <div class="stat-card">
            <div class="stat-icon">{{ icon }}</div>
            <div class="stat-content">
                <h3>{{ title }}</h3>
                <div class="stat-value">{{ value }}</div>
                <div :class="['stat-trend', trend >= 0 ? 'positive' : 'negative']">
                    {{ trend >= 0 ? '↗' : '↘' }} {{ Math.abs(trend) }}%
                </div>
            </div>
        </div>
    `
};

const ActivityList = {
    name: 'ActivityList',
    props: ['activities', 'loading'],
    emits: ['load-more'],
    setup(props, { emit }) {
        const loadMore = () => {
            emit('load-more');
        };

        return { loadMore };
    },
    template: `
        <div class="activity-list">
            <h2>最近活动</h2>
            <div v-if="loading" class="loading">加载中...</div>
            <div v-else>
                <div v-for="activity in activities" :key="activity.id" class="activity-item">
                    <div class="activity-icon">{{ activity.icon }}</div>
                    <div class="activity-content">
                        <p>{{ activity.description }}</p>
                        <small>{{ activity.time }}</small>
                    </div>
                </div>
                <button @click="loadMore" class="load-more-btn">加载更多</button>
            </div>
        </div>
    `
};

const SettingsPanel = {
    name: 'SettingsPanel',
    props: {
        visible: Boolean,
        settings: Object
    },
    emits: ['update:visible', 'save-settings'],
    setup(props, { emit }) {
        const localSettings = reactive({ ...props.settings });

        const save = () => {
            emit('save-settings', { ...localSettings });
        };

        const close = () => {
            emit('update:visible', false);
        };

        watch(() => props.settings, (newSettings) => {
            Object.assign(localSettings, newSettings);
        });

        return {
            localSettings,
            save,
            close
        };
    },
    template: `
        <div v-if="visible" class="settings-panel">
            <div class="settings-overlay" @click="close"></div>
            <div class="settings-content">
                <h2>设置</h2>
                <form @submit.prevent="save">
                    <div class="setting-group">
                        <label>
                            <input type="checkbox" v-model="localSettings.notifications">
                            启用通知
                        </label>
                    </div>
                    <div class="setting-group">
                        <label>
                            <input type="checkbox" v-model="localSettings.emailUpdates">
                            邮件更新
                        </label>
                    </div>
                    <div class="setting-group">
                        <label>语言:</label>
                        <select v-model="localSettings.language">
                            <option value="zh-CN">中文</option>
                            <option value="en-US">English</option>
                        </select>
                    </div>
                    <div class="setting-actions">
                        <button type="submit" class="save-btn">保存</button>
                        <button type="button" @click="close" class="cancel-btn">取消</button>
                    </div>
                </form>
            </div>
        </div>
    `
};
</script>

<style scoped>
.user-dashboard {
    min-height: 100vh;
    padding: 20px;
    transition: background-color 0.3s, color 0.3s;
}

.user-dashboard.light {
    background-color: #ffffff;
    color: #333333;
}

.user-dashboard.dark {
    background-color: #1a1a1a;
    color: #ffffff;
}

.dashboard-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 30px;
    padding-bottom: 20px;
    border-bottom: 1px solid #e0e0e0;
}

.dashboard-content {
    display: grid;
    gap: 20px;
}

.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 15px;
    margin-bottom: 20px;
}

/* 更多样式... */
</style>

四、前端工程化与架构

4.1 现代构建工具配置

现代前端项目离不开强大的构建工具。以下是Vite + TypeScript的配置示例:

javascript

复制代码
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';

export default defineConfig({
    plugins: [
        vue({
            template: {
                compilerOptions: {
                    // 配置Vue模板编译选项
                }
            }
        })
    ],
    
    resolve: {
        alias: {
            '@': resolve(__dirname, 'src'),
            '~': resolve(__dirname, 'node_modules')
        },
        extensions: ['.js', '.ts', '.jsx', '.tsx', '.vue', '.json']
    },
    
    server: {
        port: 3000,
        open: true,
        proxy: {
            '/api': {
                target: 'http://localhost:8080',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/, '')
            }
        }
    },
    
    build: {
        outDir: 'dist',
        sourcemap: true,
        rollupOptions: {
            output: {
                manualChunks: {
                    vendor: ['vue', 'vue-router', 'pinia'],
                    utils: ['lodash', 'dayjs', 'axios']
                },
                chunkFileNames: 'js/[name]-[hash].js',
                entryFileNames: 'js/[name]-[hash].js',
                assetFileNames: (assetInfo) => {
                    const extType = assetInfo.name.split('.')[1];
                    if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
                        return 'images/[name]-[hash][extname]';
                    }
                    return 'assets/[name]-[hash][extname]';
                }
            }
        }
    },
    
    css: {
        preprocessorOptions: {
            scss: {
                additionalData: `@import "@/styles/variables.scss";`
            }
        }
    },
    
    optimizeDeps: {
        include: ['lodash', 'dayjs']
    }
});

typescript

复制代码
// 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": "preserve",
        "strict": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noFallthroughCasesInSwitch": true,
        "baseUrl": ".",
        "paths": {
            "@/*": ["src/*"],
            "~/*": ["node_modules/*"]
        },
        "types": ["vite/client", "node"]
    },
    "include": [
        "src/**/*.ts",
        "src/**/*.d.ts",
        "src/**/*.tsx",
        "src/**/*.vue"
    ],
    "exclude": ["node_modules", "dist"]
}
4.2 状态管理架构

对于复杂应用,良好的状态管理架构至关重要:

typescript

复制代码
// store/types.ts
export interface User {
    id: number;
    name: string;
    email: string;
    avatar?: string;
    role: 'admin' | 'user' | 'guest';
    preferences: UserPreferences;
    createdAt: string;
    updatedAt: string;
}

export interface UserPreferences {
    theme: 'light' | 'dark';
    language: string;
    notifications: boolean;
    emailUpdates: boolean;
}

export interface AppState {
    user: User | null;
    loading: boolean;
    error: string | null;
    sidebar: {
        collapsed: boolean;
        visible: boolean;
    };
    modals: {
        [key: string]: boolean;
    };
}

export interface AuthState {
    token: string | null;
    isAuthenticated: boolean;
    refreshToken: string | null;
    expiresAt: number | null;
}

// store/auth.store.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { AuthState, User } from './types';

export const useAuthStore = defineStore('auth', () => {
    // State
    const token = ref<string | null>(localStorage.getItem('token'));
    const refreshToken = ref<string | null>(localStorage.getItem('refreshToken'));
    const expiresAt = ref<number | null>(
        localStorage.getItem('expiresAt') ? 
        Number(localStorage.getItem('expiresAt')) : null
    );
    const user = ref<User | null>(null);
    const loading = ref(false);

    // Getters
    const isAuthenticated = computed(() => {
        if (!token.value || !expiresAt.value) return false;
        return Date.now() < expiresAt.value;
    });

    const isAdmin = computed(() => user.value?.role === 'admin');

    // Actions
    const login = async (credentials: { email: string; password: string }) => {
        loading.value = true;
        
        try {
            const response = await fetch('/api/auth/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(credentials)
            });

            if (!response.ok) throw new Error('登录失败');

            const data = await response.json();
            
            // 更新状态
            token.value = data.token;
            refreshToken.value = data.refreshToken;
            expiresAt.value = Date.now() + (data.expiresIn * 1000);
            user.value = data.user;

            // 持久化
            localStorage.setItem('token', data.token);
            localStorage.setItem('refreshToken', data.refreshToken);
            localStorage.setItem('expiresAt', expiresAt.value.toString());

            return data;
        } catch (error) {
            logout();
            throw error;
        } finally {
            loading.value = false;
        }
    };

    const logout = () => {
        token.value = null;
        refreshToken.value = null;
        expiresAt.value = null;
        user.value = null;
        
        localStorage.removeItem('token');
        localStorage.removeItem('refreshToken');
        localStorage.removeItem('expiresAt');
    };

    const refreshAuth = async () => {
        if (!refreshToken.value) {
            throw new Error('没有刷新令牌');
        }

        try {
            const response = await fetch('/api/auth/refresh', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ refreshToken: refreshToken.value })
            });

            if (!response.ok) throw new Error('令牌刷新失败');

            const data = await response.json();
            
            token.value = data.token;
            expiresAt.value = Date.now() + (data.expiresIn * 1000);
            
            localStorage.setItem('token', data.token);
            localStorage.setItem('expiresAt', expiresAt.value.toString());

            return data;
        } catch (error) {
            logout();
            throw error;
        }
    };

    const fetchUser = async () => {
        if (!token.value) return;

        try {
            const response = await fetch('/api/user/me', {
                headers: {
                    'Authorization': `Bearer ${token.value}`
                }
            });

            if (!response.ok) throw new Error('用户信息获取失败');

            user.value = await response.json();
        } catch (error) {
            console.error('获取用户信息失败:', error);
            throw error;
        }
    };

    // 自动刷新令牌
    const setupTokenRefresh = () => {
        const checkTokenExpiry = () => {
            if (expiresAt.value && expiresAt.value - Date.now() < 5 * 60 * 1000) {
                refreshAuth().catch(console.error);
            }
        };

        const interval = setInterval(checkTokenExpiry, 60 * 1000);
        return () => clearInterval(interval);
    };

    return {
        // State
        token,
        refreshToken,
        expiresAt,
        user,
        loading,
        
        // Getters
        isAuthenticated,
        isAdmin,
        
        // Actions
        login,
        logout,
        refreshAuth,
        fetchUser,
        setupTokenRefresh
    };
});

// store/app.store.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useAppStore = defineStore('app', () => {
    // State
    const sidebarCollapsed = ref(false);
    const sidebarVisible = ref(true);
    const modals = ref<Record<string, boolean>>({});
    const loading = ref(false);
    const errors = ref<Record<string, string>>({});

    // Getters
    const hasErrors = computed(() => Object.keys(errors.value).length > 0);
    const visibleModals = computed(() => 
        Object.entries(modals.value)
            .filter(([_, visible]) => visible)
            .map(([name]) => name)
    );

    // Actions
    const toggleSidebar = () => {
        sidebarCollapsed.value = !sidebarCollapsed.value;
    };

    const showSidebar = () => {
        sidebarVisible.value = true;
    };

    const hideSidebar = () => {
        sidebarVisible.value = false;
    };

    const showModal = (modalName: string) => {
        modals.value[modalName] = true;
    };

    const hideModal = (modalName: string) => {
        modals.value[modalName] = false;
    };

    const setError = (key: string, message: string) => {
        errors.value[key] = message;
    };

    const clearError = (key: string) => {
        delete errors.value[key];
    };

    const clearAllErrors = () => {
        errors.value = {};
    };

    const setLoading = (isLoading: boolean) => {
        loading.value = isLoading;
    };

    return {
        // State
        sidebarCollapsed,
        sidebarVisible,
        modals,
        loading,
        errors,
        
        // Getters
        hasErrors,
        visibleModals,
        
        // Actions
        toggleSidebar,
        showSidebar,
        hideSidebar,
        showModal,
        hideModal,
        setError,
        clearError,
        clearAllErrors,
        setLoading
    };
});

// store/index.ts
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

export { pinia };
export * from './auth.store';
export * from './app.store';

五、性能优化与最佳实践

5.1 代码分割与懒加载

javascript

复制代码
// router/lazy-components.js
import { lazy } from 'react';

// 懒加载组件
export const LazyDashboard = lazy(() => 
    import('../components/Dashboard').then(module => ({
        default: module.Dashboard
    }))
);

export const LazyUserProfile = lazy(() => 
    import('../components/UserProfile').then(module => ({
        default: module.UserProfile
    }))
);

export const LazySettings = lazy(() =>
    import('../components/Settings').then(module => ({
        default: module.Settings
    }))
);

// 预加载策略
export const preloadComponent = (component) => {
    if (typeof component.preload === 'function') {
        return component.preload();
    }
    return component;
};

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import { createProgressGuard } from './guards/progress';

const routes = [
    {
        path: '/',
        name: 'Home',
        component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue'),
        meta: {
            requiresAuth: true,
            title: '首页'
        }
    },
    {
        path: '/dashboard',
        name: 'Dashboard',
        component: () => import(/* webpackChunkName: "dashboard" */ '../views/Dashboard.vue'),
        meta: {
            requiresAuth: true,
            title: '仪表板'
        }
    },
    {
        path: '/profile',
        name: 'Profile',
        component: () => import(/* webpackChunkName: "profile" */ '../views/Profile.vue'),
        meta: {
            requiresAuth: true,
            title: '个人资料'
        }
    },
    {
        path: '/settings',
        name: 'Settings',
        component: () => import(/* webpackChunkName: "settings" */ '../views/Settings.vue'),
        meta: {
            requiresAuth: true,
            title: '设置'
        }
    }
];

const router = createRouter({
    history: createWebHistory(),
    routes,
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
            return savedPosition;
        } else {
            return { top: 0 };
        }
    }
});

// 路由守卫
router.beforeEach(async (to, from, next) => {
    // 显示加载进度条
    createProgressGuard();
    
    // 验证认证状态
    const authStore = useAuthStore();
    
    if (to.meta.requiresAuth && !authStore.isAuthenticated) {
        next('/login');
        return;
    }
    
    // 设置页面标题
    if (to.meta.title) {
        document.title = `${to.meta.title} - 我的应用`;
    }
    
    next();
});

router.afterEach((to, from) => {
    // 隐藏加载进度条
    // 发送页面浏览统计
});

export default router;
5.2 性能监控与优化

javascript

复制代码
// utils/performance.js
class PerformanceMonitor {
    constructor() {
        this.metrics = new Map();
        this.observers = [];
    }

    // 监控关键性能指标
    startMonitoring() {
        this.monitorCoreWebVitals();
        this.monitorResourceTiming();
        this.monitorLongTasks();
    }

    monitorCoreWebVitals() {
        // 监控LCP (Largest Contentful Paint)
        const observer = new PerformanceObserver((list) => {
            const entries = list.getEntries();
            const lastEntry = entries[entries.length - 1];
            this.recordMetric('LCP', lastEntry.startTime);
        });
        observer.observe({ entryTypes: ['largest-contentful-paint'] });

        // 监控FID (First Input Delay)
        const fidObserver = new PerformanceObserver((list) => {
            const entries = list.getEntries();
            entries.forEach(entry => {
                const delay = entry.processingStart - entry.startTime;
                this.recordMetric('FID', delay);
            });
        });
        fidObserver.observe({ entryTypes: ['first-input'] });

        // 监控CLS (Cumulative Layout Shift)
        let clsValue = 0;
        const clsObserver = new PerformanceObserver((list) => {
            list.getEntries().forEach(entry => {
                if (!entry.hadRecentInput) {
                    clsValue += entry.value;
                    this.recordMetric('CLS', clsValue);
                }
            });
        });
        clsObserver.observe({ entryTypes: ['layout-shift'] });
    }

    monitorResourceTiming() {
        const resources = performance.getEntriesByType('resource');
        resources.forEach(resource => {
            this.recordMetric(
                `RESOURCE_${resource.name}`,
                resource.duration
            );
        });
    }

    monitorLongTasks() {
        const observer = new PerformanceObserver((list) => {
            list.getEntries().forEach(entry => {
                if (entry.duration > 50) {
                    this.recordMetric('LONG_TASK', entry.duration);
                    console.warn('长任务 detected:', entry);
                }
            });
        });
        observer.observe({ entryTypes: ['longtask'] });
    }

    recordMetric(name, value) {
        const timestamp = Date.now();
        this.metrics.set(`${name}_${timestamp}`, {
            name,
            value,
            timestamp
        });

        // 通知观察者
        this.observers.forEach(observer => {
            observer({ name, value, timestamp });
        });

        // 发送到监控服务
        this.reportToAnalytics(name, value);
    }

    reportToAnalytics(metricName, value) {
        // 发送到监控平台
        const data = {
            metric: metricName,
            value: value,
            url: window.location.href,
            userAgent: navigator.userAgent,
            timestamp: new Date().toISOString()
        };

        // 使用navigator.sendBeacon避免影响页面性能
        navigator.sendBeacon('/api/analytics/performance', JSON.stringify(data));
    }

    addObserver(observer) {
        this.observers.push(observer);
    }

    removeObserver(observer) {
        this.observers = this.observers.filter(obs => obs !== observer);
    }

    getMetrics() {
        return Array.from(this.metrics.values());
    }

    clearMetrics() {
        this.metrics.clear();
    }
}

// 创建全局性能监控实例
window.performanceMonitor = new PerformanceMonitor();

// utils/image-optimizer.js
class ImageOptimizer {
    constructor() {
        this.supportedFormats = this.checkWebPSupport();
    }

    async checkWebPSupport() {
        const webP = new Image();
        return new Promise(resolve => {
            webP.onload = webP.onerror = () => {
                resolve(webP.height === 2);
            };
            webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
        });
    }

    optimizeImage(src, options = {}) {
        const {
            width,
            height,
            quality = 80,
            format = 'auto'
        } = options;

        return new Promise((resolve, reject) => {
            const img = new Image();
            
            img.onload = () => {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');

                // 计算新尺寸
                let newWidth = width || img.width;
                let newHeight = height || img.height;

                if (width && !height) {
                    newHeight = (img.height * width) / img.width;
                } else if (height && !width) {
                    newWidth = (img.width * height) / img.height;
                }

                canvas.width = newWidth;
                canvas.height = newHeight;

                // 绘制优化后的图像
                ctx.drawImage(img, 0, 0, newWidth, newHeight);

                // 选择最佳格式
                let outputFormat = 'image/jpeg';
                if (format === 'auto' && this.supportedFormats) {
                    outputFormat = 'image/webp';
                } else if (format !== 'auto') {
                    outputFormat = `image/${format}`;
                }

                try {
                    const optimizedDataUrl = canvas.toDataURL(outputFormat, quality / 100);
                    resolve(optimizedDataUrl);
                } catch (error) {
                    reject(error);
                }
            };

            img.onerror = reject;
            img.src = src;
        });
    }

    createLazyLoader() {
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const img = entry.target;
                    this.loadImage(img);
                    observer.unobserve(img);
                }
            });
        });

        return observer;
    }

    loadImage(imgElement) {
        const src = imgElement.getAttribute('data-src');
        if (!src) return;

        // 添加加载状态
        imgElement.classList.add('loading');

        this.optimizeImage(src).then(optimizedSrc => {
            imgElement.src = optimizedSrc;
            imgElement.classList.remove('loading');
            imgElement.classList.add('loaded');
        }).catch(() => {
            // 如果优化失败,使用原图
            imgElement.src = src;
            imgElement.classList.remove('loading');
        });
    }
}

// 使用示例
const imageOptimizer = new ImageOptimizer();
const lazyLoader = imageOptimizer.createLazyLoader();

// 为所有懒加载图片设置观察者
document.querySelectorAll('img[data-src]').forEach(img => {
    lazyLoader.observe(img);
});

六、测试与质量保证

6.1 单元测试与集成测试

javascript

复制代码
// tests/unit/utils.spec.js
import { describe, it, expect, beforeEach } from 'vitest';
import { calculateTotal, createValidator, withLogging } from '../../src/utils/helpers';

describe('工具函数测试', () => {
    describe('calculateTotal', () => {
        it('应该正确计算商品总价', () => {
            const items = [
                { name: '商品1', price: 10 },
                { name: '商品2', price: 20 },
                { name: '商品3', price: 30 }
            ];
            
            const result = calculateTotal(items, 0.1);
            
            expect(result.subtotal).toBe(60);
            expect(result.tax).toBe(6);
            expect(result.total).toBe(66);
        });

        it('应该处理空数组', () => {
            const result = calculateTotal([], 0.1);
            
            expect(result.subtotal).toBe(0);
            expect(result.tax).toBe(0);
            expect(result.total).toBe(0);
        });
    });

    describe('createValidator', () => {
        const rules = {
            email: (value) => !value ? '邮箱必填' : null,
            password: (value) => !value ? '密码必填' : null
        };

        it('应该通过有效数据', () => {
            const validate = createValidator(rules);
            const data = { email: 'test@example.com', password: '123456' };
            
            const errors = validate(data);
            
            expect(errors).toEqual({});
        });

        it('应该返回错误信息', () => {
            const validate = createValidator(rules);
            const data = { email: '', password: '' };
            
            const errors = validate(data);
            
            expect(errors.email).toBe('邮箱必填');
            expect(errors.password).toBe('密码必填');
        });
    });
});

// tests/unit/components/UserCard.spec.js
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import UserCard from '../../../src/components/UserCard.vue';

describe('UserCard组件', () => {
    const user = {
        id: 1,
        name: '张三',
        email: 'zhangsan@example.com',
        avatar: '/avatar.jpg',
        bio: '前端开发者'
    };

    it('应该正确渲染用户信息', () => {
        const wrapper = mount(UserCard, {
            props: { user }
        });

        expect(wrapper.find('.user-name').text()).toBe('张三');
        expect(wrapper.find('.user-email').text()).toBe('zhangsan@example.com');
        expect(wrapper.find('.user-bio').text()).toBe('前端开发者');
    });

    it('点击编辑按钮应该进入编辑模式', async () => {
        const wrapper = mount(UserCard, {
            props: { user }
        });

        await wrapper.find('.edit-btn').trigger('click');

        expect(wrapper.find('.edit-mode').exists()).toBe(true);
        expect(wrapper.find('input[type="text"]').element.value).toBe('张三');
    });

    it('应该触发更新事件', async () => {
        const wrapper = mount(UserCard, {
            props: { user }
        });

        await wrapper.find('.edit-btn').trigger('click');
        await wrapper.find('input[type="text"]').setValue('李四');
        await wrapper.find('form').trigger('submit');

        expect(wrapper.emitted('update-user')).toBeTruthy();
        expect(wrapper.emitted('update-user')[0][0]).toEqual({
            name: '李四',
            email: 'zhangsan@example.com',
            bio: '前端开发者'
        });
    });
});

// tests/e2e/dashboard.spec.js
import { test, expect } from '@playwright/test';

test.describe('仪表板端到端测试', () => {
    test('应该成功加载仪表板', async ({ page }) => {
        // 登录
        await page.goto('/login');
        await page.fill('input[type="email"]', 'test@example.com');
        await page.fill('input[type="password"]', 'password');
        await page.click('button[type="submit"]');

        // 等待导航
        await page.waitForURL('/dashboard');

        // 验证页面内容
        await expect(page.locator('h1')).toContainText('仪表板');
        await expect(page.locator('.stat-card')).toHaveCount(4);

        // 测试交互
        await page.click('.sidebar-toggle');
        await expect(page.locator('.sidebar')).toHaveClass(/collapsed/);
    });

    test('应该显示错误状态', async ({ page }) => {
        await page.goto('/dashboard');

        // 模拟API错误
        await page.route('/api/stats', route => {
            route.fulfill({
                status: 500,
                body: JSON.stringify({ error: '服务器错误' })
            });
        });

        await expect(page.locator('.error-message')).toBeVisible();
    });
});

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

export default defineConfig({
    plugins: [vue()],
    test: {
        globals: true,
        environment: 'jsdom',
        coverage: {
            reporter: ['text', 'json', 'html'],
            exclude: [
                'node_modules/',
                'src/main.js',
            ],
        },
    },
});

七、部署与DevOps

7.1 Docker容器化部署

dockerfile

复制代码
# Dockerfile
# 多阶段构建
FROM node:18-alpine AS builder

# 安装依赖
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# 复制源代码
COPY . .
RUN npm run build

# 生产环境
FROM nginx:alpine

# 复制构建结果
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

# 复制SSL证书(如果有)
COPY ssl/ /etc/nginx/ssl/

# 暴露端口
EXPOSE 80 443

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
    CMD curl -f http://localhost/ || exit 1

# 启动nginx
CMD ["nginx", "-g", "daemon off;"]

nginx

复制代码
# nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/xml+rss
        application/json;

    # 安全头
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";

    # 上游服务
    upstream backend {
        server api:3000;
        keepalive 32;
    }

    server {
        listen 80;
        server_name _;
        return 301 https://$host$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name your-domain.com;

        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;
        
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers off;

        # 静态资源
        location / {
            root /usr/share/nginx/html;
            index index.html;
            try_files $uri $uri/ /index.html;
            
            # 缓存静态资源
            location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
                expires 1y;
                add_header Cache-Control "public, immutable";
            }
        }

        # API代理
        location /api/ {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
        }

        # 安全配置
        location ~ /\. {
            deny all;
            access_log off;
            log_not_found off;
        }
    }
}
7.2 CI/CD流水线配置

yaml

复制代码
# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  NODE_VERSION: '18'
  DOCKER_IMAGE: myapp/frontend

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run tests
      run: |
        npm run test:unit
        npm run test:e2e
    
    - name: Run linting
      run: npm run lint
    
    - name: Build application
      run: npm run build
    
    - name: Upload build artifacts
      uses: actions/upload-artifact@v3
      with:
        name: build-files
        path: dist/

  security-scan:
    runs-on: ubuntu-latest
    needs: test
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Run security audit
      run: |
        npm audit --audit-level moderate
        npx snyk test --all-projects
    
    - name: Dependency check
      uses: dependency-review-action@v3

  deploy-staging:
    runs-on: ubuntu-latest
    needs: [test, security-scan]
    environment: staging
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Download build artifacts
      uses: actions/download-artifact@v3
      with:
        name: build-files
        path: dist/
    
    - name: Build Docker image
      run: |
        docker build -t $DOCKER_IMAGE:${{ github.sha }} .
    
    - name: Push to registry
      run: |
        echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
        docker push $DOCKER_IMAGE:${{ github.sha }}
    
    - name: Deploy to staging
      run: |
        # 部署到暂存环境的脚本
        ./scripts/deploy-staging.sh ${{ github.sha }}

  deploy-production:
    runs-on: ubuntu-latest
    needs: deploy-staging
    environment: production
    if: github.ref == 'refs/heads/main'
    
    steps:
    - name: Deploy to production
      run: |
        # 部署到生产环境的脚本
        ./scripts/deploy-production.sh ${{ github.sha }}
    
    - name: Run smoke tests
      run: |
        # 生产环境冒烟测试
        ./scripts/smoke-test.sh
    
    - name: Notify success
      if: success()
      uses: 8398a7/action-slack@v3
      with:
        status: success
        channel: '#deployments'
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
    
    - name: Notify failure
      if: failure()
      uses: 8398a7/action-slack@v3
      with:
        status: failure
        channel: '#deployments'
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

总结

现代前端开发已经发展成为一个涵盖广泛技术和概念的复杂领域。从基础的HTML/CSS/JavaScript到复杂的架构设计、性能优化、测试和部署,前端开发者需要掌握全方位的技能。

通过本文的详细讲解和代码实例,我们深入探讨了:

  1. 现代前端基础:语义化HTML5、现代CSS架构、响应式设计

  2. JavaScript深度实践:ES6+特性、函数式编程、异步处理

  3. 前端框架应用:React Hooks、Vue 3组合式API、状态管理

  4. 工程化架构:构建工具配置、模块化设计、代码分割

  5. 性能优化:懒加载、代码分割、性能监控

  6. 质量保证:单元测试、集成测试、E2E测试

  7. 部署运维:Docker容器化、CI/CD流水线、监控告警

前端技术仍在快速演进,作为前端开发者,我们需要保持持续学习的态度,紧跟技术发展趋势,同时注重代码质量、用户体验和工程实践。只有这样,我们才能构建出高性能、可维护、用户体验优秀的现代Web应用。

相关推荐
比老马还六3 小时前
Blockly串口积木开发
前端
火星资讯3 小时前
Zenlayer 推出分布式推理平台,加速 AI 创新全球落地
人工智能·分布式·科技
小奋斗3 小时前
浏览器原理之详解渲染进程
前端·面试
伶俜monster4 小时前
搞定 Monorepo,工程能力升级,升职加薪快人一步
前端·架构
猪哥帅过吴彦祖4 小时前
Flutter 系列教程:常用基础组件 (下) - `TextField` 和 `Form`
前端·flutter·ios
Mintopia4 小时前
🌍 跨语言 AIGC:Web 国际化内容生成的多语种模型技术
前端·javascript·aigc
用户5191495848454 小时前
简单高效的SQL注入测试方法:Break & Repair技巧详解
人工智能·aigc
三十_4 小时前
前端发版自动化实战:打包时自动更新版本号并展示 commitId
前端·前端工程化
_AaronWong4 小时前
微信小程序同声传译插件接入实战:语音识别功能完整实现指南
前端·微信小程序·uni-app