refactor(ui): simplify empty state components and remove unused blog posts
- Replace custom SVG icons with lucide-react components in TaxonomyPageLayout, CategoryCard, and TagCard - Remove unnecessary "Back to blog list" links from empty states - Delete outdated blog posts about React Hooks, Docker, Tailwind CSS, and TypeScript
This commit is contained in:
@@ -1,477 +0,0 @@
|
||||
---
|
||||
title: "精通 React Hooks:深入探索"
|
||||
description: "探索 React Hooks 在函数组件中管理状态和副作用的强大功能,包含实用示例和最佳实践。"
|
||||
image: "https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=400&h=250&fit=crop&crop=center"
|
||||
date: "2025年5月10日"
|
||||
readTime: "5分钟阅读"
|
||||
tags: ["React", "JavaScript", "前端开发"]
|
||||
tagId: ["react", "javascript", "frontend"]
|
||||
category: ["React", "前端"]
|
||||
categoryId: ["react", "frontend"]
|
||||
slug: "mastering-react-hooks"
|
||||
layout: "../../../../layouts/BlogPostLayout.astro"
|
||||
---
|
||||
|
||||

|
||||
|
||||
React Hooks 彻底改变了我们编写 React 组件的方式,让我们能够在函数组件中使用状态和其他 React 特性。在这个全面的指南中,我们将探索最重要的 hooks 并学习如何有效地使用它们。
|
||||
|
||||
## 什么是 React Hooks?
|
||||
|
||||
React Hooks 是让你能够从函数组件中"钩入" React 状态和生命周期特性的函数。它们在 React 16.8 中引入,现在已经成为编写 React 组件的首选方式。
|
||||
|
||||
### 主要优势:
|
||||
- **更简洁的代码**:无需类组件
|
||||
- **更好的可复用性**:自定义 hooks 允许共享有状态的逻辑
|
||||
- **更容易测试**:函数组件更容易测试
|
||||
- **更好的性能**:优化更容易实现
|
||||
|
||||
## 基础 React Hooks
|
||||
|
||||
### 1. useState Hook
|
||||
|
||||
`useState` hook 允许你向函数组件添加状态:
|
||||
|
||||
```javascript
|
||||
import React, { useState } from 'react';
|
||||
|
||||
function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>你点击了 {count} 次</p>
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
点击我
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. useEffect Hook
|
||||
|
||||
`useEffect` hook 让你在函数组件中执行副作用:
|
||||
|
||||
```javascript
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
function UserProfile({ userId }) {
|
||||
const [user, setUser] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchUser() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(`/api/users/${userId}`);
|
||||
const userData = await response.json();
|
||||
setUser(userData);
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
fetchUser();
|
||||
}, [userId]); // 依赖数组
|
||||
|
||||
if (loading) return <div>加载中...</div>;
|
||||
if (!user) return <div>用户未找到</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{user.name}</h2>
|
||||
<p>{user.email}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. useContext Hook
|
||||
|
||||
`useContext` hook 提供了一种在组件树中传递数据的方式,无需手动逐层传递 props:
|
||||
|
||||
```javascript
|
||||
import React, { useContext, createContext } from 'react';
|
||||
|
||||
const ThemeContext = createContext();
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ThemeContext.Provider value="dark">
|
||||
<Header />
|
||||
<Main />
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Header() {
|
||||
const theme = useContext(ThemeContext);
|
||||
return (
|
||||
<header className={`header-${theme}`}>
|
||||
<h1>我的应用</h1>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义 Hooks
|
||||
|
||||
React Hooks 最强大的特性之一是能够创建自定义 hooks 来封装和复用有状态的逻辑:
|
||||
|
||||
```javascript
|
||||
// 用于 API 调用的自定义 hook
|
||||
function useApi(url) {
|
||||
const [data, setData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('网络响应不正常');
|
||||
}
|
||||
const result = await response.json();
|
||||
setData(result);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
fetchData();
|
||||
}, [url]);
|
||||
|
||||
return { data, loading, error };
|
||||
}
|
||||
|
||||
// 使用自定义 hook
|
||||
function ProductList() {
|
||||
const { data: products, loading, error } = useApi('/api/products');
|
||||
|
||||
if (loading) return <div>加载产品中...</div>;
|
||||
if (error) return <div>错误: {error}</div>;
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{products.map(product => (
|
||||
<li key={product.id}>{product.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 遵循 Hooks 规则
|
||||
- 只在 React 函数的顶层调用 hooks
|
||||
- 只在 React 函数(组件或自定义 hooks)中调用 hooks
|
||||
|
||||
### 2. 性能优化
|
||||
使用 `useMemo` 和 `useCallback` 防止不必要的重新渲染:
|
||||
|
||||
```javascript
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
|
||||
function ExpensiveComponent({ items, onItemClick }) {
|
||||
// 记忆化昂贵的计算
|
||||
const expensiveValue = useMemo(() => {
|
||||
return items.reduce((sum, item) => sum + item.value, 0);
|
||||
}, [items]);
|
||||
|
||||
// 记忆化回调函数
|
||||
const handleClick = useCallback((id) => {
|
||||
onItemClick(id);
|
||||
}, [onItemClick]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>总计: {expensiveValue}</p>
|
||||
{items.map(item => (
|
||||
<button key={item.id} onClick={() => handleClick(item.id)}>
|
||||
{item.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 清理副作用
|
||||
在 useEffect 中始终清理订阅和定时器:
|
||||
|
||||
```javascript
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
console.log('定时器触发');
|
||||
}, 1000);
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
|
||||
## 高级 Hooks 模式
|
||||
|
||||
### 1. useReducer 用于复杂状态管理
|
||||
|
||||
```javascript
|
||||
import React, { useReducer } from 'react';
|
||||
|
||||
// 定义状态和动作类型
|
||||
const initialState = {
|
||||
count: 0,
|
||||
step: 1,
|
||||
};
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'increment':
|
||||
return { ...state, count: state.count + state.step };
|
||||
case 'decrement':
|
||||
return { ...state, count: state.count - state.step };
|
||||
case 'setStep':
|
||||
return { ...state, step: action.payload };
|
||||
case 'reset':
|
||||
return initialState;
|
||||
default:
|
||||
throw new Error('未知的动作类型');
|
||||
}
|
||||
}
|
||||
|
||||
function Counter() {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>计数: {state.count}</p>
|
||||
<p>步长: {state.step}</p>
|
||||
<button onClick={() => dispatch({ type: 'increment' })}>
|
||||
增加
|
||||
</button>
|
||||
<button onClick={() => dispatch({ type: 'decrement' })}>
|
||||
减少
|
||||
</button>
|
||||
<input
|
||||
type="number"
|
||||
value={state.step}
|
||||
onChange={(e) => dispatch({
|
||||
type: 'setStep',
|
||||
payload: parseInt(e.target.value) || 1
|
||||
})}
|
||||
/>
|
||||
<button onClick={() => dispatch({ type: 'reset' })}>
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 自定义 Hook 用于表单处理
|
||||
|
||||
```javascript
|
||||
// 表单处理的自定义 hook
|
||||
function useForm(initialValues, validationRules) {
|
||||
const [values, setValues] = useState(initialValues);
|
||||
const [errors, setErrors] = useState({});
|
||||
const [touched, setTouched] = useState({});
|
||||
|
||||
const handleChange = useCallback((name, value) => {
|
||||
setValues(prev => ({ ...prev, [name]: value }));
|
||||
|
||||
// 实时验证
|
||||
if (validationRules[name]) {
|
||||
const error = validationRules[name](value);
|
||||
setErrors(prev => ({ ...prev, [name]: error }));
|
||||
}
|
||||
}, [validationRules]);
|
||||
|
||||
const handleBlur = useCallback((name) => {
|
||||
setTouched(prev => ({ ...prev, [name]: true }));
|
||||
}, []);
|
||||
|
||||
const isValid = useMemo(() => {
|
||||
return Object.values(errors).every(error => !error);
|
||||
}, [errors]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setValues(initialValues);
|
||||
setErrors({});
|
||||
setTouched({});
|
||||
}, [initialValues]);
|
||||
|
||||
return {
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
isValid,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
reset,
|
||||
};
|
||||
}
|
||||
|
||||
// 使用表单 hook
|
||||
function LoginForm() {
|
||||
const validationRules = {
|
||||
email: (value) => {
|
||||
if (!value) return '邮箱是必填的';
|
||||
if (!/\S+@\S+\.\S+/.test(value)) return '邮箱格式不正确';
|
||||
return null;
|
||||
},
|
||||
password: (value) => {
|
||||
if (!value) return '密码是必填的';
|
||||
if (value.length < 6) return '密码至少需要6个字符';
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
const {
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
isValid,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
reset,
|
||||
} = useForm({ email: '', password: '' }, validationRules);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (isValid) {
|
||||
console.log('提交表单:', values);
|
||||
reset();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="邮箱"
|
||||
value={values.email}
|
||||
onChange={(e) => handleChange('email', e.target.value)}
|
||||
onBlur={() => handleBlur('email')}
|
||||
/>
|
||||
{touched.email && errors.email && (
|
||||
<span className="error">{errors.email}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
value={values.password}
|
||||
onChange={(e) => handleChange('password', e.target.value)}
|
||||
onBlur={() => handleBlur('password')}
|
||||
/>
|
||||
{touched.password && errors.password && (
|
||||
<span className="error">{errors.password}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button type="submit" disabled={!isValid}>
|
||||
登录
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 常见陷阱和解决方案
|
||||
|
||||
### 1. 避免无限循环
|
||||
|
||||
```javascript
|
||||
// 错误:会导致无限循环
|
||||
function BadComponent() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setCount(count + 1); // 每次渲染都会触发
|
||||
}); // 缺少依赖数组
|
||||
|
||||
return <div>{count}</div>;
|
||||
}
|
||||
|
||||
// 正确:使用依赖数组
|
||||
function GoodComponent() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setCount(c => c + 1); // 使用函数式更新
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []); // 空依赖数组,只在挂载时运行
|
||||
|
||||
return <div>{count}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 正确处理异步操作
|
||||
|
||||
```javascript
|
||||
function UserProfile({ userId }) {
|
||||
const [user, setUser] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function fetchUser() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(`/api/users/${userId}`);
|
||||
const userData = await response.json();
|
||||
|
||||
// 检查组件是否仍然挂载
|
||||
if (!cancelled) {
|
||||
setUser(userData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!cancelled) {
|
||||
console.error('获取用户失败:', error);
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetchUser();
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [userId]);
|
||||
|
||||
if (loading) return <div>加载中...</div>;
|
||||
return user ? <div>{user.name}</div> : <div>用户未找到</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## 结论
|
||||
|
||||
React Hooks 从根本上改变了我们编写 React 应用的方式。它们提供了一种更直观、更强大的方式来管理函数组件中的状态和副作用。通过掌握 `useState`、`useEffect` 和 `useContext` 等 hooks,以及学习创建自定义 hooks,你将能够编写更可维护和可复用的 React 代码。
|
||||
|
||||
记住要遵循 hooks 的规则,在必要时进行性能优化,并始终清理你的副作用。有了这些最佳实践,你将很快成为 React Hooks 专家!
|
||||
|
||||
---
|
||||
|
||||
*想了解更多关于 React 的内容?查看我们关于高级 React 模式和性能优化的其他文章。*
|
||||
@@ -1,794 +0,0 @@
|
||||
---
|
||||
title: "使用 Tailwind CSS 构建现代 UI"
|
||||
description: "探索如何使用 Tailwind CSS 创建美观、响应式的用户界面,提升开发效率和设计一致性。"
|
||||
image: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=250&fit=crop&crop=center"
|
||||
date: "2025年4月20日"
|
||||
readTime: "6分钟阅读"
|
||||
tags: ["CSS", "Tailwind", "UI"]
|
||||
slug: "modern-ui-tailwind"
|
||||
layout: "../../../../layouts/BlogPostLayout.astro"
|
||||
---
|
||||
|
||||

|
||||
|
||||
Tailwind CSS 已经彻底改变了我们构建用户界面的方式。作为一个实用优先的 CSS 框架,它提供了低级实用类,让你可以直接在标记中构建完全自定义的设计。在本指南中,我们将探索如何使用 Tailwind CSS 创建现代、响应式的 UI 组件。
|
||||
|
||||
## 为什么选择 Tailwind CSS?
|
||||
|
||||
Tailwind CSS 提供了几个相比传统 CSS 框架的优势:
|
||||
|
||||
- **实用优先**:直接在 HTML 中应用样式
|
||||
- **高度可定制**:通过配置文件完全控制设计系统
|
||||
- **响应式设计**:内置响应式修饰符
|
||||
- **性能优化**:自动清除未使用的 CSS
|
||||
- **开发者体验**:优秀的 IDE 支持和文档
|
||||
|
||||
## 安装和设置
|
||||
|
||||
让我们从在项目中设置 Tailwind CSS 开始:
|
||||
|
||||
```bash
|
||||
# 使用 npm 安装 Tailwind CSS
|
||||
npm install -D tailwindcss postcss autoprefixer
|
||||
|
||||
# 生成配置文件
|
||||
npx tailwindcss init -p
|
||||
```
|
||||
|
||||
配置你的 `tailwind.config.js`:
|
||||
|
||||
```javascript
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{html,js,ts,jsx,tsx,astro}",
|
||||
"./pages/**/*.{html,js,ts,jsx,tsx,astro}",
|
||||
"./components/**/*.{html,js,ts,jsx,tsx,astro}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
500: '#3b82f6',
|
||||
900: '#1e3a8a',
|
||||
},
|
||||
secondary: {
|
||||
50: '#f0fdf4',
|
||||
500: '#22c55e',
|
||||
900: '#14532d',
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
mono: ['Fira Code', 'monospace'],
|
||||
},
|
||||
spacing: {
|
||||
'18': '4.5rem',
|
||||
'88': '22rem',
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
在你的主 CSS 文件中添加 Tailwind 指令:
|
||||
|
||||
```css
|
||||
/* src/styles/globals.css */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* 自定义基础样式 */
|
||||
@layer base {
|
||||
html {
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-semibold text-gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义组件样式 */
|
||||
@layer components {
|
||||
.btn-primary {
|
||||
@apply bg-primary-500 hover:bg-primary-600 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 构建常见 UI 组件
|
||||
|
||||
### 1. 现代卡片组件
|
||||
|
||||
```html
|
||||
<!-- 产品卡片 -->
|
||||
<div class="group relative bg-white rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden border border-gray-100">
|
||||
<!-- 图片容器 -->
|
||||
<div class="aspect-w-16 aspect-h-9 bg-gray-200 overflow-hidden">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1555041469-a586c61ea9bc?w=400&h=250&fit=crop"
|
||||
alt="产品图片"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
>
|
||||
<!-- 悬浮标签 -->
|
||||
<div class="absolute top-4 left-4">
|
||||
<span class="bg-primary-500 text-white text-xs font-medium px-2 py-1 rounded-full">
|
||||
新品
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm text-gray-500 font-medium">科技产品</span>
|
||||
<div class="flex items-center space-x-1">
|
||||
<svg class="w-4 h-4 text-yellow-400 fill-current" viewBox="0 0 20 20">
|
||||
<path d="M10 15l-5.878 3.09 1.123-6.545L.489 6.91l6.572-.955L10 0l2.939 5.955 6.572.955-4.756 4.635 1.123 6.545z"/>
|
||||
</svg>
|
||||
<span class="text-sm text-gray-600">4.8</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2 group-hover:text-primary-600 transition-colors">
|
||||
智能无线耳机
|
||||
</h3>
|
||||
|
||||
<p class="text-gray-600 text-sm mb-4 line-clamp-2">
|
||||
体验前所未有的音质和舒适度,配备主动降噪技术和长达30小时的电池续航。
|
||||
</p>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-baseline space-x-2">
|
||||
<span class="text-2xl font-bold text-gray-900">¥299</span>
|
||||
<span class="text-sm text-gray-500 line-through">¥399</span>
|
||||
</div>
|
||||
|
||||
<button class="bg-primary-500 hover:bg-primary-600 text-white font-medium px-4 py-2 rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||
加入购物车
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. 响应式导航栏
|
||||
|
||||
```html
|
||||
<!-- 导航栏 -->
|
||||
<nav class="bg-white/80 backdrop-blur-md border-b border-gray-200 sticky top-0 z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<!-- Logo -->
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<img class="h-8 w-auto" src="/logo.svg" alt="Logo">
|
||||
</div>
|
||||
<div class="hidden md:block ml-10">
|
||||
<div class="flex items-baseline space-x-8">
|
||||
<a href="#" class="text-gray-900 hover:text-primary-600 px-3 py-2 text-sm font-medium transition-colors">
|
||||
首页
|
||||
</a>
|
||||
<a href="#" class="text-gray-600 hover:text-primary-600 px-3 py-2 text-sm font-medium transition-colors">
|
||||
产品
|
||||
</a>
|
||||
<a href="#" class="text-gray-600 hover:text-primary-600 px-3 py-2 text-sm font-medium transition-colors">
|
||||
解决方案
|
||||
</a>
|
||||
<a href="#" class="text-gray-600 hover:text-primary-600 px-3 py-2 text-sm font-medium transition-colors">
|
||||
关于我们
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧按钮 -->
|
||||
<div class="hidden md:block">
|
||||
<div class="flex items-center space-x-4">
|
||||
<button class="text-gray-600 hover:text-gray-900 p-2 rounded-lg hover:bg-gray-100 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button class="text-gray-600 hover:text-primary-600 font-medium transition-colors">
|
||||
登录
|
||||
</button>
|
||||
|
||||
<button class="bg-primary-500 hover:bg-primary-600 text-white font-medium px-4 py-2 rounded-lg transition-colors duration-200">
|
||||
开始使用
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 移动端菜单按钮 -->
|
||||
<div class="md:hidden">
|
||||
<button class="text-gray-600 hover:text-gray-900 p-2 rounded-lg hover:bg-gray-100 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 移动端菜单 -->
|
||||
<div class="md:hidden border-t border-gray-200 bg-white">
|
||||
<div class="px-2 pt-2 pb-3 space-y-1">
|
||||
<a href="#" class="block px-3 py-2 text-base font-medium text-gray-900 hover:text-primary-600 hover:bg-gray-50 rounded-lg transition-colors">
|
||||
首页
|
||||
</a>
|
||||
<a href="#" class="block px-3 py-2 text-base font-medium text-gray-600 hover:text-primary-600 hover:bg-gray-50 rounded-lg transition-colors">
|
||||
产品
|
||||
</a>
|
||||
<a href="#" class="block px-3 py-2 text-base font-medium text-gray-600 hover:text-primary-600 hover:bg-gray-50 rounded-lg transition-colors">
|
||||
解决方案
|
||||
</a>
|
||||
<a href="#" class="block px-3 py-2 text-base font-medium text-gray-600 hover:text-primary-600 hover:bg-gray-50 rounded-lg transition-colors">
|
||||
关于我们
|
||||
</a>
|
||||
</div>
|
||||
<div class="pt-4 pb-3 border-t border-gray-200">
|
||||
<div class="px-2 space-y-1">
|
||||
<button class="block w-full text-left px-3 py-2 text-base font-medium text-gray-600 hover:text-primary-600 hover:bg-gray-50 rounded-lg transition-colors">
|
||||
登录
|
||||
</button>
|
||||
<button class="block w-full bg-primary-500 hover:bg-primary-600 text-white font-medium px-3 py-2 rounded-lg transition-colors duration-200">
|
||||
开始使用
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
```
|
||||
|
||||
### 3. 现代按钮变体
|
||||
|
||||
```html
|
||||
<!-- 按钮变体集合 -->
|
||||
<div class="space-y-4">
|
||||
<!-- 主要按钮 -->
|
||||
<button class="bg-primary-500 hover:bg-primary-600 active:bg-primary-700 text-white font-medium px-6 py-3 rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 shadow-lg hover:shadow-xl">
|
||||
主要操作
|
||||
</button>
|
||||
|
||||
<!-- 次要按钮 -->
|
||||
<button class="bg-white hover:bg-gray-50 active:bg-gray-100 text-gray-900 font-medium px-6 py-3 rounded-lg border border-gray-300 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 shadow-sm hover:shadow-md">
|
||||
次要操作
|
||||
</button>
|
||||
|
||||
<!-- 幽灵按钮 -->
|
||||
<button class="text-primary-600 hover:text-primary-700 hover:bg-primary-50 active:bg-primary-100 font-medium px-6 py-3 rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||
幽灵按钮
|
||||
</button>
|
||||
|
||||
<!-- 危险按钮 -->
|
||||
<button class="bg-red-500 hover:bg-red-600 active:bg-red-700 text-white font-medium px-6 py-3 rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 shadow-lg hover:shadow-xl">
|
||||
删除操作
|
||||
</button>
|
||||
|
||||
<!-- 加载状态按钮 -->
|
||||
<button class="bg-primary-500 text-white font-medium px-6 py-3 rounded-lg opacity-75 cursor-not-allowed flex items-center space-x-2" disabled>
|
||||
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span>处理中...</span>
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 高级布局模式
|
||||
|
||||
### 1. CSS Grid 仪表盘布局
|
||||
|
||||
```html
|
||||
<!-- 仪表盘网格布局 -->
|
||||
<div class="min-h-screen bg-gray-50 p-6">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- 页面标题 -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">仪表盘</h1>
|
||||
<p class="text-gray-600">欢迎回来,这是您的数据概览</p>
|
||||
</div>
|
||||
|
||||
<!-- 网格布局 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">总用户数</p>
|
||||
<p class="text-3xl font-bold text-gray-900">12,345</p>
|
||||
<p class="text-sm text-green-600 mt-1">
|
||||
<span class="inline-flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 9.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 7.414V15a1 1 0 11-2 0V7.414L6.707 9.707a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
+12%
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-blue-100 p-3 rounded-lg">
|
||||
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 重复其他统计卡片... -->
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- 图表区域 -->
|
||||
<div class="lg:col-span-2 bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-4">销售趋势</h2>
|
||||
<div class="h-80 bg-gray-100 rounded-lg flex items-center justify-center">
|
||||
<p class="text-gray-500">图表占位符</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<div class="space-y-6">
|
||||
<!-- 最近活动 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">最近活动</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start space-x-3">
|
||||
<div class="bg-green-100 p-1 rounded-full">
|
||||
<div class="w-2 h-2 bg-green-600 rounded-full"></div>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900">新用户注册</p>
|
||||
<p class="text-sm text-gray-500">2分钟前</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 更多活动项... -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速操作 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">快速操作</h3>
|
||||
<div class="space-y-3">
|
||||
<button class="w-full text-left p-3 rounded-lg hover:bg-gray-50 transition-colors">
|
||||
<div class="flex items-center space-x-3">
|
||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-gray-900">添加新项目</span>
|
||||
</div>
|
||||
</button>
|
||||
<!-- 更多快速操作... -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. 响应式 Hero 部分
|
||||
|
||||
```html
|
||||
<!-- Hero 部分 -->
|
||||
<section class="relative bg-gradient-to-br from-primary-50 via-white to-secondary-50 overflow-hidden">
|
||||
<!-- 背景装饰 -->
|
||||
<div class="absolute inset-0">
|
||||
<div class="absolute top-0 left-0 w-40 h-40 bg-primary-200 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob"></div>
|
||||
<div class="absolute top-0 right-0 w-40 h-40 bg-secondary-200 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob animation-delay-2000"></div>
|
||||
<div class="absolute -bottom-8 left-20 w-40 h-40 bg-pink-200 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob animation-delay-4000"></div>
|
||||
</div>
|
||||
|
||||
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24 lg:py-32">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
<!-- 文本内容 -->
|
||||
<div class="text-center lg:text-left">
|
||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight mb-6">
|
||||
构建
|
||||
<span class="bg-gradient-to-r from-primary-600 to-secondary-600 bg-clip-text text-transparent">
|
||||
未来
|
||||
</span>
|
||||
的应用
|
||||
</h1>
|
||||
|
||||
<p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto lg:mx-0">
|
||||
使用我们强大的工具和服务,快速构建、部署和扩展您的应用程序。体验前所未有的开发效率。
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
|
||||
<button class="bg-primary-500 hover:bg-primary-600 text-white font-medium px-8 py-4 rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl transform hover:-translate-y-1">
|
||||
开始免费试用
|
||||
</button>
|
||||
|
||||
<button class="bg-white hover:bg-gray-50 text-gray-900 font-medium px-8 py-4 rounded-xl border border-gray-300 transition-all duration-200 shadow-sm hover:shadow-md flex items-center justify-center space-x-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>观看演示</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 社会证明 -->
|
||||
<div class="mt-12">
|
||||
<p class="text-sm text-gray-500 mb-4">受到全球 10,000+ 开发者信赖</p>
|
||||
<div class="flex items-center justify-center lg:justify-start space-x-8 opacity-60">
|
||||
<!-- 公司 Logo 占位符 -->
|
||||
<div class="w-20 h-8 bg-gray-300 rounded"></div>
|
||||
<div class="w-20 h-8 bg-gray-300 rounded"></div>
|
||||
<div class="w-20 h-8 bg-gray-300 rounded"></div>
|
||||
<div class="w-20 h-8 bg-gray-300 rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片/插图 -->
|
||||
<div class="relative">
|
||||
<div class="relative z-10">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1551434678-e076c223a692?w=600&h=400&fit=crop"
|
||||
alt="应用界面"
|
||||
class="w-full h-auto rounded-2xl shadow-2xl"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 浮动元素 -->
|
||||
<div class="absolute -top-4 -right-4 w-24 h-24 bg-primary-500 rounded-2xl shadow-lg transform rotate-12 opacity-80"></div>
|
||||
<div class="absolute -bottom-4 -left-4 w-32 h-32 bg-secondary-500 rounded-full shadow-lg opacity-60"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
## 实现暗模式
|
||||
|
||||
Tailwind CSS 使实现暗模式变得简单:
|
||||
|
||||
```html
|
||||
<!-- 暗模式切换器 -->
|
||||
<button
|
||||
id="theme-toggle"
|
||||
class="p-2 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<!-- 太阳图标(暗模式时显示) -->
|
||||
<svg class="w-5 h-5 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path>
|
||||
</svg>
|
||||
|
||||
<!-- 月亮图标(亮模式时显示) -->
|
||||
<svg class="w-5 h-5 block dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- 暗模式样式示例 -->
|
||||
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white p-6 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<h2 class="text-2xl font-bold mb-4">暗模式支持</h2>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-4">
|
||||
这个组件在亮模式和暗模式下都能完美工作。
|
||||
</p>
|
||||
|
||||
<button class="bg-primary-500 hover:bg-primary-600 dark:bg-primary-600 dark:hover:bg-primary-700 text-white font-medium px-4 py-2 rounded-lg transition-colors">
|
||||
操作按钮
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
配置暗模式的 JavaScript:
|
||||
|
||||
```javascript
|
||||
// 暗模式切换逻辑
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const html = document.documentElement;
|
||||
|
||||
// 检查本地存储或系统偏好
|
||||
const currentTheme = localStorage.getItem('theme') ||
|
||||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||
|
||||
// 应用主题
|
||||
if (currentTheme === 'dark') {
|
||||
html.classList.add('dark');
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
themeToggle.addEventListener('click', () => {
|
||||
html.classList.toggle('dark');
|
||||
const newTheme = html.classList.contains('dark') ? 'dark' : 'light';
|
||||
localStorage.setItem('theme', newTheme);
|
||||
});
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 清除未使用的 CSS
|
||||
|
||||
Tailwind 自动清除未使用的样式,但你可以优化配置:
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
content: [
|
||||
'./src/**/*.{html,js,ts,jsx,tsx,astro}',
|
||||
// 确保包含所有可能使用 Tailwind 类的文件
|
||||
],
|
||||
// 保护动态生成的类名
|
||||
safelist: [
|
||||
'bg-red-500',
|
||||
'bg-green-500',
|
||||
'bg-blue-500',
|
||||
// 或使用模式
|
||||
{
|
||||
pattern: /bg-(red|green|blue)-(100|200|300|400|500|600|700|800|900)/,
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 创建自定义组件类
|
||||
|
||||
```css
|
||||
/* 在 @layer components 中定义可重用的组件 */
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply font-medium py-2 px-4 rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply btn bg-primary-500 hover:bg-primary-600 text-white focus:ring-primary-500;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply btn bg-white hover:bg-gray-50 text-gray-900 border border-gray-300 focus:ring-primary-500;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 overflow-hidden;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 一致的间距系统
|
||||
|
||||
```html
|
||||
<!-- 使用一致的间距比例 -->
|
||||
<div class="space-y-4"> <!-- 4 = 1rem -->
|
||||
<div class="p-6"> <!-- 6 = 1.5rem -->
|
||||
<h2 class="mb-4"> <!-- 4 = 1rem -->
|
||||
<p class="mb-8"> <!-- 8 = 2rem -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 推荐的间距比例:2, 4, 6, 8, 12, 16, 20, 24 -->
|
||||
```
|
||||
|
||||
### 2. 响应式优先设计
|
||||
|
||||
```html
|
||||
<!-- 从移动端开始,然后添加更大屏幕的样式 -->
|
||||
<div class="
|
||||
grid
|
||||
grid-cols-1
|
||||
sm:grid-cols-2
|
||||
lg:grid-cols-3
|
||||
xl:grid-cols-4
|
||||
gap-4
|
||||
sm:gap-6
|
||||
lg:gap-8
|
||||
">
|
||||
<!-- 内容 -->
|
||||
</div>
|
||||
|
||||
<!-- 文本大小响应式 -->
|
||||
<h1 class="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold">
|
||||
响应式标题
|
||||
</h1>
|
||||
```
|
||||
|
||||
### 3. 语义化颜色使用
|
||||
|
||||
```html
|
||||
<!-- 使用语义化的颜色名称 -->
|
||||
<div class="bg-white text-gray-900"> <!-- 主要内容 -->
|
||||
<div class="bg-gray-50 text-gray-600"> <!-- 次要内容 -->
|
||||
<div class="bg-primary-500 text-white"> <!-- 主要操作 -->
|
||||
<div class="bg-red-500 text-white"> <!-- 危险操作 -->
|
||||
<div class="bg-green-500 text-white"> <!-- 成功状态 -->
|
||||
<div class="bg-yellow-500 text-white"> <!-- 警告状态 -->
|
||||
```
|
||||
|
||||
### 4. 可访问性考虑
|
||||
|
||||
```html
|
||||
<!-- 确保足够的颜色对比度 -->
|
||||
<button class="bg-primary-500 text-white hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
||||
可访问的按钮
|
||||
</button>
|
||||
|
||||
<!-- 为屏幕阅读器提供文本 -->
|
||||
<button class="p-2 rounded-lg hover:bg-gray-100" aria-label="关闭对话框">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- 使用适当的焦点样式 -->
|
||||
<input class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent" type="text">
|
||||
```
|
||||
|
||||
## 与 JavaScript 框架集成
|
||||
|
||||
### React 组件示例
|
||||
|
||||
```jsx
|
||||
// Button.jsx
|
||||
import React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
|
||||
const Button = ({
|
||||
children,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
disabled = false,
|
||||
className = '',
|
||||
...props
|
||||
}) => {
|
||||
const baseClasses = 'font-medium rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2';
|
||||
|
||||
const variants = {
|
||||
primary: 'bg-primary-500 hover:bg-primary-600 text-white focus:ring-primary-500',
|
||||
secondary: 'bg-white hover:bg-gray-50 text-gray-900 border border-gray-300 focus:ring-primary-500',
|
||||
danger: 'bg-red-500 hover:bg-red-600 text-white focus:ring-red-500'
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: 'px-3 py-1.5 text-sm',
|
||||
md: 'px-4 py-2',
|
||||
lg: 'px-6 py-3 text-lg'
|
||||
};
|
||||
|
||||
const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : '';
|
||||
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
baseClasses,
|
||||
variants[variant],
|
||||
sizes[size],
|
||||
disabledClasses,
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
```
|
||||
|
||||
### Vue 组件示例
|
||||
|
||||
```vue
|
||||
<!-- Card.vue -->
|
||||
<template>
|
||||
<div :class="cardClasses">
|
||||
<div v-if="$slots.header" class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div v-if="$slots.footer" class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Card',
|
||||
props: {
|
||||
shadow: {
|
||||
type: String,
|
||||
default: 'md',
|
||||
validator: value => ['sm', 'md', 'lg', 'xl'].includes(value)
|
||||
},
|
||||
rounded: {
|
||||
type: String,
|
||||
default: 'lg',
|
||||
validator: value => ['md', 'lg', 'xl', '2xl'].includes(value)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cardClasses() {
|
||||
return [
|
||||
'bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 overflow-hidden',
|
||||
`shadow-${this.shadow}`,
|
||||
`rounded-${this.rounded}`
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## 调试和开发工具
|
||||
|
||||
### 1. Tailwind CSS IntelliSense
|
||||
|
||||
在 VS Code 中安装 Tailwind CSS IntelliSense 扩展以获得:
|
||||
- 自动完成
|
||||
- 语法高亮
|
||||
- 悬停预览
|
||||
- CSS 类验证
|
||||
|
||||
### 2. 浏览器开发工具
|
||||
|
||||
```html
|
||||
<!-- 添加调试类来可视化布局 -->
|
||||
<div class="debug-screens"> <!-- 显示当前断点 -->
|
||||
<div class="border border-red-500"> <!-- 临时边框 -->
|
||||
<div class="bg-red-100"> <!-- 临时背景色 -->
|
||||
```
|
||||
|
||||
### 3. 配置开发环境
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js - 开发配置
|
||||
module.exports = {
|
||||
// 开发模式下禁用清除以便调试
|
||||
content: process.env.NODE_ENV === 'production'
|
||||
? ['./src/**/*.{html,js,ts,jsx,tsx,astro}']
|
||||
: [],
|
||||
|
||||
theme: {
|
||||
extend: {
|
||||
// 开发时添加调试断点
|
||||
screens: {
|
||||
'debug': '1px',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 结论
|
||||
|
||||
Tailwind CSS 提供了一个强大而灵活的方式来构建现代用户界面。通过遵循这些最佳实践:
|
||||
|
||||
- 使用实用优先的方法进行快速原型设计
|
||||
- 实现一致的设计系统
|
||||
- 优化性能和可访问性
|
||||
- 利用响应式设计原则
|
||||
- 正确实现暗模式支持
|
||||
|
||||
你将能够创建美观、高性能且易于维护的用户界面。记住,Tailwind CSS 的真正力量在于它的可定制性和与现代开发工作流的无缝集成。
|
||||
|
||||
---
|
||||
|
||||
*准备深入了解更多?查看我们关于高级 CSS 动画和微交互的指南。*
|
||||
@@ -1,557 +0,0 @@
|
||||
---
|
||||
title: "使用 Docker 扩展 Node.js 应用"
|
||||
description: "学习如何使用 Docker 容器化 Node.js 应用程序,实现生产环境中的无缝部署和可扩展性。"
|
||||
image: "https://images.unsplash.com/photo-1605745341112-85968b19335b?w=400&h=250&fit=crop&crop=center"
|
||||
date: "2025年4月25日"
|
||||
readTime: "7分钟阅读"
|
||||
tags: ["Node.js", "Docker", "DevOps"]
|
||||
slug: "scaling-nodejs-docker"
|
||||
layout: "../../../../layouts/BlogPostLayout.astro"
|
||||
---
|
||||
|
||||

|
||||
|
||||
Docker 彻底改变了我们部署和扩展应用程序的方式。当与 Node.js 结合使用时,它提供了一个强大的平台来构建可扩展、可维护的应用程序。在本指南中,我们将探索如何容器化 Node.js 应用程序并有效地扩展它们。
|
||||
|
||||
## 为什么为 Node.js 选择 Docker?
|
||||
|
||||
Docker 为 Node.js 应用程序提供了几个优势:
|
||||
|
||||
- **一致性**:开发、测试和生产环境保持一致
|
||||
- **隔离性**:应用程序在隔离的容器中运行
|
||||
- **可扩展性**:通过容器编排轻松实现水平扩展
|
||||
- **可移植性**:在任何支持 Docker 的地方运行
|
||||
- **资源效率**:相比虚拟机更轻量级
|
||||
|
||||
## 为 Node.js 创建 Dockerfile
|
||||
|
||||
让我们从一个基本的 Node.js 应用程序开始,创建一个 Dockerfile:
|
||||
|
||||
```dockerfile
|
||||
# 使用官方 Node.js 运行时作为基础镜像
|
||||
FROM node:18-alpine
|
||||
|
||||
# 设置容器内的工作目录
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# 复制 package.json 和 package-lock.json(如果可用)
|
||||
COPY package*.json ./
|
||||
|
||||
# 安装依赖
|
||||
RUN npm ci --only=production
|
||||
|
||||
# 复制应用程序代码的其余部分
|
||||
COPY . .
|
||||
|
||||
# 创建非 root 用户来运行应用程序
|
||||
RUN addgroup -g 1001 -S nodejs
|
||||
RUN adduser -S nextjs -u 1001
|
||||
|
||||
# 将应用目录的所有权更改为 nodejs 用户
|
||||
RUN chown -R nextjs:nodejs /usr/src/app
|
||||
USER nextjs
|
||||
|
||||
# 暴露应用程序运行的端口
|
||||
EXPOSE 3000
|
||||
|
||||
# 定义运行应用程序的命令
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
## 多阶段构建优化
|
||||
|
||||
对于生产应用程序,使用多阶段构建来减少镜像大小:
|
||||
|
||||
```dockerfile
|
||||
# 构建阶段
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# 复制包文件
|
||||
COPY package*.json ./
|
||||
|
||||
# 安装所有依赖(包括 devDependencies)
|
||||
RUN npm ci
|
||||
|
||||
# 复制源代码
|
||||
COPY . .
|
||||
|
||||
# 构建应用程序(如果有构建步骤)
|
||||
RUN npm run build
|
||||
|
||||
# 生产阶段
|
||||
FROM node:18-alpine AS production
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# 复制包文件
|
||||
COPY package*.json ./
|
||||
|
||||
# 只安装生产依赖
|
||||
RUN npm ci --only=production && npm cache clean --force
|
||||
|
||||
# 从构建阶段复制构建的应用程序
|
||||
COPY --from=builder /usr/src/app/dist ./dist
|
||||
COPY --from=builder /usr/src/app/server.js ./
|
||||
|
||||
# 创建非 root 用户
|
||||
RUN addgroup -g 1001 -S nodejs
|
||||
RUN adduser -S nextjs -u 1001
|
||||
RUN chown -R nextjs:nodejs /usr/src/app
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
## 开发环境的 Docker Compose
|
||||
|
||||
使用 Docker Compose 管理你的开发环境:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- .:/usr/src/app
|
||||
- /usr/src/app/node_modules
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- DATABASE_URL=mongodb://mongo:27017/myapp
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
command: npm run dev
|
||||
|
||||
mongo:
|
||||
image: mongo:5.0
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- mongo_data:/data/db
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=admin
|
||||
- MONGO_INITDB_ROOT_PASSWORD=password
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
mongo_data:
|
||||
redis_data:
|
||||
```
|
||||
|
||||
## 使用 Docker Swarm 进行生产部署
|
||||
|
||||
对于生产扩展,使用 Docker Swarm 或 Kubernetes。这里是一个 Docker Swarm 示例:
|
||||
|
||||
```yaml
|
||||
# docker-compose.prod.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
image: myapp:latest
|
||||
deploy:
|
||||
replicas: 3
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 5s
|
||||
max_attempts: 3
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
failure_action: rollback
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 256M
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=mongodb://mongo:27017/myapp
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
- mongo
|
||||
|
||||
mongo:
|
||||
image: mongo:5.0
|
||||
deploy:
|
||||
replicas: 1
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
volumes:
|
||||
- mongo_data:/data/db
|
||||
networks:
|
||||
- app-network
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=admin
|
||||
- MONGO_INITDB_ROOT_PASSWORD=password
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
deploy:
|
||||
replicas: 1
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./ssl:/etc/nginx/ssl
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
- app
|
||||
|
||||
volumes:
|
||||
mongo_data:
|
||||
external: true
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: overlay
|
||||
```
|
||||
|
||||
## 健康检查和监控
|
||||
|
||||
在你的 Dockerfile 中添加健康检查:
|
||||
|
||||
```dockerfile
|
||||
# 添加到你的 Dockerfile
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD node healthcheck.js
|
||||
```
|
||||
|
||||
创建一个简单的健康检查脚本:
|
||||
|
||||
```javascript
|
||||
// healthcheck.js
|
||||
const http = require('http');
|
||||
|
||||
const options = {
|
||||
host: 'localhost',
|
||||
port: 3000,
|
||||
path: '/health',
|
||||
timeout: 2000
|
||||
};
|
||||
|
||||
const request = http.request(options, (res) => {
|
||||
console.log(`状态: ${res.statusCode}`);
|
||||
if (res.statusCode === 200) {
|
||||
process.exit(0);
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
request.on('error', (err) => {
|
||||
console.log('错误:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
request.end();
|
||||
```
|
||||
|
||||
## 性能优化技巧
|
||||
|
||||
### 1. 使用 .dockerignore
|
||||
|
||||
创建一个 `.dockerignore` 文件来排除不必要的文件:
|
||||
|
||||
```
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
.env
|
||||
.nyc_output
|
||||
coverage
|
||||
.cache
|
||||
```
|
||||
|
||||
### 2. 优化层缓存
|
||||
|
||||
在你的 Dockerfile 中排序命令以最大化缓存效率:
|
||||
|
||||
```dockerfile
|
||||
# 首先复制包文件(变化频率较低)
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
# 最后复制源代码(变化频率较高)
|
||||
COPY . .
|
||||
```
|
||||
|
||||
### 3. 使用 Alpine 镜像
|
||||
|
||||
Alpine Linux 镜像要小得多:
|
||||
|
||||
```dockerfile
|
||||
FROM node:18-alpine # ~40MB
|
||||
# vs
|
||||
FROM node:18 # ~350MB
|
||||
```
|
||||
|
||||
### 4. 实现优雅关闭
|
||||
|
||||
```javascript
|
||||
// server.js
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const server = require('http').createServer(app);
|
||||
|
||||
// 你的应用路由
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Hello World!');
|
||||
});
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
res.status(200).send('OK');
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
server.listen(PORT, () => {
|
||||
console.log(`服务器运行在端口 ${PORT}`);
|
||||
});
|
||||
|
||||
// 优雅关闭
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('收到 SIGTERM,正在优雅关闭');
|
||||
server.close(() => {
|
||||
console.log('进程已终止');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('收到 SIGINT,正在优雅关闭');
|
||||
server.close(() => {
|
||||
console.log('进程已终止');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 监控和日志记录
|
||||
|
||||
使用结构化日志记录和监控:
|
||||
|
||||
```javascript
|
||||
// logger.js
|
||||
const winston = require('winston');
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
)
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
module.exports = logger;
|
||||
```
|
||||
|
||||
## 安全最佳实践
|
||||
|
||||
### 1. 使用非 root 用户
|
||||
|
||||
```dockerfile
|
||||
# 创建专用用户
|
||||
RUN addgroup -g 1001 -S nodejs
|
||||
RUN adduser -S nextjs -u 1001
|
||||
|
||||
# 切换到非 root 用户
|
||||
USER nextjs
|
||||
```
|
||||
|
||||
### 2. 扫描漏洞
|
||||
|
||||
```bash
|
||||
# 使用 Docker 安全扫描
|
||||
docker scan myapp:latest
|
||||
|
||||
# 使用 Snyk 扫描
|
||||
npx snyk test --docker myapp:latest
|
||||
```
|
||||
|
||||
### 3. 使用多阶段构建移除开发依赖
|
||||
|
||||
```dockerfile
|
||||
# 确保生产镜像中没有开发依赖
|
||||
RUN npm ci --only=production
|
||||
```
|
||||
|
||||
## 容器编排策略
|
||||
|
||||
### 1. 负载均衡配置
|
||||
|
||||
```nginx
|
||||
# nginx.conf
|
||||
upstream app {
|
||||
server app:3000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://app;
|
||||
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;
|
||||
}
|
||||
|
||||
location /health {
|
||||
access_log off;
|
||||
proxy_pass http://app/health;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 环境变量管理
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
app:
|
||||
environment:
|
||||
- NODE_ENV=${NODE_ENV:-production}
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- REDIS_URL=${REDIS_URL}
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
env_file:
|
||||
- .env.production
|
||||
```
|
||||
|
||||
### 3. 数据持久化
|
||||
|
||||
```yaml
|
||||
services:
|
||||
mongo:
|
||||
volumes:
|
||||
- mongo_data:/data/db
|
||||
- ./mongo-init:/docker-entrypoint-initdb.d
|
||||
|
||||
volumes:
|
||||
mongo_data:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: /opt/myapp/data
|
||||
```
|
||||
|
||||
## 部署和 CI/CD 集成
|
||||
|
||||
### 1. GitHub Actions 工作流
|
||||
|
||||
```yaml
|
||||
# .github/workflows/deploy.yml
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build Docker image
|
||||
run: docker build -t myapp:${{ github.sha }} .
|
||||
|
||||
- name: Run tests
|
||||
run: docker run --rm myapp:${{ github.sha }} npm test
|
||||
|
||||
- name: Push to registry
|
||||
run: |
|
||||
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
docker push myapp:${{ github.sha }}
|
||||
|
||||
- name: Deploy to production
|
||||
run: |
|
||||
docker service update --image myapp:${{ github.sha }} production_app
|
||||
```
|
||||
|
||||
## 故障排除和调试
|
||||
|
||||
### 1. 容器日志
|
||||
|
||||
```bash
|
||||
# 查看容器日志
|
||||
docker logs -f container_name
|
||||
|
||||
# 查看服务日志(Swarm)
|
||||
docker service logs -f service_name
|
||||
```
|
||||
|
||||
### 2. 进入运行中的容器
|
||||
|
||||
```bash
|
||||
# 进入容器进行调试
|
||||
docker exec -it container_name sh
|
||||
|
||||
# 检查容器资源使用
|
||||
docker stats container_name
|
||||
```
|
||||
|
||||
### 3. 网络调试
|
||||
|
||||
```bash
|
||||
# 检查网络连接
|
||||
docker network ls
|
||||
docker network inspect network_name
|
||||
|
||||
# 测试容器间连接
|
||||
docker exec container1 ping container2
|
||||
```
|
||||
|
||||
## 结论
|
||||
|
||||
Docker 为扩展 Node.js 应用程序提供了一个强大的平台。通过遵循这些最佳实践:
|
||||
|
||||
- 使用多阶段构建优化生产镜像
|
||||
- 实现适当的健康检查和优雅关闭
|
||||
- 使用 Docker Compose 进行开发环境
|
||||
- 利用 Docker Swarm 或 Kubernetes 等编排工具进行生产
|
||||
- 正确监控和记录你的应用程序
|
||||
|
||||
你将能够构建强大、可扩展的 Node.js 应用程序,能够高效地处理生产工作负载。
|
||||
|
||||
记住,容器化只是可扩展架构的一部分。考虑实现负载均衡、缓存策略和数据库优化以实现完整的可扩展性。
|
||||
|
||||
---
|
||||
|
||||
*准备部署了吗?查看我们关于 Kubernetes 部署策略的指南,了解更高级的扩展技术。*
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user