Skip to content

使用 tailwind css 重构组件

在上一节课中,我们学习了如何使用 Props 让 PracticeCard 组件接收动态数据,从而展示不同的练习内容。现在,我们的卡片虽然功能完善,但在视觉上还比较朴素。

本节课,我们将引入强大的 Tailwind CSS 框架,对我们的“作业展示首页”进行全面的样式优化。我们将从重新初始化一个支持 Tailwind 的 Next.js 项目开始,然后逐步重构全局导航条和 PracticeCard 组件。整个过程,我们会特别关注认知负荷理论 (CLT) 的应用,力求让学习过程更平滑,成就感更强。

学习目标:

  1. 学会在创建 Next.js 项目时集成 Tailwind CSS。
  2. 理解如何将现有组件和页面结构迁移到新的 Tailwind 项目中。
  3. 应用认知负荷理论的原则,有条理地使用 Tailwind CSS 重构UI组件。
  4. 掌握使用 Tailwind 工具类构建响应式导航条。
  5. 能够用 Tailwind CSS 美化 PracticeCard 组件,并根据 Props(如 completed 状态)应用条件样式。
  6. 体验 Tailwind CSS 带来的开发效率和样式一致性的提升。

1. 重新初始化项目并集成 Tailwind CSS

为了确保 Tailwind CSS 从一开始就正确配置,我们将创建一个新的 Next.js 项目,并在创建过程中选择集成 Tailwind CSS。

步骤 1: 创建新项目

打开你的终端,运行以下命令:

Terminal window
npx create-next-app@latest nextjs-tailwind-homework

在安装过程中,create-next-app 会询问你一系列问题。请确保:

  • Would you like to use TypeScript? (No,本示例将使用 JavaScript)
  • Would you like to use ESLint? (No)
  • Would you like to use Tailwind CSS? 务必选择 Yes!
  • Would you like to use src/ directory? (Yes,推荐)
  • Would you like to use App Router? (recommended) (Yes)
  • Would you like to customize the default import alias? (No,或根据你的喜好设置)

完成后,进入新项目目录:

Terminal window
cd nextjs-tailwind-homework

步骤 2: 迁移现有代码

现在,我们需要将上节课的 PracticeCard 组件和 app/page.js 的内容复制到新项目中。

  1. 创建 PracticeCard 组件:nextjs-tailwind-homework/src/components/ 目录下创建一个新文件 PracticeCard.js。 将上节课 PracticeCard.js 的代码(包含 Props 解构的版本)复制粘贴到这个新文件中。 目前,它可能还包含一些内联样式或者简单的 className

    // src/components/PracticeCard.js (初始版本,可能带内联样式)
    export default function PracticeCard({ title, description, date, completed }) {
    return (
    <div className="practice-card" style={{ border: `2px solid ${completed ? 'green' : '#eee'}`, padding: '16px', margin: '10px 0', borderRadius: '8px' }}>
    <h2>{title}</h2>
    <p>{description}</p>
    <p>日期: {date}</p>
    <p>状态: {completed ? '已完成' : '未完成'}</p>
    </div>
    );
    }
  2. 更新首页 app/page.js: 打开 nextjs-tailwind-homework/src/app/page.js。 用上节课 app/page.js 的内容替换它,确保导入 PracticeCard 的路径正确 (@/components/PracticeCard)。

    // src/app/page.js (初始版本)
    import PracticeCard from '@/components/PracticeCard';
    export default function HomePage() {
    const weekOneTitle = "第一周:组件基础";
    const practiceDate = new Date().toLocaleDateString();
    return (
    <div> {/* 稍后我们会用 Tailwind 优化这个外层 div */}
    <h1>每周练习</h1> {/* 稍后我们会用 Tailwind 优化这个标题 */}
    <PracticeCard
    title={weekOneTitle}
    description="学习 React 组件的核心概念和 JSX 基础。"
    date={practiceDate}
    completed={false}
    />
    <PracticeCard
    title="第二周:Props 与 State"
    description="掌握组件间数据传递和内部状态管理。"
    date={practiceDate}
    completed={true}
    />
    <PracticeCard
    title="第三周:事件处理"
    description="让你的组件响应用户交互。"
    date={practiceDate}
    completed={false}
    />
    </div>
    );
    }

步骤 3: 检查 Tailwind 配置

Next.js 已经为我们配置好了 Tailwind:

  • tailwind.config.js: Tailwind 的主配置文件。
    tailwind.config.js
    /** @type {import('tailwindcss').Config} */
    module.exports = {
    content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
    ],
    theme: {
    extend: {
    // 可以在这里扩展主题
    },
    },
    plugins: [],
    }
  • src/app/globals.css: 包含了 Tailwind 的基础指令。
    src/app/globals.css
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    /* 你也可以在这里添加一些全局基础样式 */
    body {
    /* 例如,设置一个默认的背景色 */
    /* background-color: #f3f4f6; /* 这是一个浅灰色 */
    }

步骤 4: 启动开发服务器

Terminal window
npm run dev

访问 http://localhost:3000。你会看到 Next.js 的默认欢迎页(因为我们还没修改 layout.js)或者一个非常朴素的、带有我们卡片内容的页面。我们的目标就是用 Tailwind 来美化它。

2. 应用认知负荷理论 (CLT) 进行重构

在开始用 Tailwind CSS 重构之前,我们回顾一下如何应用认知负荷理论来优化学习:

  1. 分解任务 (Intrinsic Load Management): 我们不会一次性重构整个页面。而是先处理导航条,再处理卡片,最后调整整体布局。每个组件的样式也会分步进行。
  2. 减少无关干扰 (Extraneous Load Minimization): Tailwind 的工具类直接应用于 JSX,减少了在 CSS 文件和 JS 文件间切换的认知成本。我们将专注于理解和应用这些类,而不是复杂的 CSS 规则或命名。
  3. 促进知识构建 (Germane Load Optimization): 每应用一组 Tailwind 类,我们都会立即观察其效果,并将这些原子化的类与它们产生的视觉变化联系起来,从而构建对 Tailwind 工作方式的理解。

3. 重构全局导航条 (Layout)

通常,一个应用会有一个全局导航条。我们将在 src/app/layout.js 中添加一个简单的导航条,并用 Tailwind 美化它。

步骤 1: 修改 src/app/layout.js

src/app/layout.js
import './globals.css' // 确保 Tailwind 指令被引入
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: '每周作业平台',
description: '使用 Next.js 和 Tailwind CSS 构建',
}
// 简单的导航条组件
function Navbar() {
return (
<nav className="bg-slate-800 text-white p-4 shadow-md">
<div className="container mx-auto flex justify-between items-center">
<a href="/" className="text-xl font-bold hover:text-slate-300 transition-colors">
作业平台
</a>
<div className="space-x-4">
<a href="/" className="hover:text-slate-300 transition-colors">首页</a>
<a href="/archive" className="hover:text-slate-300 transition-colors">归档</a>
{/* 可以添加更多链接 */}
</div>
</div>
</nav>
);
}
export default function RootLayout({ children }) {
return (
<html lang="zh-CN">
<body className={`${inter.className} bg-slate-50`}> {/* 给 body 添加一个背景色 */}
<Navbar /> {/* 添加导航条 */}
<main> {/* children 会被渲染在这里 */}
{children}
</main>
{/* 你也可以在这里添加 Footer */}
</body>
</html>
)
}

代码讲解 (Navbar):

  • <nav className="bg-slate-800 text-white p-4 shadow-md">:
    • bg-slate-800: 设置导航条背景为深蓝灰色 (Slate 800)。
    • text-white: 设置默认文字颜色为白色。
    • p-4: 内边距 1rem (16px)。
    • shadow-md: 添加一个中等大小的阴影。
  • <div className="container mx-auto flex justify-between items-center">:
    • container: 一个 Tailwind 提供的类,用于设置最大宽度并居中内容,响应式。
    • mx-auto: 水平居中(当 container 类不完全适用或需要自定义断点时)。
    • flex: 启用 Flexbox 布局。
    • justify-between: 项目在主轴上两端对齐(Logo 和链接分开)。
    • items-center: 项目在交叉轴上居中对齐。
  • <a href="/" className="text-xl font-bold hover:text-slate-300 transition-colors">:
    • text-xl: 字体大小。
    • font-bold: 字体加粗。
    • hover:text-slate-300: 鼠标悬停时文字颜色变为浅灰色。
    • transition-colors: 为颜色变化添加平滑过渡效果。
  • <div className="space-x-4">:
    • space-x-4: 为直接子元素之间添加水平间距 (1rem)。这是一个非常方便的布局工具。

保存文件,现在你的页面顶部应该出现了一个美观的导航条了!

强烈建议在你的代码编辑器 (如 VS Code) 中安装 “Tailwind CSS IntelliSense” 插件。它会提供类名自动补全、预览等功能,极大提升开发效率。

4. 用 Tailwind 重构 PracticeCard 组件

现在轮到我们的核心组件 PracticeCard.js 了。我们将移除之前的内联样式,完全用 Tailwind 工具类来替代。

修改 src/components/PracticeCard.js:

src/components/PracticeCard.js
export default function PracticeCard({ title, description, date, completed }) {
// 根据 completed 状态决定边框和一些文本颜色
const cardClasses = `
bg-white
rounded-lg
shadow-lg
p-6
m-4
transition-all
duration-300
ease-in-out
hover:shadow-xl
border-2
${completed ? 'border-green-500' : 'border-slate-200'}
`;
const titleClasses = `
text-xl
font-semibold
mb-2
${completed ? 'text-green-700' : 'text-slate-800'}
`;
const statusTextClasses = `
text-sm
font-medium
${completed ? 'text-green-600' : 'text-orange-500'}
`;
return (
<div className={cardClasses}>
<h2 className={titleClasses}>{title}</h2>
<p className="text-slate-600 text-sm mb-3 min-h-[40px]">{description}</p>
<p className="text-xs text-slate-400 mb-3">日期: {date}</p>
<p className={statusTextClasses}>
状态: {completed ? '已完成 ✔' : '未完成 ⏳'}
</p>
</div>
);
}

代码讲解 (PracticeCard.js):

  • cardClasses (卡片容器 div):
    • bg-white: 白色背景。
    • rounded-lg: 圆角。
    • shadow-lg: 较大的阴影。
    • p-6: 内边距 (1.5rem)。
    • m-4: 外边距 (1rem)。
    • transition-all duration-300 ease-in-out: 为所有可过渡属性添加 300ms 的缓动过渡。
    • hover:shadow-xl: 鼠标悬停时阴影变大。
    • border-2: 2px 边框宽度。
    • ${completed ? 'border-green-500' : 'border-slate-200'}: 条件样式! 如果 completedtrue,边框为绿色;否则为浅灰色。这是 Tailwind 结合 JavaScript 动态性的强大之处。
  • titleClasses (标题 h2):
    • text-xl font-semibold mb-2: 字体大小、加粗、底部外边距。
    • ${completed ? 'text-green-700' : 'text-slate-800'}: 标题颜色也根据 completed 状态变化。
  • 描述 (p):
    • text-slate-600 text-sm mb-3: 文字颜色、大小、底部外边距。
    • min-h-[40px]: 设置一个最小高度,让卡片在描述内容长短不一时也能大致对齐。[] 允许你使用任意值。
  • 日期 (p):
    • text-xs text-slate-400 mb-3: 更小的字体,浅灰色,底部外边距。
  • statusTextClasses (状态 p):
    • text-sm font-medium: 字体大小、中等粗细。
    • ${completed ? 'text-green-600' : 'text-orange-500'}: 状态文字颜色,已完成为绿色,未完成为橙色,并添加了小图标增强视觉。

将动态类名(如基于 completed 状态的类)单独提取到变量中,或者直接在 className 中使用模板字符串,可以使 JSX 更整洁。对于复杂的条件,前者更易读。

5. 优化首页布局 (app/page.js)

最后,我们来调整 app/page.js 的整体布局,让卡片排列更美观。

修改 src/app/page.js:

src/app/page.js
import PracticeCard from '@/components/PracticeCard';
export default function HomePage() {
const weekOneTitle = "第一周:组件基础";
const practiceDate = new Date().toLocaleDateString();
const practices = [
{
id: 1,
title: weekOneTitle,
description: "学习 React 组件的核心概念和 JSX 基础。",
date: practiceDate,
completed: false,
},
{
id: 2,
title: "第二周:Props 与 State",
description: "掌握组件间数据传递和内部状态管理。",
date: practiceDate,
completed: true,
},
{
id: 3,
title: "第三周:事件处理与列表渲染",
description: "让你的组件响应用户交互,并学习如何高效渲染列表数据。",
date: practiceDate,
completed: false,
},
{
id: 4,
title: "第四周:Tailwind CSS 集成",
description: "使用 Tailwind CSS 快速美化你的 Next.js 应用。",
date: practiceDate,
completed: true,
}
];
return (
// 使用 Flexbox 居中内容,并添加上下内边距
<div className="container mx-auto p-4 sm:p-6 lg:p-8">
<h1 className="text-3xl sm:text-4xl font-bold text-slate-800 mb-8 text-center">
每周练习进度
</h1>
{/* 使用 Grid 布局来排列卡片 */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{practices.map((practice) => (
<PracticeCard
key={practice.id} // 列表渲染时,key 是必须的
title={practice.title}
description={practice.description}
date={practice.date}
completed={practice.completed}
/>
))}
</div>
</div>
);
}

代码讲解 (app/page.js):

  • 数据结构化: 我们将练习数据放到了一个 practices 数组中,这更符合实际应用场景,便于使用 map 方法渲染。
  • 最外层 div:
    • container mx-auto: 限制内容最大宽度并居中。
    • p-4 sm:p-6 lg:p-8: 响应式的内边距。在小屏幕上是 p-4,在中等屏幕 (sm) 及以上是 p-6,在大屏幕 (lg) 及以上是 p-8
  • 标题 h1:
    • text-3xl sm:text-4xl: 响应式的字体大小。
    • font-bold text-slate-800 mb-8 text-center: 加粗、颜色、下边距、文本居中。
  • 卡片容器 div (Grid 布局):
    • grid: 启用 Grid 布局。
    • grid-cols-1 sm:grid-cols-2 lg:grid-cols-3: 响应式列数!
      • 默认(移动端优先):1 列。
      • sm: (small breakpoint, 640px+): 2 列。
      • lg: (large breakpoint, 1024px+): 3 列。
    • gap-6: 设置网格项之间的间距 (1.5rem)。
  • .map() 渲染:
    • 我们使用 practices.map() 来遍历数据并为每个练习渲染一个 PracticeCard 组件。
    • key={practice.id}: 在 React 中进行列表渲染时,为每个列表项提供一个唯一的 key prop 至关重要,有助于 React 高效更新 DOM。

现在,刷新你的浏览器。你应该能看到一个包含导航条、居中标题和响应式网格布局卡片的漂亮页面了!尝试调整浏览器窗口大小,观察卡片是如何根据屏幕宽度自动调整列数的。

6. 总结与回顾

通过本节课的实践,我们完成了:

  1. 项目初始化: 成功创建了一个集成了 Tailwind CSS 的 Next.js 项目。
  2. 组件迁移与重构: 将已有的 PracticeCard 组件和首页内容迁移,并使用 Tailwind CSS 进行了彻底的样式重构。
  3. 布局实现: 构建了全局导航条和响应式的卡片网格布局。
  4. 条件样式: 学会了根据 Props 动态改变 Tailwind 类,实现条件化样式。
  5. CLT 应用: 通过分解任务、减少上下文切换、即时反馈等方式,力求学习过程更高效。

Tailwind CSS 的核心优势在于其原子化工具优先的理念。它让我们能够直接在 JSX 中快速构建复杂界面,而无需编写大量自定义 CSS。这不仅提升了开发效率,还有助于保持项目样式的一致性和可维护性。

下一步:

  • 继续探索 Tailwind CSS 官方文档,了解更多工具类。
  • 尝试为你的组件添加更多交互效果,如 :focus, :active 状态。
  • 研究如何在 tailwind.config.js 中自定义主题(颜色、间距、字体等)以满足特定设计需求。

希望你享受使用 Tailwind CSS 的过程!