服务端面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 02月17日 23:35

Linux 系统监控和告警的常用工具、配置方法和最佳实践有哪些?

Linux 系统监控和告警是保障系统稳定运行的重要手段,需要掌握各种监控工具和告警机制。系统监控工具:CPU 监控:top:实时查看 CPU 使用率和进程信息htop:交互式进程查看器,功能更强大mpstat:显示各个 CPU 核心的使用情况sar:系统活动报告,可记录历史数据vmstat:报告虚拟内存统计信息内存监控:free:显示内存使用情况vmstat:查看内存交换、缓存等信息ps aux:查看进程的内存占用pmap:查看进程的内存映射磁盘监控:df:查看磁盘空间使用情况du:查看目录或文件的大小iostat:查看磁盘 I/O 统计信息iotop:实时查看磁盘 I/O 使用情况网络监控:ifconfig/ip:查看网络接口配置netstat/ss:查看网络连接和端口监听nethogs:按进程查看网络带宽使用tcpdump:抓包分析网络流量iftop:实时显示网络带宽使用进程监控:ps:查看进程状态top/htop:实时监控进程pgrep:查找进程 IDpidstat:监控进程资源使用性能分析工具:strace:跟踪系统调用和信号ltrace:跟踪库函数调用perf:性能分析工具sysdig:系统级监控和故障排查eBPF:扩展伯克利数据包过滤器日志监控:/var/log/messages:系统主日志/var/log/syslog:系统日志(Debian/Ubuntu)/var/log/auth.log:认证日志/var/log/secure:安全日志(CentOS/RHEL)journalctl:systemd 日志查看工具logrotate:日志轮转工具监控告警系统:Nagios:企业级监控系统Zabbix:分布式监控系统Prometheus:时序数据库和监控系统Grafana:数据可视化平台ELK Stack(Elasticsearch、Logstash、Kibana):日志分析和可视化Datadog:云监控平台New Relic:应用性能监控Prometheus 监控:数据采集:使用 Exporter 采集指标常用 Exporter:node_exporter:系统指标mysqld_exporter:MySQL 指标nginx_exporter:Nginx 指标redis_exporter:Redis 指标配置文件:/etc/prometheus/prometheus.yml告警规则:使用 PromQL 定义告警条件告警管理:Alertmanager 处理告警通知Grafana 可视化:数据源配置:支持 Prometheus、Elasticsearch 等仪表板:创建自定义监控面板告警:基于可视化图表设置告警模板:使用变量创建动态仪表板告警通知方式:邮件:SMTP 邮件通知短信:短信网关即时通讯:Slack、钉钉、企业微信电话:语音通知Webhook:自定义 Web 回调告警策略:告警级别:Critical、Warning、Info告警阈值:根据业务需求设置合理阈值告警抑制:避免告警风暴告警聚合:相关告警合并通知告警升级:长时间未处理自动升级自定义监控脚本:编写 Shell/Python 脚本采集指标使用 cron 定时执行输出格式:支持 Nagios、Prometheus 等格式示例: #!/bin/bash # 检查磁盘使用率 DISK_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') if [ $DISK_USAGE -gt 80 ]; then echo "CRITICAL: Disk usage is ${DISK_USAGE}%" exit 2 fi echo "OK: Disk usage is ${DISK_USAGE}%" exit 0监控最佳实践:全面监控:覆盖 CPU、内存、磁盘、网络等关键指标合理采样:避免监控数据量过大告警分级:区分紧急和一般告警告警收敛:避免重复告警定期维护:清理过期数据,更新监控规则文档记录:维护监控配置文档测试验证:定期测试告警机制常见监控指标:系统指标:CPU 使用率、内存使用率、磁盘使用率、网络流量应用指标:请求数、响应时间、错误率、并发数业务指标:订单量、用户数、交易额故障排查流程:确认告警信息查看系统监控数据检查相关服务状态分析日志文件定位问题根因实施修复措施验证修复效果总结经验教训
服务端阅读 02月17日 23:35

Linux 网络配置和管理中常用的命令有哪些,如何配置网络接口、DNS 和防火墙?

Linux 网络配置和管理是系统管理员的核心技能之一。网络配置文件:/etc/network/interfaces(Debian/Ubuntu):网络接口配置文件/etc/sysconfig/network-scripts/ifcfg-*(CentOS/RHEL):网络接口配置文件/etc/resolv.conf:DNS 解析配置文件/etc/hosts:主机名到 IP 地址的映射/etc/hostname:主机名配置网络配置命令:ifconfig:配置和显示网络接口(已废弃,推荐使用 ip 命令)ip:新一代网络配置工具,如 ip addr show(显示 IP 地址)、ip link show(显示网络接口)、ip route show(显示路由表)route:显示和操作 IP 路由表,如 route -n(显示路由表)、route add default gw 192.168.1.1(添加默认网关)netstat:网络统计工具,如 netstat -tulnp(显示 TCP/UDP 监听端口)、netstat -an(显示所有连接)ss:netstat 的替代工具,性能更好,如 ss -tulnpping:测试网络连通性,如 ping -c 4 google.comtraceroute:跟踪数据包路由,如 traceroute google.comnslookup/dig:DNS 查询工具,如 dig google.comcurl/wget:下载工具,curl -I http://example.com(查看响应头)telnet:测试端口连通性,如 telnet host portnc(netcat):网络工具,如 nc -zv host port(测试端口)网络服务管理:systemctl:管理网络服务,如 systemctl restart network(重启网络服务)service:传统服务管理命令,如 service network restart防火墙配置:iptables:传统的防火墙工具,如 iptables -L(列出规则)、iptables -A INPUT -p tcp --dport 80 -j ACCEPT(添加规则)firewalld:动态防火墙管理守护进程,如 firewall-cmd --list-all(查看规则)、firewall-cmd --add-port=80/tcp(开放端口)ufw:简化防火墙配置工具(Ubuntu),如 ufw allow 80(开放端口)网络故障排查:检查网络接口:ip addr show 或 ifconfig检查路由表:ip route show 或 route -n检查 DNS:cat /etc/resolv.conf、dig domain.com检查端口监听:ss -tulnp 或 netstat -tulnp检查网络连通性:ping、traceroute抓包分析:tcpdump -i eth0 port 80查看网络统计:sar -n DEV 1网络性能优化:调整 TCP 参数:修改 /etc/sysctl.conf,如 net.ipv4.tcptwreuse=1增加连接数:net.core.somaxconn、net.ipv4.tcpmaxsyn_backlog优化网络缓冲区:net.core.rmemmax、net.core.wmemmax
服务端阅读 02月17日 23:34

Linux 进程管理中常用的命令有哪些,如何查看、终止和管理后台进程?

Linux 进程管理是系统运维和开发的核心技能。每个进程都有唯一的进程 ID(PID),父进程 ID(PPID),以及运行状态。常用进程管理命令:ps:查看当前进程状态,常用选项包括 ps aux(显示所有进程)、ps -ef(完整格式显示)、ps -l(长格式显示)top/htop:实时监控系统进程和资源使用情况,htop 提供更友好的交互界面kill:终止进程,如 kill PID(发送 SIGTERM 信号)、kill -9 PID(强制终止,发送 SIGKILL 信号)killall:按进程名终止进程,如 killall nginxpkill:按模式匹配终止进程,如 pkill -f "python script.py"pgrep:查找进程 ID,如 pgrep nginxnohup:让命令在后台持续运行,如 nohup command &&:在后台运行命令,如 command &jobs:查看当前 shell 的后台任务bg/fg:将任务切换到后台/前台运行进程状态包括:R(Running):正在运行或在运行队列中等待S(Sleeping):可中断睡眠D(Uninterruptible Sleep):不可中断睡眠T(Stopped):已停止Z(Zombie):僵尸进程僵尸进程是已完成但父进程尚未读取其退出状态的进程,需要父进程调用 wait() 或 waitpid() 来回收。孤儿进程是父进程已终止但仍在运行的进程,会被 init 进程(PID 1)收养。进程间通信(IPC)方式包括管道、消息队列、共享内存、信号量、套接字等。
服务端阅读 02月17日 23:31

Next.js 的 API Routes 是如何工作的?

Next.js 提供了强大的 API Routes 功能,允许开发者创建 API 端点来处理服务器端逻辑。API Routes 可以处理数据库查询、身份验证、表单提交等服务器端操作。API Routes 基础创建 API Route在 pages/api 目录下创建文件,每个文件都会成为一个 API 端点。pages/ api/ hello.js → /api/hello users/ index.js → /api/users [id].js → /api/users/123基本示例// pages/api/hello.jsexport default function handler(req, res) { res.status(200).json({ message: 'Hello from Next.js API!' });}请求和响应对象请求对象 (req)req 对象包含以下属性:req.method:HTTP 方法(GET、POST、PUT、DELETE 等)req.query:查询参数req.body:请求体(POST、PUT 等)req.headers:请求头req.cookies:Cookie响应对象 (res)res 对象提供以下方法:res.status(code):设置状态码res.json(data):发送 JSON 响应res.send(data):发送响应res.redirect(url):重定向res.setHeader(name, value):设置响应头处理不同的 HTTP 方法// pages/api/users/[id].jsexport default async function handler(req, res) { const { id } = req.query; switch (req.method) { case 'GET': const user = await getUserById(id); res.status(200).json(user); break; case 'PUT': const updatedUser = await updateUser(id, req.body); res.status(200).json(updatedUser); break; case 'DELETE': await deleteUser(id); res.status(204).end(); break; default: res.setHeader('Allow', ['GET', 'PUT', 'DELETE']); res.status(405).end(`Method ${req.method} Not Allowed`); }}中间件自定义中间件// lib/middleware.jsexport function authMiddleware(req, res, next) { const token = req.headers.authorization; if (!token) { return res.status(401).json({ error: 'Unauthorized' }); } // 验证 token const user = verifyToken(token); if (!user) { return res.status(401).json({ error: 'Invalid token' }); } req.user = user; next();}使用中间件// pages/api/protected.jsimport { authMiddleware } from '@/lib/middleware';export default function handler(req, res) { // 受保护的路由 res.status(200).json({ user: req.user });}// 应用中间件export const config = { api: { bodyParser: false, externalResolver: true, },};// 在实际使用中,需要手动调用中间件数据库集成使用 Prisma// pages/api/posts/index.jsimport { PrismaClient } from '@prisma/client';const prisma = new PrismaClient();export default async function handler(req, res) { if (req.method === 'GET') { const posts = await prisma.post.findMany(); res.status(200).json(posts); } else if (req.method === 'POST') { const post = await prisma.post.create({ data: req.body, }); res.status(201).json(post); } else { res.status(405).end(); }}使用 MongoDB// pages/api/users/index.jsimport clientPromise from '@/lib/mongodb';export default async function handler(req, res) { const client = await clientPromise; const db = client.db(); if (req.method === 'GET') { const users = await db.collection('users').find({}).toArray(); res.status(200).json(users); } else if (req.method === 'POST') { const result = await db.collection('users').insertOne(req.body); res.status(201).json(result); } else { res.status(405).end(); }}身份验证使用 NextAuth.js// pages/api/auth/[...nextauth].jsimport NextAuth from 'next-auth';import Providers from 'next-auth/providers';export default NextAuth({ providers: [ Providers.Google({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, }), Providers.Credentials({ name: 'Credentials', credentials: { username: { label: "Username", type: "text" }, password: { label: "Password", type: "password" } }, authorize: async (credentials) => { // 验证用户 const user = await authenticate(credentials); if (user) { return user; } return null; } }), ], database: process.env.DATABASE_URL,});JWT 验证// lib/auth.jsimport jwt from 'jsonwebtoken';export function verifyToken(token) { try { return jwt.verify(token, process.env.JWT_SECRET); } catch (error) { return null; }}export function createToken(payload) { return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1d' });}文件上传// pages/api/upload.jsimport formidable from 'formidable';import fs from 'fs';import path from 'path';export const config = { api: { bodyParser: false, },};export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).end(); } const form = formidable({ uploadDir: path.join(process.cwd(), '/public/uploads'), keepExtensions: true, }); form.parse(req, (err, fields, files) => { if (err) { return res.status(500).json({ error: 'File upload failed' }); } const file = files.file[0]; res.status(200).json({ url: `/uploads/${path.basename(file.filepath)}` }); });}错误处理// pages/api/error.jsexport default function handler(req, res) { try { // 业务逻辑 const data = processData(req.body); res.status(200).json(data); } catch (error) { console.error('API Error:', error); if (error.name === 'ValidationError') { res.status(400).json({ error: error.message }); } else if (error.name === 'UnauthorizedError') { res.status(401).json({ error: 'Unauthorized' }); } else { res.status(500).json({ error: 'Internal server error' }); } }}CORS 配置// pages/api/cors.jsexport default function handler(req, res) { res.setHeader('Access-Control-Allow-Credentials', true); res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT'); res.setHeader( 'Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' ); if (req.method === 'OPTIONS') { res.status(200).end(); return; } res.status(200).json({ message: 'CORS enabled' });}最佳实践使用环境变量:敏感信息存储在 .env 文件中验证输入:使用验证库如 Zod 或 Yup错误处理:统一错误处理格式日志记录:记录请求和错误信息速率限制:防止 API 滥用缓存:对频繁访问的数据进行缓存文档化:使用 Swagger 或 OpenAPI 文档化 APINext.js API Routes 提供了简单而强大的方式来构建服务器端 API,无需单独的后端服务器,使全栈开发变得更加便捷。
服务端阅读 02月17日 23:30

什么是 Next.js 及其主要特性是什么?

Next.js 是一个基于 React 的开源 JavaScript 框架,用于构建服务器端渲染(SSR)和静态生成(SSG)的 Web 应用程序。它提供了许多开箱即用的功能,使开发者能够快速构建高性能的 React 应用。Next.js 的主要特性包括:服务器端渲染(SSR):Next.js 支持服务器端渲染,这意味着 HTML 在服务器上生成并发送到客户端,有助于 SEO 和首屏加载性能。静态生成(SSG):可以在构建时生成静态 HTML 文件,非常适合内容不经常变化的页面,如博客文章、产品页面等。自动代码分割:Next.js 自动将代码分割成小块,只加载当前页面所需的代码,提高应用性能。文件系统路由:基于 pages 目录的文件结构自动创建路由,无需手动配置路由。API 路由:可以创建 API 端点,处理服务器端逻辑,如数据库查询、身份验证等。CSS 支持:内置支持 CSS Modules、Sass、styled-jsx 等 CSS 方案。TypeScript 支持:开箱即用的 TypeScript 支持,提供类型安全。图像优化:提供 next/image 组件,自动优化图像大小和格式。快速刷新:开发时提供快速的热重载体验,保持组件状态。环境变量:支持 .env 文件来管理环境变量。国际化支持:内置 i18n 路由支持,轻松构建多语言应用。增量静态生成(ISR):允许在构建后更新静态页面,结合了 SSG 和 SSR 的优势。Next.js 通过这些特性,为 React 开发者提供了一个功能强大且易于使用的框架,特别适合构建需要良好 SEO、高性能和良好用户体验的 Web 应用。
服务端阅读 02月17日 23:30

Next.js 的图片优化是如何工作的?

Next.js 的图片优化功能通过 next/image 组件提供,这是一个强大的工具,可以自动优化图片以提升性能和用户体验。next/image 组件基础基本用法import Image from 'next/image';export default function Page() { return ( <Image src="/hero.jpg" alt="Hero image" width={800} height={600} /> );}必需属性src: 图片路径(本地或远程)width: 图片宽度(整数)height: 图片高度(整数)alt: 替代文本(用于可访问性)图片优化特性1. 自动格式转换Next.js 自动将图片转换为现代格式(WebP、AVIF),以减少文件大小。<Image src="/photo.jpg" alt="Photo" width={800} height={600} // 自动转换为 WebP 或 AVIF/>2. 响应式图片自动生成不同尺寸的图片,根据设备加载合适的尺寸。<Image src="/responsive.jpg" alt="Responsive image" width={1200} height={800} sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" // 生成多个尺寸:640w, 750w, 828w, 1080w, 1200w, 1920w, 2048w, 3840w/>3. 懒加载默认情况下,图片会懒加载,只有当它们进入视口时才会加载。<Image src="/lazy.jpg" alt="Lazy loaded image" width={800} height={600} // 默认启用懒加载/>// 禁用懒加载(首屏图片)<Image src="/hero.jpg" alt="Hero image" width={800} height={600} priority // 禁用懒加载,优先加载/>4. 模糊占位符在图片加载前显示模糊的占位符,提升用户体验。<Image src="/photo.jpg" alt="Photo" width={800} height={600} placeholder="blur" blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBD..."/>5. 避免布局偏移通过设置明确的宽高,避免图片加载时的布局偏移。<Image src="/photo.jpg" alt="Photo" width={800} height={600} // 预留空间,避免布局偏移/>高级用法1. 远程图片配置允许的远程图片域名。// next.config.jsmodule.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'example.com', port: '', pathname: '/images/**', }, { protocol: 'https', hostname: 'cdn.example.com', }, ], },};使用远程图片:<Image src="https://example.com/images/photo.jpg" alt="Remote image" width={800} height={600}/>2. 动态图片import Image from 'next/image';export default function ProductGallery({ images }) { return ( <div> {images.map((image) => ( <Image key={image.id} src={image.url} alt={image.alt} width={image.width} height={image.height} placeholder="blur" blurDataURL={image.blurDataURL} /> ))} </div> );}3. 图片填充模式<Image src="/photo.jpg" alt="Photo" width={800} height={600} fill // 填充父容器 style={{ objectFit: 'cover' }}/>4. 图片质量控制<Image src="/photo.jpg" alt="Photo" width={800} height={600} quality={80} // 默认 75,范围 1-100/>5. 自定义加载器import Image from 'next/image';const imageLoader = ({ src, width, quality }) => { return `https://cdn.example.com/${src}?w=${width}&q=${quality || 75}`;};export default function Page() { return ( <Image loader={imageLoader} src="photo.jpg" alt="Photo" width={800} height={600} /> );}配置选项next.config.js 配置// next.config.jsmodule.exports = { images: { // 设备尺寸 deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], // 图片尺寸 imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // 图片格式 formats: ['image/avif', 'image/webp'], // 最小化缓存时间(秒) minimumCacheTTL: 60, // 禁用静态导入 disableStaticImages: false, // 禁用优化 unoptimized: false, // 危险地允许所有域名(不推荐) dangerouslyAllowSVG: false, // 内容安全策略 contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", // 内容处置类型 contentDispositionType: 'attachment', // 远程图片模式 remotePatterns: [ { protocol: 'https', hostname: 'example.com', port: '', pathname: '/images/**', }, ], // 域名列表(已弃用,使用 remotePatterns) domains: ['example.com'], },};实际应用场景1. 电商产品图片import Image from 'next/image';export default function ProductCard({ product }) { return ( <div className="product-card"> <Image src={product.image} alt={product.name} width={400} height={400} priority // 首屏产品优先加载 placeholder="blur" blurDataURL={product.blurDataURL} sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" /> <h3>{product.name}</h3> <p>${product.price}</p> </div> );}2. 博客文章封面图import Image from 'next/image';export default function BlogPost({ post }) { return ( <article> <Image src={post.coverImage} alt={post.title} width={1200} height={630} priority // 文章封面优先加载 placeholder="blur" blurDataURL={post.blurDataURL} sizes="100vw" /> <h1>{post.title}</h1> <p>{post.excerpt}</p> </article> );}3. 用户头像import Image from 'next/image';export default function UserAvatar({ user, size = 40 }) { return ( <div className="avatar"> <Image src={user.avatar || '/default-avatar.png'} alt={user.name} width={size} height={size} className="rounded-full" placeholder="blur" blurDataURL={user.blurDataURL} /> </div> );}4. 图片画廊'use client';import { useState } from 'react';import Image from 'next/image';export default function ImageGallery({ images }) { const [selectedIndex, setSelectedIndex] = useState(0); return ( <div className="gallery"> <div className="main-image"> <Image src={images[selectedIndex].url} alt={images[selectedIndex].alt} width={1200} height={800} priority placeholder="blur" blurDataURL={images[selectedIndex].blurDataURL} /> </div> <div className="thumbnails"> {images.map((image, index) => ( <button key={image.id} onClick={() => setSelectedIndex(index)} className={index === selectedIndex ? 'active' : ''} > <Image src={image.url} alt={image.alt} width={150} height={100} placeholder="blur" blurDataURL={image.blurDataURL} /> </button> ))} </div> </div> );}5. 响应式英雄图片import Image from 'next/image';export default function HeroSection() { return ( <section className="hero"> <Image src="/hero.jpg" alt="Hero image" width={1920} height={1080} priority placeholder="blur" blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBD..." sizes="100vw" style={{ width: '100%', height: 'auto', objectFit: 'cover', }} /> <div className="hero-content"> <h1>Welcome to Our Site</h1> <p>Discover amazing content</p> </div> </section> );}性能优化技巧1. 使用 blurDataURL// 生成模糊占位符import { getPlaiceholder } from 'plaiceholder';export async function getBlurDataURL(src) { const buffer = await fetch(src).then(res => res.arrayBuffer()); const { base64 } = await getPlaiceholder(buffer); return base64;}// 使用const blurDataURL = await getBlurDataURL('/photo.jpg');<Image src="/photo.jpg" alt="Photo" width={800} height={600} placeholder="blur" blurDataURL={blurDataURL}/>2. 优化图片尺寸// 为不同场景使用合适的尺寸// 小缩略图<Image src="/thumb.jpg" alt="Thumbnail" width={150} height={150}/>// 中等图片<Image src="/medium.jpg" alt="Medium" width={400} height={300}/>// 大图<Image src="/large.jpg" alt="Large" width={1200} height={800}/>3. 使用合适的图片格式// next.config.jsmodule.exports = { images: { formats: ['image/avif', 'image/webp'], },};4. 延迟加载非关键图片// 首屏图片使用 priority<Image src="/hero.jpg" alt="Hero" width={800} height={600} priority/>// 非首屏图片使用默认懒加载<Image src="/below-fold.jpg" alt="Below fold" width={800} height={600}/>5. 使用 CDN 加速// next.config.jsmodule.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'cdn.example.com', }, ], },};最佳实践1. 始终提供 alt 文本// ✅ 好的做法<Image src="/photo.jpg" alt="A beautiful sunset over the ocean" width={800} height={600}/>// ❌ 不好的做法<Image src="/photo.jpg" alt="" width={800} height={600}/>2. 为首屏图片设置 priority// ✅ 好的做法:首屏图片优先加载<Image src="/hero.jpg" alt="Hero" width={800} height={600} priority/>// ❌ 不好的做法:所有图片都设置 priority<Image src="/below-fold.jpg" alt="Below fold" width={800} height={600} priority // 不必要的优先加载/>3. 使用合适的 sizes 属性// ✅ 好的做法:根据布局设置 sizes<Image src="/photo.jpg" alt="Photo" width={800} height={600} sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"/>// ❌ 不好的做法:不设置 sizes<Image src="/photo.jpg" alt="Photo" width={800} height={600} // 浏览器会假设图片占满视口/>4. 优化图片质量// ✅ 好的做法:根据图片类型调整质量<Image src="/photo.jpg" alt="Photo" width={800} height={600} quality={80} // 照片使用较高质量/><Image src="/icon.png" alt="Icon" width={64} height={64} quality={90} // 图标使用更高质量/>// ❌ 不好的做法:所有图片使用相同质量<Image src="/photo.jpg" alt="Photo" width={800} height={600} quality={100} // 不必要的高质量/>5. 使用占位符提升用户体验// ✅ 好的做法:使用模糊占位符<Image src="/photo.jpg" alt="Photo" width={800} height={600} placeholder="blur" blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBD..."/>// ❌ 不好的做法:不使用占位符<Image src="/photo.jpg" alt="Photo" width={800} height={600} // 图片加载前会显示空白/>常见问题Q: 如何处理动态图片?A: 使用动态导入或从 API 获取图片信息。import Image from 'next/image';export default async function DynamicImagePage() { const images = await fetch('/api/images').then(res => res.json()); return ( <div> {images.map(image => ( <Image key={image.id} src={image.url} alt={image.alt} width={image.width} height={image.height} /> ))} </div> );}Q: 如何禁用图片优化?A: 在 next.config.js 中设置 unoptimized: true。module.exports = { images: { unoptimized: true, },};Q: 如何处理 SVG 图片?A: SVG 图片需要特殊配置。module.exports = { images: { dangerouslyAllowSVG: true, contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", },};Next.js 的图片优化功能可以显著提升应用性能和用户体验。通过合理使用 next/image 组件和配置选项,可以构建出高性能的图片展示方案。
服务端阅读 02月17日 23:29

npm 7+ 有哪些新功能?它们如何提升包管理?

npm 7+ 引入了重大改进,包括并行安装、工作区支持和更好的依赖解析。了解这些新特性对于现代 JavaScript 开发至关重要。npm 7 主要新特性1. 并行安装npm 7 改进了依赖安装算法,支持并行下载和安装包。性能提升:安装速度比 npm 6 快 2-3 倍更好的网络资源利用减少总体安装时间配置并行度:# 设置最大并行连接数npm config set maxsockets 50# 设置最大网络请求并发数npm config set network-concurrency 162. 工作区(Workspaces)npm 7 原生支持 monorepo 工作区,无需额外配置。配置工作区:{ "name": "my-monorepo", "version": "1.0.0", "private": true, "workspaces": [ "packages/*" ], "scripts": { "install": "npm install -ws", "build": "npm run build -ws", "test": "npm test -ws" }}目录结构:my-monorepo/├── package.json├── packages/│ ├── shared/│ │ ├── package.json│ │ └── index.js│ ├── app/│ │ ├── package.json│ │ └── index.js│ └── utils/│ ├── package.json│ └── index.js工作区命令:# 在所有工作区中运行命令npm run build -ws# 在特定工作区中运行命令npm run build --workspace=packages/app# 安装依赖到特定工作区npm install lodash --workspace=packages/app# 添加工作区依赖npm install ../shared --workspace=packages/app3. 改进的依赖解析npm 7 使用更智能的依赖解析算法,减少重复安装。依赖提升:node_modules/├── lodash/ # 提升到顶层├── package-a/├── package-b/└── package-c/配置解析策略:# 禁用依赖提升npm config set legacy-bundling true# 使用严格的 peer 依赖解析npm config set strict-peer-deps true4. package-lock.json v2npm 7 引入了新的锁文件格式,提供更好的可读性和更详细的元数据。新特性:更清晰的 JSON 结构包含包的完整性信息支持工作区更好的版本范围处理示例:{ "name": "my-project", "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "my-project", "version": "1.0.0", "dependencies": { "express": "^4.18.0" } }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "integrity": "sha512-...", "dependencies": { "accepts": "~1.3.8" }, "engines": { "node": ">= 0.10.0" } } }}5. npm exec 和 npx 改进npm 7 改进了 npx 的实现,并引入了 npm exec 命令。npm exec:# 执行包中的二进制文件npm exec create-react-app my-app# 等同于 npxnpx create-react-app my-app# 传递参数npm exec --package=eslint -- eslint src/改进的 npx:更快的包解析更好的缓存机制支持多个包6. 自动安装 peer 依赖npm 7 默认自动安装 peer 依赖,简化依赖管理。行为变化:# npm 6: 需要手动安装 peer 依赖npm install <package>npm install <peer-dependency># npm 7: 自动安装 peer 依赖npm install <package>配置:# 禁用自动安装 peer 依赖npm config set auto-install-peers false# 使用严格的 peer 依赖解析npm config set strict-peer-deps true7. 改进的输出格式npm 7 提供更清晰、更简洁的输出格式。示例:added 1423 packages, and audited 1424 packages in 32s238 packages are looking for funding run `npm fund` for detailsfound 0 vulnerabilities8. npm fund 命令npm 7 引入了 npm fund 命令,显示项目的资金来源信息。使用:npm fund输出示例:my-project@1.0.0├── express@4.18.2│ └── https://opencollective.com/express├── lodash@4.17.21│ └── https://opencollective.com/lodash└── webpack@5.0.0 └── https://github.com/sponsors/webpack9. 支持 overridesnpm 8+ 支持 overrides 字段,强制使用特定版本的依赖。配置 overrides:{ "overrides": { "vulnerable-package": "1.2.3", "package-a": { "package-b": "2.0.0" } }}使用场景:修复安全漏洞解决版本冲突强制使用特定版本10. 改进的错误处理npm 7 提供更详细的错误信息和更好的错误恢复机制。错误信息示例:npm ERR! code ERESOLVEnpm ERR! ERESOLVE unable to resolve dependency treenpm ERR!npm ERR! While resolving: my-project@1.0.0npm ERR! Found: react@18.0.0npm ERR! node_modules/reactnpm ERR! react@"^18.0.0" from the root projectnpm ERR!npm ERR! Could not resolve dependency:npm ERR! peer react@"^16.0.0" from some-package@1.0.0npm ERR! node_modules/some-packagenpm 8 新特性1. 改进的 workspacesnpm 8 改进了工作区功能,支持更复杂的 monorepo 结构。2. npm diff 命令npm 8 引入了 npm diff 命令,比较依赖版本差异。# 比较当前安装与 package.jsonnpm diff# 比较特定包npm diff <package-name># 比较两个版本npm diff <package-name>@1.0.0 <package-name>@2.0.03. 改进的 npm querynpm 8 改进了 npm query 命令,支持更强大的依赖查询。# 查找所有过期的包npm query ":outdated"# 查找所有开发依赖npm query ":dev"# 查找特定包的依赖npm query "lodash > *"4. 支持 .npmrc 的继承npm 8 支持从父目录继承 .npmrc 配置。npm 9 新特性1. 改进的性能npm 9 进一步优化了性能,安装速度更快。2. 改进的安全性npm 9 增强了安全功能,包括更好的完整性验证。3. 改进的错误报告npm 9 提供更清晰的错误信息和更好的故障排除指导。迁移到 npm 7+1. 升级 npm# 使用 npm 自身升级npm install -g npm@latest# 使用 nvmnvm install node --latest-npm# 使用 nn latest2. 检查兼容性# 检查项目依赖npm ls# 检查 peer 依赖npm ls --depth=03. 处理 peer 依赖# 自动安装 peer 依赖npm install# 手动解决冲突npm install --force4. 更新 package-lock.json# 删除旧的锁文件rm package-lock.json# 重新安装生成新锁文件npm install最佳实践1. 使用工作区管理 monorepo{ "workspaces": [ "packages/*" ], "scripts": { "install": "npm install -ws", "build": "npm run build -ws", "test": "npm test -ws", "clean": "npm run clean -ws" }}2. 使用 overrides 管理版本{ "overrides": { "vulnerable-package": "1.2.3" }}3. 使用 npm ci 替代 npm install# CI 环境使用 npm cinpm ci4. 配置合理的并行度# 根据网络环境调整npm config set maxsockets 50npm config set network-concurrency 165. 使用 npm fund 支持开源项目# 查看项目资金来源npm fund# 支持开源项目npm fund <package-name>常见问题1. peer 依赖冲突# 使用严格的 peer 依赖解析npm config set strict-peer-deps true# 手动解决冲突npm install --force2. 工作区依赖问题# 清理工作区缓存npm cache clean --force# 重新安装npm install -ws3. 性能问题# 检查配置npm config list# 调整并行度npm config set maxsockets 50# 使用缓存npm install --prefer-offlinenpm 7+ 带来了显著的性能提升和功能改进,是现代 JavaScript 开发的理想选择。
服务端阅读 02月17日 23:00

Tailwind CSS 的动画和过渡效果如何使用,有哪些常用的动画类?

Tailwind CSS 的动画和过渡效果通过内置的工具类实现,让开发者能够轻松创建动态效果。过渡效果:transition:启用所有过渡属性transition-none:禁用过渡效果transition-all:所有属性都有过渡效果transition-colors:颜色属性有过渡效果transition-opacity:透明度有过渡效果transition-shadow:阴影有过渡效果transition-transform:变换有过渡效果过渡持续时间:duration-75:75msduration-100:100msduration-150:150msduration-200:200msduration-300:300msduration-500:500msduration-700:700msduration-1000:1000ms过渡缓动函数:ease-linear:线性缓动ease-in:缓入ease-out:缓出ease-in-out:缓入缓出过渡延迟:delay-75:延迟 75msdelay-100:延迟 100msdelay-150:延迟 150msdelay-200:延迟 200msdelay-300:延迟 300msdelay-500:延迟 500msdelay-700:延迟 700msdelay-1000:延迟 1000ms动画效果:animate-none:无动画animate-spin:旋转动画animate-ping:脉冲动画animate-pulse:心跳动画animate-bounce:弹跳动画悬停状态:hover:鼠标悬停时应用样式示例: <button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"> Hover me </button>焦点状态:focus:元素获得焦点时应用样式示例: <input class="border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 rounded">活动状态:active:元素被激活时应用样式示例: <button class="bg-blue-500 active:bg-blue-700 text-white px-4 py-2 rounded"> Click me </button>组合状态:可以组合多个状态修饰符示例: <button class="bg-blue-500 hover:bg-blue-600 focus:ring-2 focus:ring-blue-500 text-white px-4 py-2 rounded"> Button </button>响应式动画:结合响应式前缀使用示例: <div class="animate-pulse md:animate-spin"> 响应式动画 </div>深色模式动画:结合深色模式使用示例: <div class="bg-white dark:bg-gray-900 transition-colors duration-300"> 深色模式过渡 </div>自定义动画:在 tailwind.config.js 中配置示例: // tailwind.config.js module.exports = { theme: { extend: { animation: { 'fade-in': 'fadeIn 0.5s ease-in', 'slide-up': 'slideUp 0.5s ease-out', }, keyframes: { fadeIn: { '0%': { opacity: '0' }, '100%': { opacity: '1' }, }, slideUp: { '0%': { transform: 'translateY(100%)' }, '100%': { transform: 'translateY(0)' }, }, }, }, }, }使用自定义动画: <div class="animate-fade-in">淡入动画</div> <div class="animate-slide-up">上滑动画</div>最佳实践:合理使用过渡效果,避免过度动画保持动画简洁,提高用户体验测试动画性能,避免卡顿考虑用户偏好设置(减少动画)使用硬件加速(transform、opacity)
服务端阅读 02月17日 23:00

Tailwind CSS 的插件系统有哪些常用插件,如何创建和使用自定义插件?

Tailwind CSS 的插件系统允许开发者扩展框架的功能,添加自定义工具类和组件。官方插件:@tailwindcss/forms:表单样式插件为表单元素提供基础样式重置表单元素的默认样式安装:npm install -D @tailwindcss/forms使用:javascript// tailwind.config.jsplugins: [ require('@tailwindcss/forms'),]示例:<input type="text" class="form-input"><select class="form-select"> <option>Option 1</option> <option>Option 2</option></select>@tailwindcss/typography:排版插件为内容区域提供优美的排版样式支持 prose 类安装:npm install -D @tailwindcss/typography使用:javascript// tailwind.config.jsplugins: [ require('@tailwindcss/typography'),]示例:<article class="prose lg:prose-xl"> <h1>Article Title</h1> <p>Article content goes here.</p></article>@tailwindcss/aspect-ratio:宽高比插件提供宽高比工具类安装:npm install -D @tailwindcss/aspect-ratio使用:javascript// tailwind.config.jsplugins: [ require('@tailwindcss/aspect-ratio'),]示例:<div class="aspect-w-16 aspect-h-9"> <img src="image.jpg" alt="Image"></div>@tailwindcss/line-clamp:文本截断插件提供文本截断工具类安装:npm install -D @tailwindcss/line-clamp使用:javascript// tailwind.config.jsplugins: [ require('@tailwindcss/line-clamp'),]示例:```html This is a long text that will be truncated after 3 lines.```第三方插件:tailwindcss-animate:动画插件提供常用的动画类安装:npm install -D tailwindcss-animate使用:javascriptplugins: [ require('tailwindcss-animate'),]示例:<div class="animate-bounce">Bouncing element</div><div class="animate-pulse">Pulsing element</div>@tailwindcss/container-queries:容器查询插件提供容器查询支持安装:npm install -D @tailwindcss/container-queries使用:javascriptplugins: [ require('@tailwindcss/container-queries'),]自定义插件:创建自定义插件示例: // my-plugin.js const plugin = require('tailwindcss/plugin') module.exports = plugin(function({ addUtilities, theme }) { const newUtilities = { '.text-shadow': { textShadow: theme('textShadow'), }, } addUtilities(newUtilities) }, { theme: { textShadow: { sm: '2px 2px 4px rgba(0, 0, 0, 0.1)', DEFAULT: '4px 4px 8px rgba(0, 0, 0, 0.2)', lg: '8px 8px 16px rgba(0, 0, 0, 0.3)', }, }, })使用自定义插件: // tailwind.config.js const myPlugin = require('./my-plugin') module.exports = { plugins: [ myPlugin, ], }插件配置:为插件传递配置选项示例: plugins: [ require('@tailwindcss/forms')({ strategy: 'class', }), require('@tailwindcss/typography')({ className: 'prose', }), ]最佳实践:只安装需要的插件合理使用插件,避免过度依赖考虑创建自定义插件满足特定需求定期更新插件版本测试插件兼容性
服务端阅读 02月17日 22:57

TailwindCSS 如何与 React、Vue、Angular 等框架集成?

TailwindCSS 在现代前端框架(React、Vue、Angular)中都有良好的支持,可以无缝集成到各种项目中。在 React 中使用 TailwindCSS1. 安装和配置# 安装 TailwindCSS 和相关依赖npm install -D tailwindcss postcss autoprefixernpx tailwindcss init -p2. 配置文件// tailwind.config.jsmodule.exports = { content: [ "./src/**/*.{js,jsx,ts,tsx}", ], theme: { extend: {}, }, plugins: [],}3. 在 React 组件中使用// 基础使用function Button({ children, onClick }) { return ( <button onClick={onClick} className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded" > {children} </button> );}// 响应式组件function Card({ title, content }) { return ( <div className="bg-white rounded-lg shadow-md p-6 w-full md:w-1/2 lg:w-1/3"> <h3 className="text-xl font-bold mb-2">{title}</h3> <p className="text-gray-600">{content}</p> </div> );}// 条件类名function StatusBadge({ status }) { const statusClasses = { active: 'bg-green-100 text-green-800', inactive: 'bg-gray-100 text-gray-800', error: 'bg-red-100 text-red-800', }; return ( <span className={`px-2 py-1 rounded ${statusClasses[status]}`}> {status} </span> );}4. 使用 clsx 或 classnames 库import clsx from 'clsx';function Button({ variant, size, children, className, ...props }) { const baseClasses = 'font-bold rounded'; const variantClasses = { primary: 'bg-blue-500 hover:bg-blue-600 text-white', secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-800', danger: 'bg-red-500 hover:bg-red-600 text-white', }; const sizeClasses = { small: 'py-1 px-2 text-sm', medium: 'py-2 px-4', large: 'py-3 px-6 text-lg', }; return ( <button className={clsx( baseClasses, variantClasses[variant], sizeClasses[size], className )} {...props} > {children} </button> );}在 Vue 中使用 TailwindCSS1. 安装和配置# 安装 TailwindCSSnpm install -D tailwindcss postcss autoprefixernpx tailwindcss init -p2. 配置文件// tailwind.config.jsmodule.exports = { content: [ "./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [],}3. 在 Vue 组件中使用<template> <!-- 基础使用 --> <button @click="handleClick" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded" > 点击按钮 </button> <!-- 动态类名 --> <div :class="['bg-white', isActive ? 'bg-blue-500' : 'bg-gray-200']"> 动态类名 </div> <!-- 对象语法 --> <div :class="{ 'bg-blue-500': isActive, 'bg-gray-200': !isActive, 'text-white': isActive }" > 对象语法 </div></template><script>export default { data() { return { isActive: false, }; }, methods: { handleClick() { this.isActive = !this.isActive; }, },};</script>4. 组合式 API<template> <button @click="toggle" :class="buttonClasses" > {{ isActive ? '激活' : '未激活' }} </button></template><script setup>import { computed, ref } from 'vue';const isActive = ref(false);const buttonClasses = computed(() => { return [ 'font-bold py-2 px-4 rounded', isActive.value ? 'bg-blue-500 hover:bg-blue-600 text-white' : 'bg-gray-200 hover:bg-gray-300 text-gray-800' ];});function toggle() { isActive.value = !isActive.value;}</script>在 Angular 中使用 TailwindCSS1. 安装和配置# 安装 TailwindCSSnpm install -D tailwindcss postcss autoprefixernpx tailwindcss init2. 配置文件// tailwind.config.jsmodule.exports = { content: [ "./src/**/*.{html,ts}", ], theme: { extend: {}, }, plugins: [],}3. 在 Angular 组件中使用// 组件类import { Component } from '@angular/core';@Component({ selector: 'app-button', template: ` <button (click)="handleClick()" [ngClass]="buttonClasses" > {{ buttonText }} </button> `, styles: []})export class ButtonComponent { isActive = false; get buttonText() { return this.isActive ? '激活' : '未激活'; } get buttonClasses() { return { 'bg-blue-500': this.isActive, 'bg-gray-200': !this.isActive, 'hover:bg-blue-600': this.isActive, 'hover:bg-gray-300': !this.isActive, 'text-white': this.isActive, 'text-gray-800': !this.isActive, 'font-bold': true, 'py-2': true, 'px-4': true, 'rounded': true }; } handleClick() { this.isActive = !this.isActive; }}在 Svelte 中使用 TailwindCSS1. 安装和配置# 安装 TailwindCSSnpm install -D tailwindcss postcss autoprefixernpx tailwindcss init -p2. 在 Svelte 组件中使用<script> let isActive = false; function toggle() { isActive = !isActive; }</script><button on:click={toggle} class:bg-blue-500={isActive} class:bg-gray-200={!isActive} class:text-white={isActive} class:text-gray-800={!isActive} class="font-bold py-2 px-4 rounded"> {isActive ? '激活' : '未激活'}</button>最佳实践1. 组件封装// 创建可复用的组件const Button = ({ variant, size, children, ...props }) => { const variants = { primary: 'bg-blue-500 hover:bg-blue-600 text-white', secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-800', }; const sizes = { small: 'py-1 px-2 text-sm', medium: 'py-2 px-4', large: 'py-3 px-6 text-lg', }; return ( <button className={`font-bold rounded ${variants[variant]} ${sizes[size]}`} {...props} > {children} </button> );};2. 样式提取// 提取常用样式为常量const CARD_STYLES = 'bg-white rounded-lg shadow-md p-6';const BUTTON_STYLES = 'bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded';function Card({ title, content }) { return ( <div className={CARD_STYLES}> <h3 className="text-xl font-bold mb-2">{title}</h3> <p className="text-gray-600">{content}</p> </div> );}3. 响应式设计// 使用响应式类function ResponsiveGrid({ children }) { return ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {children} </div> );}4. 性能优化// 使用 useMemo 优化类名计算import { useMemo } from 'react';function OptimizedButton({ variant, size, children }) { const className = useMemo(() => { return `font-bold rounded ${variant} ${size}`; }, [variant, size]); return <button className={className}>{children}</button>;}注意事项类名管理:在大型项目中,考虑使用类名管理工具代码可读性:避免在单个元素上使用过多的类名组件复用:将常用的样式组合封装为组件类型安全:在 TypeScript 中使用类型定义确保类名正确测试:确保在不同框架中样式表现一致
服务端阅读 02月17日 22:57

TailwindCSS 的 Forms 插件如何使用?

TailwindCSS 提供了专门的 Forms 插件(@tailwindcss/forms),为表单元素提供了美观且一致的样式,大大简化了表单开发工作。安装和配置1. 安装插件# 使用 npmnpm install -D @tailwindcss/forms# 使用 yarnyarn add -D @tailwindcss/forms# 使用 pnpmpnpm add -D @tailwindcss/forms2. 配置插件// tailwind.config.jsmodule.exports = { plugins: [ require('@tailwindcss/forms'), ],}基础表单样式1. 输入框<!-- 文本输入框 --><input type="text" placeholder="请输入用户名" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"><!-- 邮箱输入框 --><input type="email" placeholder="请输入邮箱" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"><!-- 密码输入框 --><input type="password" placeholder="请输入密码" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"><!-- 数字输入框 --><input type="number" placeholder="请输入数字" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">2. 文本域<!-- 文本域 --><textarea placeholder="请输入内容" rows="4" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none "></textarea>3. 选择框<!-- 单选下拉框 --><select class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> <option value="">请选择</option> <option value="1">选项 1</option> <option value="2">选项 2</option> <option value="3">选项 3</option></select><!-- 多选下拉框 --><select multiple class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> <option value="1">选项 1</option> <option value="2">选项 2</option> <option value="3">选项 3</option></select>4. 复选框和单选按钮<!-- 复选框 --><label class="flex items-center space-x-2"> <input type="checkbox" class=" w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 "> <span>同意条款</span></label><!-- 单选按钮 --><div class="space-y-2"> <label class="flex items-center space-x-2"> <input type="radio" name="gender" value="male" class=" w-4 h-4 text-blue-600 border-gray-300 focus:ring-blue-500 "> <span>男</span> </label> <label class="flex items-center space-x-2"> <input type="radio" name="gender" value="female" class=" w-4 h-4 text-blue-600 border-gray-300 focus:ring-blue-500 "> <span>女</span> </label></div>表单状态1. 禁用状态<!-- 禁用的输入框 --><input type="text" placeholder="禁用的输入框" disabled class=" w-full px-4 py-2 border border-gray-300 rounded bg-gray-100 text-gray-500 cursor-not-allowed "><!-- 禁用的按钮 --><button disabled class=" px-4 py-2 bg-blue-500 text-white rounded disabled:bg-gray-400 disabled:cursor-not-allowed "> 禁用的按钮</button>2. 只读状态<!-- 只读输入框 --><input type="text" value="只读内容" readonly class=" w-full px-4 py-2 border border-gray-300 rounded bg-gray-100 ">3. 错误状态<!-- 错误状态的输入框 --><input type="text" placeholder="请输入用户名" class=" w-full px-4 py-2 border border-red-500 rounded focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent "><p class="text-red-500 text-sm mt-1">用户名不能为空</p>表单布局1. 水平布局<!-- 水平表单 --><form class="flex items-end space-x-4"> <div class="flex-1"> <label class="block text-sm font-medium text-gray-700 mb-1"> 用户名 </label> <input type="text" placeholder="请输入用户名" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <div class="flex-1"> <label class="block text-sm font-medium text-gray-700 mb-1"> 密码 </label> <input type="password" placeholder="请输入密码" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <button type="submit" class=" px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded font-medium " > 登录 </button></form>2. 垂直布局<!-- 垂直表单 --><form class="space-y-4 max-w-md"> <div> <label class="block text-sm font-medium text-gray-700 mb-1"> 用户名 </label> <input type="text" placeholder="请输入用户名" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1"> 邮箱 </label> <input type="email" placeholder="请输入邮箱" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1"> 密码 </label> <input type="password" placeholder="请输入密码" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <button type="submit" class=" w-full px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded font-medium " > 注册 </button></form>3. 网格布局<!-- 网格表单 --><form class="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-2xl"> <div> <label class="block text-sm font-medium text-gray-700 mb-1"> 名字 </label> <input type="text" placeholder="请输入名字" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1"> 姓氏 </label> <input type="text" placeholder="请输入姓氏" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <div class="md:col-span-2"> <label class="block text-sm font-medium text-gray-700 mb-1"> 邮箱 </label> <input type="email" placeholder="请输入邮箱" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <div class="md:col-span-2"> <label class="block text-sm font-medium text-gray-700 mb-1"> 地址 </label> <textarea placeholder="请输入地址" rows="3" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none " ></textarea> </div> <div class="md:col-span-2"> <button type="submit" class=" w-full px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded font-medium " > 提交 </button> </div></form>自定义表单样式1. 使用 @apply/* 在 CSS 文件中定义表单样式 */.form-input { @apply w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent;}.form-input-error { @apply border-red-500 focus:ring-red-500;}.form-label { @apply block text-sm font-medium text-gray-700 mb-1;}<!-- 使用自定义样式 --><form class="space-y-4"> <div> <label class="form-label">用户名</label> <input type="text" class="form-input" placeholder="请输入用户名"> </div> <div> <label class="form-label">密码</label> <input type="password" class="form-input form-input-error" placeholder="请输入密码"> </div></form>2. 配置插件选项// tailwind.config.jsmodule.exports = { plugins: [ require('@tailwindcss/forms')({ strategy: 'class', // 或 'base' }), ],}完整表单示例<!-- 注册表单 --><form class="max-w-md mx-auto space-y-6 bg-white p-8 rounded-lg shadow-md"> <h2 class="text-2xl font-bold text-center text-gray-900"> 创建账户 </h2> <div> <label class="block text-sm font-medium text-gray-700 mb-1"> 用户名 </label> <input type="text" placeholder="请输入用户名" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1"> 邮箱 </label> <input type="email" placeholder="请输入邮箱" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1"> 密码 </label> <input type="password" placeholder="请输入密码" class=" w-full px-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent " > </div> <div> <label class="flex items-center space-x-2"> <input type="checkbox" class=" w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 " > <span class="text-sm text-gray-700"> 我同意服务条款和隐私政策 </span> </label> </div> <button type="submit" class=" w-full px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded font-medium transition-colors " > 注册 </button> <p class="text-center text-sm text-gray-600"> 已有账户? <a href="#" class="text-blue-600 hover:text-blue-500"> 登录 </a> </p></form>最佳实践1. 一致的焦点样式<!-- 所有表单元素使用一致的焦点样式 --><input type="text" class=" border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent ">2. 清晰的标签<!-- 使用清晰的标签 --><div> <label class="block text-sm font-medium text-gray-700 mb-1"> 用户名 <span class="text-red-500">*</span> </label> <input type="text" placeholder="请输入用户名" class="w-full px-4 py-2 border border-gray-300 rounded" ></div>3. 错误提示<!-- 提供清晰的错误提示 --><div> <label class="block text-sm font-medium text-gray-700 mb-1"> 邮箱 </label> <input type="email" class=" w-full px-4 py-2 border border-red-500 rounded focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent " > <p class="text-red-500 text-sm mt-1"> 请输入有效的邮箱地址 </p></div>注意事项可访问性:确保表单元素有正确的标签和 ARIA 属性移动端优化:考虑移动端的触摸目标和输入体验验证反馈:提供即时的验证反馈错误处理:清晰显示错误信息提交状态:处理提交中的状态和禁用按钮总结TailwindCSS Forms 插件提供了:美观的默认表单样式一致的跨浏览器表现灵活的自定义选项简化的表单开发流程通过合理使用 Forms 插件,可以快速创建美观、易用的表单界面。
服务端阅读 02月17日 22:56

TailwindCSS 的插件系统如何工作?如何开发自定义插件?

TailwindCSS 提供了强大的插件系统,允许开发者扩展框架功能,添加自定义工具类、组件和变体。插件系统概述TailwindCSS 插件本质上是 JavaScript 函数,可以访问 TailwindCSS 的内部 API,包括主题配置、工具类生成器、变体等。插件基本结构const plugin = require('tailwindcss/plugin');module.exports = plugin(function({ addUtilities, addComponents, addBase, theme, variants }) { // 插件逻辑}, { // 插件选项 theme: { extend: {}, },});官方插件1. Forms 插件// 安装npm install @tailwindcss/forms// 配置module.exports = { plugins: [ require('@tailwindcss/forms'), ],}Forms 插件提供了表单元素的基础样式重置和美化。2. Typography 插件// 安装npm install @tailwindcss/typography// 配置module.exports = { plugins: [ require('@tailwindcss/typography'), ],}使用示例:<article class="prose prose-lg"> <h1>文章标题</h1> <p>文章内容...</p></article>3. Aspect Ratio 插件// 安装npm install @tailwindcss/aspect-ratio// 配置module.exports = { plugins: [ require('@tailwindcss/aspect-ratio'), ],}使用示例:<div class="aspect-w-16 aspect-h-9"> <iframe src="video.mp4"></iframe></div>4. Container Queries 插件// 安装npm install @tailwindcss/container-queries// 配置module.exports = { plugins: [ require('@tailwindcss/container-queries'), ],}自定义插件开发1. 添加工具类const plugin = require('tailwindcss/plugin');module.exports = plugin(function({ addUtilities, theme }) { const newUtilities = { '.text-shadow': { textShadow: theme('textShadow.DEFAULT'), }, '.text-shadow-sm': { textShadow: '1px 1px 2px rgba(0, 0, 0, 0.1)', }, '.text-shadow-lg': { textShadow: '4px 4px 8px rgba(0, 0, 0, 0.2)', }, }; addUtilities(newUtilities);});2. 添加组件类const plugin = require('tailwindcss/plugin');module.exports = plugin(function({ addComponents, theme }) { const buttons = { '.btn': { display: 'inline-block', padding: theme('spacing.2') + ' ' + theme('spacing.4'), borderRadius: theme('borderRadius.default'), fontWeight: theme('fontWeight.bold'), textAlign: 'center', }, '.btn-primary': { backgroundColor: theme('colors.blue.500'), color: theme('colors.white'), '&:hover': { backgroundColor: theme('colors.blue.600'), }, }, '.btn-secondary': { backgroundColor: theme('colors.gray.200'), color: theme('colors.gray.800'), '&:hover': { backgroundColor: theme('colors.gray.300'), }, }, }; addComponents(buttons);});3. 添加基础样式const plugin = require('tailwindcss/plugin');module.exports = plugin(function({ addBase }) { addBase({ 'body': { fontFamily: 'system-ui, sans-serif', lineHeight: '1.5', }, 'h1, h2, h3, h4, h5, h6': { fontWeight: 'bold', lineHeight: '1.2', }, });});4. 添加变体const plugin = require('tailwindcss/plugin');module.exports = plugin(function({ addVariant }) { // 添加自定义变体 addVariant('group-hover', ({ modifySelectors, separator }) => { modifySelectors(({ className }) => { return `.group:hover .${className}`; }); }); // 添加更复杂的变体 addVariant('not-first', ({ modifySelectors, separator }) => { modifySelectors(({ className }) => { return `:not(:first-child) > .${className}`; }); });});5. 扩展主题const plugin = require('tailwindcss/plugin');module.exports = plugin(function({ theme }) { return { theme: { extend: { colors: { brand: { primary: '#3b82f6', secondary: '#10b981', }, }, spacing: { '128': '32rem', }, }, }, };});高级插件技巧1. 动态生成工具类const plugin = require('tailwindcss/plugin');module.exports = plugin(function({ addUtilities, theme }) { const colors = theme('colors'); const textUtilities = {}; Object.keys(colors).forEach(color => { if (typeof colors[color] === 'object') { Object.keys(colors[color]).forEach(shade => { textUtilities[`.text-${color}-${shade}`] = { color: colors[color][shade], }; }); } }); addUtilities(textUtilities);});2. 条件性工具类const plugin = require('tailwindcss/plugin');module.exports = plugin(function({ addUtilities, e, config }) { const prefix = config('prefix'); addUtilities({ [`.${e(`${prefix}print-hidden`)}`]: { '@media print': { display: 'none', }, }, });});3. 组合多个功能const plugin = require('tailwindcss/plugin');module.exports = plugin(function({ addUtilities, addComponents, addBase, theme, variants }) { // 添加基础样式 addBase({ 'html': { fontSize: '16px', }, }); // 添加工具类 addUtilities({ '.truncate-multiline': { overflow: 'hidden', display: '-webkit-box', '-webkit-line-clamp': '3', '-webkit-box-orient': 'vertical', }, }); // 添加组件 addComponents({ '.card': { backgroundColor: theme('colors.white'), borderRadius: theme('borderRadius.lg'), boxShadow: theme('boxShadow.lg'), padding: theme('spacing.6'), }, });});插件最佳实践单一职责:每个插件只负责一个特定功能可配置性:提供配置选项让用户自定义插件行为文档完善:为插件提供详细的使用文档类型安全:使用 TypeScript 编写插件以获得类型支持性能优化:避免在插件中进行重复计算发布插件1. 创建插件包// package.json{ "name": "tailwindcss-my-plugin", "version": "1.0.0", "main": "index.js", "peerDependencies": { "tailwindcss": ">=3.0.0" }}2. 导出插件// index.jsconst plugin = require('tailwindcss/plugin');module.exports = plugin(function({ addUtilities }) { // 插件逻辑});3. 使用插件// tailwind.config.jsmodule.exports = { plugins: [ require('tailwindcss-my-plugin'), ],}
服务端阅读 02月17日 22:55

TailwindCSS 的状态变体(hover、focus、active 等)如何使用?

TailwindCSS 提供了强大的状态变体系统,允许开发者根据元素的不同状态(如 hover、focus、active 等)应用不同的样式。基础状态变体1. Hover 状态鼠标悬停时应用的样式。<!-- 基础 hover --><button class="bg-blue-500 hover:bg-blue-600"> 悬停变色</button><!-- 多个 hover 效果 --><button class="bg-blue-500 hover:bg-blue-600 hover:scale-105 hover:shadow-lg"> 多重悬停效果</button>2. Focus 状态元素获得焦点时应用的样式。<!-- 基础 focus --><input class="border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200"><!-- focus-visible(仅键盘导航时) --><button class="focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"> 键盘导航焦点</button>3. Active 状态元素被激活(点击)时应用的样式。<!-- 基础 active --><button class="bg-blue-500 active:bg-blue-700"> 点击效果</button><!-- 组合状态 --><button class="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 active:scale-95"> 组合状态效果</button>表单状态变体1. Disabled 状态禁用元素的样式。<!-- 基础 disabled --><button class="bg-blue-500 disabled:bg-gray-400 disabled:cursor-not-allowed" disabled> 禁用按钮</button><!-- 表单输入 --><input class="border-gray-300 disabled:bg-gray-100 disabled:text-gray-500" disabled>2. Read-only 状态只读元素的样式。<input class="border-gray-300 read-only:bg-gray-100 read-only:text-gray-500" readonly>3. Checked 状态复选框和单选按钮的选中状态。<!-- 复选框 --><input type="checkbox" class="accent-blue-500 checked:accent-blue-600"><!-- 使用 peer 变体 --><label class="flex items-center space-x-2"> <input type="checkbox" class="peer"> <span class="peer-checked:text-blue-500 peer-checked:font-bold"> 选中时变色 </span></label>伪类变体1. First-child 和 Last-child<!-- 第一个子元素 --><ul class="space-y-2"> <li class="first:font-bold first:text-blue-500">第一个项目</li> <li>第二个项目</li> <li>第三个项目</li></ul><!-- 最后一个子元素 --><ul class="space-y-2"> <li>第一个项目</li> <li>第二个项目</li> <li class="last:font-bold last:text-blue-500">最后一个项目</li></ul>2. Odd 和 Even<!-- 奇数行 --><table class="w-full"> <tbody> <tr class="odd:bg-gray-100"> <td>奇数行</td> </tr> <tr> <td>偶数行</td> </tr> </tbody></table><!-- 偶数行 --><table class="w-full"> <tbody> <tr> <td>奇数行</td> </tr> <tr class="even:bg-gray-100"> <td>偶数行</td> </tr> </tbody></table>3. Before 和 After<!-- 使用 before 伪元素 --><div class="before:content-[''] before:block before:w-4 before:h-4 before:bg-blue-500"> 前缀元素</div><!-- 使用 after 伪元素 --><div class="after:content-['→'] after:ml-2 after:text-blue-500"> 后缀元素</div>媒体查询变体1. 响应式变体<!-- 移动优先响应式 --><div class="w-full md:w-1/2 lg:w-1/3"> 响应式宽度</div><!-- 响应式显示隐藏 --><div class="block md:hidden lg:block"> 条件显示</div>2. Dark Mode<!-- 启用暗色模式 --><div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white"> 暗色模式支持</div><!-- 暗色模式配置 --><script>// tailwind.config.jsmodule.exports = { darkMode: 'class', // 或 'media'}</script>3. Print 样式<div class="print:hidden"> 打印时隐藏</div><div class="print:block hidden"> 仅打印时显示</div>交互状态变体1. Group 和 Group-hover<!-- 父子元素交互 --><div class="group"> <p class="text-gray-600 group-hover:text-blue-500"> 悬停父元素时变色 </p></div><!-- 多层嵌套 --><div class="group"> <div class="group-hover:bg-blue-100"> <span class="group-hover:text-blue-500"> 嵌套悬停效果 </span> </div></div>2. Peer 和 Peer-checked<!-- 同级元素交互 --><label class="flex items-center space-x-2"> <input type="checkbox" class="peer"> <span class="peer-checked:text-blue-500 peer-checked:font-bold"> 选中时变色 </span></label><!-- 复杂交互 --><div> <input type="checkbox" class="peer" id="toggle"> <div class="hidden peer-checked:block"> 选中时显示的内容 </div></div>3. Focus-within<!-- 子元素获得焦点时 --><div class="focus-within:ring-2 focus-within:ring-blue-500"> <input type="text" placeholder="输入时父元素会有边框"></div>自定义状态变体1. 添加自定义变体// tailwind.config.jsconst plugin = require('tailwindcss/plugin');module.exports = { plugins: [ plugin(function({ addVariant }) { // 添加自定义变体 addVariant('important', ({ modifySelectors, separator }) => { modifySelectors(({ className }) => { return `.${className}!`; }); }); }), ],}2. 使用自定义变体<!-- 使用自定义变体 --><div class="text-gray-500 important:text-blue-500"> 优先级更高的样式</div>状态变体堆叠TailwindCSS 支持堆叠多个状态变体,实现复杂的交互效果。<!-- 堆叠多个变体 --><button class=" bg-blue-500 hover:bg-blue-600 focus:bg-blue-700 active:bg-blue-800 disabled:bg-gray-400 disabled:cursor-not-allowed"> 多状态按钮</button><!-- 响应式 + 状态 --><div class=" w-full md:w-1/2 lg:w-1/3 hover:shadow-lg focus:ring-2"> 响应式交互元素</div><!-- 暗色模式 + 状态 --><button class=" bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-900 dark:text-white"> 暗色模式按钮</button>最佳实践合理使用状态变体:避免过度使用,保持代码可读性移动优先:先编写基础样式,再添加状态变体组合使用:合理组合多个状态变体实现复杂效果测试交互:确保所有状态变体在不同设备和浏览器中正常工作性能考虑:避免使用过多的状态变体影响性能注意事项变体顺序:某些变体有特定的顺序要求(如 group-hover)浏览器兼容性:某些伪类在旧浏览器中可能不支持性能影响:过多的状态变体可能影响 CSS 文件大小可访问性:确保状态变体不会影响键盘导航和屏幕阅读器
服务端阅读 02月17日 22:52

Next.js 与微前端架构如何结合使用?

Next.js 与微前端架构的结合是构建大型企业级应用的重要方案。微前端架构允许将大型应用拆分为多个独立开发、部署和维护的小型前端应用。微前端架构概述1. 微前端核心概念微前端是一种架构风格,将前端应用分解为更小、更简单的块,这些块可以由不同的团队独立开发和部署。核心优势:独立开发和部署技术栈无关增量升级团队自治代码隔离Next.js 微前端实现方案1. Module Federation(模块联邦)// next.config.js - 主应用配置const NextFederationPlugin = require('@module-federation/nextjs-mf');module.exports = { webpack(config, options) { const { isServer } = options; config.plugins.push( new NextFederationPlugin({ name: 'main_app', filename: 'static/chunks/remoteEntry.js', remotes: { productApp: 'product_app@https://product.example.com/_next/static/chunks/remoteEntry.js', cartApp: 'cart_app@https://cart.example.com/_next/static/chunks/remoteEntry.js', userApp: 'user_app@https://user.example.com/_next/static/chunks/remoteEntry.js', }, shared: { react: { singleton: true, requiredVersion: false, }, 'react-dom': { singleton: true, requiredVersion: false, }, next: { singleton: true, requiredVersion: false, }, }, extraOptions: { automaticAsyncBoundary: true, }, }) ); return config; },};// next.config.js - 子应用配置(productApp)const NextFederationPlugin = require('@module-federation/nextjs-mf');module.exports = { webpack(config, options) { const { isServer } = options; config.plugins.push( new NextFederationPlugin({ name: 'product_app', filename: 'static/chunks/remoteEntry.js', exposes: { './ProductList': './components/ProductList', './ProductDetail': './components/ProductDetail', './ProductSearch': './components/ProductSearch', }, shared: { react: { singleton: true, requiredVersion: false, }, 'react-dom': { singleton: true, requiredVersion: false, }, next: { singleton: true, requiredVersion: false, }, }, }) ); return config; },};// 主应用中使用远程组件// app/products/page.js'use client';import dynamic from 'next/dynamic';const ProductList = dynamic(() => import('productApp/ProductList'), { loading: () => <div>Loading products...</div>, ssr: false,});const ProductSearch = dynamic(() => import('productApp/ProductSearch'), { loading: () => <div>Loading search...</div>, ssr: false,});export default function ProductsPage() { return ( <div> <h1>Products</h1> <ProductSearch /> <ProductList /> </div> );}2. iframe 方案// components/IframeWrapper.js'use client';import { useState, useEffect, useRef } from 'react';export default function IframeWrapper({ src, title, onMessage }) { const iframeRef = useRef(null); const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { const iframe = iframeRef.current; const handleMessage = (event) => { // 验证消息来源 if (event.origin !== new URL(src).origin) return; onMessage?.(event.data); }; window.addEventListener('message', handleMessage); return () => { window.removeEventListener('message', handleMessage); }; }, [src, onMessage]); const handleLoad = () => { setIsLoaded(true); }; const sendMessage = (message) => { if (iframeRef.current && iframeRef.current.contentWindow) { iframeRef.current.contentWindow.postMessage(message, new URL(src).origin); } }; return ( <div className="iframe-container"> {!isLoaded && <div className="loading">Loading...</div>} <iframe ref={iframeRef} src={src} title={title} onLoad={handleLoad} style={{ border: 'none', width: '100%', height: '100%', display: isLoaded ? 'block' : 'none' }} allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" /> </div> );}// 使用 iframe 集成子应用// app/dashboard/page.js'use client';import IframeWrapper from '@/components/IframeWrapper';export default function DashboardPage() { const handleMessage = (data) => { console.log('Message from iframe:', data); if (data.type === 'NAVIGATION') { // 处理导航事件 } else if (data.type === 'AUTH') { // 处理认证事件 } }; return ( <div className="dashboard"> <nav> <a href="/">Home</a> <a href="/dashboard">Dashboard</a> </nav> <main> <IframeWrapper src="https://cart.example.com" title="Shopping Cart" onMessage={handleMessage} /> </main> </div> );}3. Web Components 方案// components/MicroFrontendWrapper.js'use client';import { useEffect, useRef } from 'react';export default function MicroFrontendWrapper({ name, host, history, onNavigate, onUnmount}) { const ref = useRef(null); useEffect(() => { const scriptId = `micro-frontend-script-${name}`; const renderMicroFrontend = () => { window[name] = { mount: (container, history) => { console.log(`Mounting ${name}`); // 调用子应用的 mount 方法 }, unmount: (container) => { console.log(`Unmounting ${name}`); onUnmount?.(); }, }; if (window[name] && window[name].mount) { window[name].mount(ref.current, history); } }; const loadScript = () => { if (document.getElementById(scriptId)) { renderMicroFrontend(); return; } const script = document.createElement('script'); script.id = scriptId; script.src = `${host}/main.js`; script.onload = renderMicroFrontend; document.head.appendChild(script); }; loadScript(); return () => { if (window[name] && window[name].unmount) { window[name].unmount(ref.current); } }; }, [name, host, history, onUnmount]); return <div ref={ref} />;}// 使用 Web Components 集成// app/micro/page.js'use client';import MicroFrontendWrapper from '@/components/MicroFrontendWrapper';export default function MicroFrontendPage() { const handleNavigate = (location) => { console.log('Navigate to:', location); window.history.pushState({}, '', location); }; const handleUnmount = () => { console.log('Micro frontend unmounted'); }; return ( <div> <h1>Micro Frontend Integration</h1> <MicroFrontendWrapper name="productApp" host="https://product.example.com" history={window.history} onNavigate={handleNavigate} onUnmount={handleUnmount} /> </div> );}4. 单体仓库(Monorepo)方案// 使用 Turborepo 管理 monorepo// turbo.json{ "$schema": "https://turbo.build/schema.json", "globalDependencies": ["**/.env.*local"], "pipeline": { "build": { "dependsOn": ["^build"], "outputs": [".next/**", "!.next/cache/**", "dist/**"] }, "dev": { "cache": false, "persistent": true }, "lint": { "dependsOn": ["^lint"] }, "test": { "dependsOn": ["^build"], "outputs": ["coverage/**"] } }}// pnpm-workspace.yamlpackages: - 'apps/*' - 'packages/*'// 目录结构// apps/// main-app/ # 主应用// product-app/ # 产品子应用// cart-app/ # 购物车子应用// user-app/ # 用户子应用// packages/// ui/ # 共享 UI 组件// utils/ # 共享工具函数// types/ # 共享类型定义// config/ # 共享配置// apps/main-app/package.json{ "name": "main-app", "dependencies": { "next": "^14.0.0", "react": "^18.0.0", "react-dom": "^18.0.0", "@workspace/ui": "workspace:*", "@workspace/utils": "workspace:*" }}// apps/product-app/package.json{ "name": "product-app", "dependencies": { "next": "^14.0.0", "react": "^18.0.0", "react-dom": "^18.0.0", "@workspace/ui": "workspace:*", "@workspace/utils": "workspace:*" }}状态管理和通信1. 跨应用状态管理// packages/shared-state/src/store.jsimport { createStore } from 'zustand/vanilla';export const createSharedStore = (initialState) => { return createStore((set, get) => ({ ...initialState, update: (key, value) => set({ [key]: value }), reset: () => set(initialState), }));};// 创建共享状态export const userStore = createSharedStore({ user: null, isAuthenticated: false, cart: [],});export const productStore = createSharedStore({ products: [], filters: {}, sortBy: 'name',});// 主应用中使用// app/layout.js'use client';import { userStore } from '@workspace/shared-state';import { useEffect } from 'react';export default function RootLayout({ children }) { useEffect(() => { // 监听用户状态变化 const unsubscribe = userStore.subscribe((state) => { console.log('User state changed:', state); // 通知其他应用 window.postMessage({ type: 'USER_STATE_CHANGE', state }, '*'); }); return () => unsubscribe(); }, []); return ( <html lang="en"> <body>{children}</body> </html> );}// 子应用中使用// product-app/components/UserInfo.js'use client';import { userStore } from '@workspace/shared-state';import { useEffect, useState } from 'react';export default function UserInfo() { const [user, setUser] = useState(null); useEffect(() => { // 订阅用户状态 const unsubscribe = userStore.subscribe((state) => { setUser(state.user); }); return () => unsubscribe(); }, []); if (!user) { return <div>Please login</div>; } return <div>Welcome, {user.name}</div>;}2. 事件总线通信// packages/event-bus/src/index.jsclass EventBus { constructor() { this.events = {}; } on(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); } off(event, callback) { if (!this.events[event]) return; this.events[event] = this.events[event].filter(cb => cb !== callback); } emit(event, data) { if (!this.events[event]) return; this.events[event].forEach(callback => { callback(data); }); } once(event, callback) { const onceCallback = (data) => { callback(data); this.off(event, onceCallback); }; this.on(event, onceCallback); }}export const eventBus = new EventBus();// 定义事件类型export const Events = { USER_LOGIN: 'USER_LOGIN', USER_LOGOUT: 'USER_LOGOUT', CART_UPDATE: 'CART_UPDATE', PRODUCT_ADD: 'PRODUCT_ADD', NAVIGATION: 'NAVIGATION',};// 主应用中监听事件// app/_components/EventListeners.js'use client';import { useEffect } from 'react';import { eventBus, Events } from '@workspace/event-bus';import { useRouter } from 'next/navigation';export default function EventListeners() { const router = useRouter(); useEffect(() => { const handleNavigation = (data) => { console.log('Navigation event:', data); router.push(data.path); }; const handleCartUpdate = (data) => { console.log('Cart updated:', data); // 更新购物车 UI }; eventBus.on(Events.NAVIGATION, handleNavigation); eventBus.on(Events.CART_UPDATE, handleCartUpdate); return () => { eventBus.off(Events.NAVIGATION, handleNavigation); eventBus.off(Events.CART_UPDATE, handleCartUpdate); }; }, [router]); return null;}// 子应用中发送事件// product-app/components/AddToCart.js'use client';import { eventBus, Events } from '@workspace/event-bus';export default function AddToCart({ product }) { const handleAddToCart = () => { eventBus.emit(Events.PRODUCT_ADD, { product }); eventBus.emit(Events.CART_UPDATE, { type: 'ADD', product }); }; return ( <button onClick={handleAddToCart}> Add to Cart </button> );}样式隔离1. CSS Modules 隔离// product-app/components/ProductCard.module.css.productCard { border: 1px solid #ddd; padding: 16px; border-radius: 8px; background: white;}.productCard__title { font-size: 18px; font-weight: bold; margin-bottom: 8px;}.productCard__price { color: #e44d26; font-size: 20px; font-weight: bold;}// product-app/components/ProductCard.jsimport styles from './ProductCard.module.css';export default function ProductCard({ product }) { return ( <div className={styles.productCard}> <h3 className={styles.productCard__title}>{product.name}</h3> <p className={styles.productCard__price}>${product.price}</p> </div> );}2. CSS-in-JS 隔离// product-app/components/ProductCard.js'use client';import styled from 'styled-components';const Card = styled.div` border: 1px solid #ddd; padding: 16px; border-radius: 8px; background: white;`;const Title = styled.h3` font-size: 18px; font-weight: bold; margin-bottom: 8px;`;const Price = styled.p` color: #e44d26; font-size: 20px; font-weight: bold;`;export default function ProductCard({ product }) { return ( <Card> <Title>{product.name}</Title> <Price>${product.price}</Price> </Card> );}3. Shadow DOM 隔离// components/ShadowDOMWrapper.js'use client';import { useEffect, useRef } from 'react';export default function ShadowDOMWrapper({ children, styles }) { const containerRef = useRef(null); const shadowRootRef = useRef(null); useEffect(() => { if (!containerRef.current) return; // 创建 Shadow DOM shadowRootRef.current = containerRef.current.attachShadow({ mode: 'open' }); // 添加样式 if (styles) { const styleElement = document.createElement('style'); styleElement.textContent = styles; shadowRootRef.current.appendChild(styleElement); } // 添加内容 const content = document.createElement('div'); content.className = 'shadow-content'; shadowRootRef.current.appendChild(content); return () => { if (shadowRootRef.current) { containerRef.current.removeChild(shadowRootRef.current); } }; }, [styles]); useEffect(() => { if (shadowRootRef.current) { const content = shadowRootRef.current.querySelector('.shadow-content'); if (content) { // 使用 ReactDOM 渲染到 Shadow DOM import('react-dom/client').then(({ createRoot }) => { const root = createRoot(content); root.render(children); }); } } }, [children]); return <div ref={containerRef} />;}// 使用 Shadow DOM// app/micro/page.js'use client';import ShadowDOMWrapper from '@/components/ShadowDOMWrapper';const shadowStyles = ` .product-card { border: 1px solid #ddd; padding: 16px; border-radius: 8px; background: white; } .product-title { font-size: 18px; font-weight: bold; }`;export default function MicroFrontendPage() { return ( <ShadowDOMWrapper styles={shadowStyles}> <div className="product-card"> <h3 className="product-title">Product Name</h3> <p>$99.99</p> </div> </ShadowDOMWrapper> );}部署策略1. 独立部署// Vercel 配置 - 主应用// vercel.json{ "framework": "nextjs", "buildCommand": "pnpm build", "outputDirectory": ".next", "routes": [ { "src": "/(.*)", "dest": "/$1" } ]}// Vercel 配置 - 子应用// product-app/vercel.json{ "framework": "nextjs", "buildCommand": "pnpm build", "outputDirectory": ".next", "routes": [ { "src": "/(.*)", "dest": "/$1" } ]}// Docker 部署配置// DockerfileFROM node:18-alpine AS base# 依赖安装FROM base AS depsWORKDIR /appCOPY package.json pnpm-lock.yaml ./RUN npm install -g pnpm && pnpm install --frozen-lockfile# 构建FROM base AS builderWORKDIR /appCOPY --from=deps /app/node_modules ./node_modulesCOPY . .RUN pnpm build# 运行FROM base AS runnerWORKDIR /appENV NODE_ENV productionCOPY --from=builder /app/public ./publicCOPY --from=builder /app/.next/standalone ./COPY --from=builder /app/.next/static ./.next/staticEXPOSE 3000CMD ["node", "server.js"]2. CI/CD 流程// .github/workflows/deploy.ymlname: Deployon: push: branches: [main]jobs: deploy-main: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install pnpm uses: pnpm/action-setup@v2 with: version: 8 - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build run: pnpm --filter main-app build - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.ORG_ID }} vercel-project-id: ${{ secrets.PROJECT_ID }} working-directory: ./apps/main-app deploy-product: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install pnpm uses: pnpm/action-setup@v2 with: version: 8 - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build run: pnpm --filter product-app build - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.ORG_ID }} vercel-project-id: ${{ secrets.PRODUCT_PROJECT_ID }} working-directory: ./apps/product-app最佳实践选择合适的方案: Module Federation 适合技术栈统一的项目,iframe 适合完全隔离的场景共享依赖: 使用 monorepo 管理共享代码和依赖状态管理: 使用事件总线或共享状态管理跨应用通信样式隔离: 使用 CSS Modules、CSS-in-JS 或 Shadow DOM 避免样式冲突独立部署: 每个子应用独立构建和部署版本管理: 使用语义化版本管理子应用依赖监控和日志: 统一监控和日志收集性能优化: 按需加载子应用,避免重复依赖测试策略: 集成测试覆盖跨应用场景文档和规范: 建立清晰的开发规范和文档Next.js 与微前端架构的结合为企业级应用提供了灵活、可扩展的解决方案。
服务端阅读 02月17日 22:48

TypeORM 中如何使用生命周期钩子?

TypeORM 中使用订阅者(Subscriber)和监听器(Listener)实现生命周期钩子:实体监听器:在实体类中使用装饰器定义监听器: @Entity() export class User { @BeforeInsert() beforeInsert() { this.createdAt = new Date(); } @BeforeUpdate() beforeUpdate() { this.updatedAt = new Date(); } @AfterLoad() afterLoad() { // 加载后执行 } }订阅者:独立的类,可以监听多个实体的生命周期事件: @EventSubscriber() export class UserSubscriber implements EntitySubscriberInterface<User> { listenTo() { return User; } beforeInsert(event: InsertEvent<User>) { console.log('Before insert:', event.entity); } }可用的事件:BeforeInsert / AfterInsertBeforeUpdate / AfterUpdateBeforeRemove / AfterRemoveBeforeSoftRemove / AfterSoftRemoveBeforeRecover / AfterRecoverAfterLoad注册订阅者:在 DataSource 配置中添加 subscribers 选项。生命周期钩子适用于数据验证、自动填充字段、日志记录等场景。
服务端阅读 02月17日 22:45

什么是 TypeORM,它的核心概念有哪些?

TypeORM 是一个基于 TypeScript 的 ORM 框架,它使用装饰器(Decorators)来定义实体类和数据库表的映射关系。核心概念包括:Entity(实体):使用 @Entity() 装饰器标记的类,对应数据库中的表Column(列):使用 @Column() 装饰器标记的类属性,对应表中的列PrimaryGeneratedColumn:自动生成的主键列Repository:用于执行数据库操作的数据访问层DataSource:管理数据库连接和配置的核心类TypeORM 支持 Active Record 和 Data Mapper 两种模式,开发者可以根据项目需求选择合适的模式。它还提供了强大的查询构建器和关系映射功能,使得数据库操作更加类型安全和便捷。
服务端阅读 02月17日 22:44

TypeORM 有哪些性能优化技巧?

TypeORM 的性能优化技巧:使用索引: @Column() @Index() email: string;避免 N+1 查询:使用 relations 或 join 预加载关联数据。选择必要的字段: userRepository.find({ select: ['id', 'name'] });使用分页: userRepository.find({ skip: 0, take: 10 });批量操作:使用 insert、update 的批量方法代替循环操作。使用缓存:在 DataSource 中启用缓存选项。优化查询:使用 QueryBuilder 构建高效查询避免 SELECT *合理使用 WHERE 条件连接池配置:合理设置 poolSize 和连接池参数。使用原生 SQL:对于复杂查询,考虑使用原生 SQL。监控和日志:启用 logging 选项监控 SQL 查询性能。
服务端阅读 02月17日 22:44

TypeORM 中如何进行数据验证?

TypeORM 的验证(Validation)可以通过以下方式实现:使用 class-validator 库:安装:npm install class-validator在实体中使用验证装饰器: import { IsEmail, IsNotEmpty, Length } from 'class-validator'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() @IsNotEmpty() @Length(2, 50) name: string; @Column() @IsEmail() email: string; }在保存前验证: import { validate } from 'class-validator'; const user = new User(); user.name = 'John'; user.email = 'invalid-email'; const errors = await validate(user); if (errors.length > 0) { console.log('Validation failed:', errors); }使用订阅器自动验证:创建订阅器在 BeforeInsert 和 BeforeUpdate 时自动验证。数据库级别验证:使用 @Column 的 nullable、unique、length 等选项进行数据库约束。推荐结合使用 class-validator 和数据库约束,确保数据完整性和安全性。
服务端阅读 02月17日 22:43

TypeORM 中如何实现软删除?

TypeORM 的软删除(Soft Delete)功能允许标记记录为已删除而不是物理删除:启用软删除:在实体中使用 @DeleteDateColumn() 装饰器: @Entity() export class User { @DeleteDateColumn() deletedAt?: Date; }软删除操作: await userRepository.softRemove(user); // 或使用 await userRepository.softDelete(userId);恢复软删除的记录: await userRepository.restore(userId);查询时排除软删除的记录:默认情况下,find() 方法会自动排除已软删除的记录。包含软删除的记录: userRepository.find({ withDeleted: true });只查询软删除的记录: userRepository.find({ withDeleted: true, where: { deletedAt: Not(IsNull()) } });软删除的优势是可以保留数据历史,便于数据恢复和审计。
服务端阅读 1082024年6月24日 16:43

nodejs 如何使用 DllPlugin 动态链接库?

Node.js 中的 DllPlugin 及相关的 DllReferencePlugin 主要是用于改善构建时间和实现代码分离,在 Webpack 构建过程中使用。DllPlugin 用来打包出一个个独立的动态链接库文件,而 DllReferencePlugin 则用于在主应用程序中引用这些动态链接库。以下是使用 DllPlugin 的具体步骤:步骤 1: 创建 DLL 文件首先,你需要在项目中创建一个 webpack 配置文件专门用于构建 DLL。// webpack.dll.config.jsconst path = require('path');const webpack = require('webpack');module.exports = { entry: { vendor: ['lodash', 'react'] // 假设我们希望将 lodash 和 react 打包成一个 DLL }, output: { path: path.join(__dirname, 'dist'), filename: '[name].dll.js', // 输出的文件名 library: '[name]_library' // 全局变量名,其他模块会从此变量上获取到里面的模块 }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, 'dist', '[name]-manifest.json'), name: '[name]_library' }) ]};这个配置会将 lodash 和 react 打包成一个名为 vendor.dll.js 的文件,并生成一个 vendor-manifest.json 文件。步骤 2: 在主配置中引用 DLL然后,在你的主 webpack 配置文件中,你需要使用 DllReferencePlugin 来引用上一步中生成的 vendor-manifest.json 文件。// webpack.config.jsconst path = require('path');const webpack = require('webpack');module.exports = { // ...你的其他配置 plugins: [ // ...你的其他插件 new webpack.DllReferencePlugin({ context: __dirname, manifest: require('./dist/vendor-manifest.json') // 引入 DLL 的 manifest 文件 }) ], // ...其余配置};步骤 3: 在 HTML 中引入 DLL 文件最后,你需要确保在应用程序加载前,先在 HTML 文件中引入这些 DLL 文件。例如:<!DOCTYPE html><html><head> <title>My App</title></head><body> <script src="./dist/vendor.dll.js"></script> <!-- 接下来是你应用程序的其它脚本 --></body></html>使用例子假设你有一个大型项目,每次构建都需要很长时间,因为第三方库例如 React、Vue 或 Lodash 并不经常更改,但它们每次都会被重新编译。通过使用 DLL,你可以将这些库预编译成静态资源,以便在开发过程中重复使用,从而减少了构建时需要处理的工作量,并加快了构建速度。使用 DLL 时需要注意的是,当你更新了 DLL 中的依赖项时,你需要重新构建 DLL 文件。同时,应当确保在生产环境构建中不包含 DLL 的引用,或者确保 DLL 是最新的,以避免因为版本不一致带来的问题。总的来说,DllPlugin 提高了开发效率,尤其是在大型项目和频繁构建的环境中,它可以显著减少构建时间并提升开发体验。