feat(blog): add blog posts and typography plugin
Add new blog posts covering React Hooks, Docker with Node.js, Tailwind CSS, and TypeScript best practices. Implement Tailwind Typography plugin for better blog post styling. Create blog post listing pages in both English and Chinese. Update BlogLayout to use prose styling for improved readability. The changes include: - Added 4 new blog posts in English and Chinese versions - Integrated @tailwindcss/typography plugin - Created blog post index pages - Updated BlogLayout with typography-focused styling - Added related dependencies to package.json
This commit is contained in:
22
src/pages/zh/blog/posts/index.astro
Normal file
22
src/pages/zh/blog/posts/index.astro
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
import BlogLayout from '../../../../layouts/BlogLayout.astro';
|
||||
import type { Lang } from '../../../../i18n/utils';
|
||||
|
||||
const lang = (Astro.currentLocale || 'zh') as Lang;
|
||||
---
|
||||
|
||||
<BlogLayout title="博客文章">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1 class="text-4xl font-bold text-center mb-8">所有博客文章</h1>
|
||||
<p class="text-center text-muted-foreground mb-8">
|
||||
浏览按分类和主题组织的所有博客文章。
|
||||
</p>
|
||||
|
||||
<!-- 此页面可用于其他博客组织功能 -->
|
||||
<div class="text-center">
|
||||
<a href="/zh/blog" class="text-primary hover:underline">
|
||||
← 返回博客首页
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</BlogLayout>
|
||||
476
src/pages/zh/blog/posts/mastering-react-hooks.md
Normal file
476
src/pages/zh/blog/posts/mastering-react-hooks.md
Normal file
@@ -0,0 +1,476 @@
|
||||
---
|
||||
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", "前端"]
|
||||
slug: "mastering-react-hooks"
|
||||
layout: "../../../../layouts/BlogLayout.astro"
|
||||
---
|
||||
|
||||
# 精通 React Hooks:深入探索
|
||||
|
||||

|
||||
|
||||
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 模式和性能优化的其他文章。*
|
||||
796
src/pages/zh/blog/posts/modern-ui-tailwind.md
Normal file
796
src/pages/zh/blog/posts/modern-ui-tailwind.md
Normal file
@@ -0,0 +1,796 @@
|
||||
---
|
||||
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/UX"]
|
||||
slug: "modern-ui-tailwind"
|
||||
layout: "../../../../layouts/BlogLayout.astro"
|
||||
---
|
||||
|
||||
# 使用 Tailwind CSS 构建现代 UI
|
||||
|
||||

|
||||
|
||||
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 动画和微交互的指南。*
|
||||
559
src/pages/zh/blog/posts/scaling-nodejs-docker.md
Normal file
559
src/pages/zh/blog/posts/scaling-nodejs-docker.md
Normal file
@@ -0,0 +1,559 @@
|
||||
---
|
||||
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/BlogLayout.astro"
|
||||
---
|
||||
|
||||
# 使用 Docker 扩展 Node.js 应用
|
||||
|
||||

|
||||
|
||||
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 部署策略的指南,了解更高级的扩展技术。*
|
||||
1142
src/pages/zh/blog/posts/typescript-best-practices.md
Normal file
1142
src/pages/zh/blog/posts/typescript-best-practices.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user