前端工程化最佳实践
目录
前端工程化最佳实践
现代前端开发已经从简单的页面制作演进为复杂的工程化项目。本文将分享构建高效前端工程化体系的最佳实践。
项目架构设计
目录结构规范
src/
├── components/ # 通用组件
│ ├── ui/ # 基础 UI 组件
│ └── business/ # 业务组件
├── pages/ # 页面组件
├── hooks/ # 自定义 Hooks
├── utils/ # 工具函数
├── services/ # API 服务
├── stores/ # 状态管理
├── types/ # TypeScript 类型定义
├── constants/ # 常量定义
└── assets/ # 静态资源
组件设计原则
// 单一职责原则
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger';
size: 'small' | 'medium' | 'large';
disabled?: boolean;
loading?: boolean;
onClick?: () => void;
children: React.ReactNode;
}
const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'medium',
disabled = false,
loading = false,
onClick,
children
}) => {
return (
<button
className={`btn btn--${variant} btn--${size}`}
disabled={disabled || loading}
onClick={onClick}
>
{loading ? <Spinner /> : children}
</button>
);
};
代码质量保障
ESLint 配置
{
"extends": [
"@typescript-eslint/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-function-return-type": "warn",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
Prettier 配置
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2
}
Husky + lint-staged
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix",
"prettier --write"
]
}
}
构建优化
Webpack 配置优化
const path = require('path');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
// 代码分割
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
enforce: true,
},
},
},
},
// 别名配置
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
},
// 生产环境优化
plugins: [
process.env.ANALYZE && new BundleAnalyzerPlugin(),
].filter(Boolean),
};
Vite 配置
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
},
},
},
},
});
状态管理
Zustand 轻量级状态管理
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
interface UserState {
user: User | null;
isLoading: boolean;
login: (credentials: LoginCredentials) => Promise<void>;
logout: () => void;
}
const useUserStore = create<UserState>()(devtools((set, get) => ({
user: null,
isLoading: false,
login: async (credentials) => {
set({ isLoading: true });
try {
const user = await authService.login(credentials);
set({ user, isLoading: false });
} catch (error) {
set({ isLoading: false });
throw error;
}
},
logout: () => {
authService.logout();
set({ user: null });
},
})));
测试策略
单元测试
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button Component', () => {
it('renders correctly', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('shows loading state', () => {
render(<Button loading>Click me</Button>);
expect(screen.getByTestId('spinner')).toBeInTheDocument();
});
});
E2E 测试
import { test, expect } from '@playwright/test';
test('user login flow', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'password123');
await page.click('[data-testid="login-button"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('[data-testid="welcome-message"]')).toBeVisible();
});
CI/CD 流程
GitHub Actions 配置
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run test
- run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- run: npm ci
- run: npm run build
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
性能监控
Web Vitals 监控
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
function sendToAnalytics(metric: any) {
// 发送到分析服务
console.log(metric);
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
最佳实践总结
- 标准化工具链:统一代码格式化、检查和构建工具
- 组件化开发:遵循单一职责原则,提高复用性
- 类型安全:使用 TypeScript 提升代码质量
- 自动化测试:建立完善的测试体系
- 持续集成:自动化构建、测试和部署流程
- 性能监控:持续关注应用性能指标
前端工程化是一个持续演进的过程,需要根据项目规模和团队情况选择合适的工具和实践。