前端开发已经从一个简单的页面美化角色,演变为构建复杂用户界面的关键技术。随着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>© 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到复杂的架构设计、性能优化、测试和部署,前端开发者需要掌握全方位的技能。
通过本文的详细讲解和代码实例,我们深入探讨了:
-
现代前端基础:语义化HTML5、现代CSS架构、响应式设计
-
JavaScript深度实践:ES6+特性、函数式编程、异步处理
-
前端框架应用:React Hooks、Vue 3组合式API、状态管理
-
工程化架构:构建工具配置、模块化设计、代码分割
-
性能优化:懒加载、代码分割、性能监控
-
质量保证:单元测试、集成测试、E2E测试
-
部署运维:Docker容器化、CI/CD流水线、监控告警
前端技术仍在快速演进,作为前端开发者,我们需要保持持续学习的态度,紧跟技术发展趋势,同时注重代码质量、用户体验和工程实践。只有这样,我们才能构建出高性能、可维护、用户体验优秀的现代Web应用。