install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/aaaaqwq/claude-code-skills/frontend-development" ~/.claude/skills/neversight-learn-skills-dev-frontend-development-276ac3 && rm -rf "$T"
manifest:
data/skills-md/aaaaqwq/claude-code-skills/frontend-development/SKILL.mdsource content
前端页面开发
功能说明
此技能专门用于前端 Web 开发,包括:
- 现代前端框架开发(React、Vue、Angular)
- 响应式页面布局
- 交互功能实现
- 状态管理
- 性能优化
- 前端工程化
使用场景
- "创建一个 React 登录页面"
- "实现一个响应式导航栏"
- "优化页面加载性能"
- "集成第三方 API"
- "实现深色模式切换"
- "创建可复用的 UI 组件库"
技术栈
核心框架
- React:组件化、Hooks、Context
- Vue:响应式、组合式 API、Pinia
- Angular:TypeScript、依赖注入、RxJS
- Next.js:SSR、SSG、API Routes
- Nuxt.js:Vue SSR 框架
UI 框架
- Tailwind CSS:实用优先的 CSS 框架
- Material-UI:React Material Design
- Ant Design:企业级 UI 组件库
- Element Plus:Vue 3 组件库
- Chakra UI:可访问的组件系统
状态管理
- Redux:可预测的状态容器
- Zustand:轻量级状态管理
- Pinia:Vue 状态管理
- MobX:响应式状态管理
- Jotai:原子化状态管理
构建工具
- Vite:下一代前端构建工具
- Webpack:模块打包器
- Turbopack:Rust 驱动的打包器
- esbuild:极速 JavaScript 打包器
开发工作流程
项目初始化
- 创建项目:使用脚手架工具
- 配置环境:ESLint、Prettier、TypeScript
- 目录结构:组织代码文件
- 依赖安装:安装必要的包
- Git 初始化:版本控制设置
开发流程
- 需求分析:理解功能需求
- 组件设计:拆分组件结构
- 样式开发:实现 UI 设计
- 功能实现:编写业务逻辑
- 测试验证:单元测试和集成测试
- 代码审查:团队 Code Review
- 部署上线:构建和发布
最佳实践
代码组织
src/ ├── components/ # 可复用组件 │ ├── Button/ │ ├── Input/ │ └── Modal/ ├── pages/ # 页面组件 │ ├── Home/ │ ├── Login/ │ └── Dashboard/ ├── hooks/ # 自定义 Hooks ├── utils/ # 工具函数 ├── services/ # API 服务 ├── store/ # 状态管理 ├── styles/ # 全局样式 ├── types/ # TypeScript 类型 └── constants/ # 常量定义
组件设计原则
- 单一职责:一个组件只做一件事
- 可复用性:设计通用的组件
- 可组合性:小组件组合成大组件
- Props 验证:使用 TypeScript 或 PropTypes
- 默认值:提供合理的默认属性
性能优化
- 代码分割:动态导入和懒加载
- 虚拟滚动:处理大列表
- 防抖节流:优化事件处理
- Memo 化:避免不必要的重渲染
- 图片优化:懒加载、WebP 格式
- 缓存策略:合理使用缓存
可访问性
- 语义化 HTML:使用正确的标签
- 键盘导航:支持键盘操作
- ARIA 属性:辅助技术支持
- 对比度:确保文字可读性
- 焦点管理:清晰的焦点指示
代码示例
React 组件示例
import React, { useState } from 'react'; import styled from 'styled-components'; interface ButtonProps { variant?: 'primary' | 'secondary'; size?: 'small' | 'medium' | 'large'; disabled?: boolean; onClick?: () => void; children: React.ReactNode; } const StyledButton = styled.button<ButtonProps>` padding: ${props => { switch (props.size) { case 'small': return '8px 16px'; case 'large': return '16px 32px'; default: return '12px 24px'; } }}; background: ${props => props.variant === 'primary' ? '#2196F3' : '#757575' }; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; transition: all 0.2s; &:hover:not(:disabled) { opacity: 0.9; transform: translateY(-2px); } &:disabled { opacity: 0.5; cursor: not-allowed; } `; export const Button: React.FC<ButtonProps> = ({ variant = 'primary', size = 'medium', disabled = false, onClick, children }) => { return ( <StyledButton variant={variant} size={size} disabled={disabled} onClick={onClick} > {children} </StyledButton> ); };
Vue 组件示例
<template> <button :class="buttonClasses" :disabled="disabled" @click="handleClick" > <slot /> </button> </template> <script setup lang="ts"> import { computed } from 'vue'; interface Props { variant?: 'primary' | 'secondary'; size?: 'small' | 'medium' | 'large'; disabled?: boolean; } const props = withDefaults(defineProps<Props>(), { variant: 'primary', size: 'medium', disabled: false }); const emit = defineEmits<{ click: []; }>(); const buttonClasses = computed(() => [ 'btn', `btn-${props.variant}`, `btn-${props.size}`, { 'btn-disabled': props.disabled } ]); const handleClick = () => { if (!props.disabled) { emit('click'); } }; </script> <style scoped> .btn { padding: 12px 24px; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; transition: all 0.2s; } .btn-primary { background: #2196F3; color: white; } .btn-secondary { background: #757575; color: white; } .btn-small { padding: 8px 16px; font-size: 14px; } .btn-large { padding: 16px 32px; font-size: 18px; } .btn:hover:not(.btn-disabled) { opacity: 0.9; transform: translateY(-2px); } .btn-disabled { opacity: 0.5; cursor: not-allowed; } </style>
自定义 Hook 示例
import { useState, useEffect } from 'react'; interface FetchState<T> { data: T | null; loading: boolean; error: Error | null; } export function useFetch<T>(url: string): FetchState<T> { const [state, setState] = useState<FetchState<T>>({ data: null, loading: true, error: null }); useEffect(() => { let cancelled = false; const fetchData = async () => { try { setState(prev => ({ ...prev, loading: true })); const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (!cancelled) { setState({ data, loading: false, error: null }); } } catch (error) { if (!cancelled) { setState({ data: null, loading: false, error: error as Error }); } } }; fetchData(); return () => { cancelled = true; }; }, [url]); return state; }
状态管理示例(Zustand)
import create from 'zustand'; import { persist } from 'zustand/middleware'; interface User { id: string; name: string; email: string; } interface AuthState { user: User | null; token: string | null; isAuthenticated: boolean; login: (user: User, token: string) => void; logout: () => void; } export const useAuthStore = create<AuthState>()( persist( (set) => ({ user: null, token: null, isAuthenticated: false, login: (user, token) => set({ user, token, isAuthenticated: true }), logout: () => set({ user: null, token: null, isAuthenticated: false }) }), { name: 'auth-storage' } ) );
响应式设计
断点系统
/* Mobile First */ /* Mobile: 默认样式 */ .container { padding: 16px; } /* Tablet: >= 768px */ @media (min-width: 768px) { .container { padding: 24px; } } /* Desktop: >= 1024px */ @media (min-width: 1024px) { .container { padding: 32px; max-width: 1200px; margin: 0 auto; } } /* Large Desktop: >= 1440px */ @media (min-width: 1440px) { .container { max-width: 1400px; } }
Flexbox 布局
.flex-container { display: flex; flex-direction: row; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; } @media (max-width: 768px) { .flex-container { flex-direction: column; } }
Grid 布局
.grid-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 24px; padding: 24px; }
测试策略
单元测试(Jest + React Testing Library)
import { render, screen, fireEvent } from '@testing-library/react'; import { Button } from './Button'; describe('Button', () => { 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('is disabled when disabled prop is true', () => { render(<Button disabled>Click me</Button>); expect(screen.getByText('Click me')).toBeDisabled(); }); });
E2E 测试(Playwright)
import { test, expect } from '@playwright/test'; test('user can login', async ({ page }) => { await page.goto('http://localhost:3000/login'); await page.fill('input[name="email"]', 'user@example.com'); await page.fill('input[name="password"]', 'password123'); await page.click('button[type="submit"]'); await expect(page).toHaveURL('http://localhost:3000/dashboard'); await expect(page.locator('h1')).toContainText('Dashboard'); });
部署和优化
构建优化
// vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], build: { rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], ui: ['@mui/material'] } } }, minify: 'terser', terserOptions: { compress: { drop_console: true } } } });
环境变量
# .env.production VITE_API_URL=https://api.production.com VITE_APP_NAME=My App
注意事项
- 遵循团队代码规范
- 编写清晰的注释和文档
- 考虑浏览器兼容性
- 注意安全问题(XSS、CSRF)
- 优化首屏加载时间
- 实现错误边界和降级方案
- 使用 TypeScript 提高代码质量