菜鸟教程 -- 学的不仅是技术,更是梦想!

React 教程
(追記) (追記ここまで)

拆分组件 — Props 与事件回调

组件化是 React 最核心的设计思想。本章你将学会把页面拆成可复用的小组件,通过 Props 和回调函数实现父子通信。


为什么要拆组件?

App.jsx 写得越来越长,上百行代码挤在一起,难以维护和复用。

拆组件的原则:每个组件只负责一件事,组合起来完成复杂功能

什么粒度合适?

页面区域是否该拆成组件理由
导航栏每个页面都需要,独立功能
文章卡片列表里每篇都是同一个结构
分类筛选按钮组有自己的状态和交互
简单包裹容器只是为了布局,没有独立行为

父传子:Props

在 React 中,Props 就是函数的参数——父组件调用子组件时,把数据通过属性传进去。

实例

// 文件路径:src/components/BlogCard.jsx
// Props 即函数的第一个参数,通常解构使用
function BlogCard({ title, summary, date, category }) {
return (
<div className="card">
<span className="tag">{category}</span>
<h3>{title}</h3>
<p>{summary}</p>
<span className="date">{date}</span>
</div>
)
}

export default BlogCard

父组件像写 HTML 属性一样把数据传进去:

实例

// 文件路径:src/App.jsx
import BlogCard from './components/BlogCard'

function App() {
const articles = [
{ id: 1, title: 'React 入门', summary: '从零学 React', date: '2024-05-10', category: 'React' },
{ id: 2, title: 'Promise 详解', summary: '搞懂异步', date: '2024-05-08', category: 'JavaScript' },
]

return (
<div className="article-grid">
{articles.map(article => (
<BlogCard
key={article.id}
title={article.title}
summary={article.summary}
date={article.date}
category={article.category}
/>
))}
</div>
)
}

Props 是单向数据流:数据从父组件流向子组件。子组件不能修改接收到的 Props。如果需要修改,应该由父组件通过回调函数来处理。


子传父:回调函数

子组件要通知父组件「发生了什么事」,父组件需要传一个回调函数作为 Props。

实例

// 文件路径:src/components/CategoryFilter.jsx
function CategoryFilter({ categories, activeCategory, onCategoryChange }) {
return (
<div className="filter-bar">
{categories.map(cat => (
<button
key={cat}
className={activeCategory === cat ? 'active' : ''}
// 点击时调用父组件传来的回调函数
onClick={() => onCategoryChange(cat)}
>
{cat}
</button>
))}
</div>
)
}

export default CategoryFilter

父组件传递回调函数:

实例

import { useState } from 'react'
import CategoryFilter from './components/CategoryFilter'

function App() {
const [activeCategory, setActiveCategory] = useState('全部')
const categories = ['全部', 'React', 'JavaScript', 'CSS']

// 回调函数:响应子组件的点击事件
function handleCategoryChange(cat) {
setActiveCategory(cat)
}

return (
<CategoryFilter
categories={categories}
activeCategory={activeCategory}
onCategoryChange={handleCategoryChange}
/>
)
}

React 与 Vue3 的通信对比

场景Vue3React
父传子Props(defineProps)Props(函数参数)
子传父emit 事件Props 回调函数
数据方向单向单向

Vue3 用 emit 机制,React 用回调函数——本质一样,只是语法不同。


Props 解构与默认值

实例

// 带默认值的 Props
function BlogCard({
title = '未命名文章', // ES6 解构默认值
summary = '暂无摘要',
date = '',
category = '未分类'
}) {
return (
<div className="card">
<span className="tag">{category}</span>
<h3>{title}</h3>
<p>{summary}</p>
{date && <span className="date">{date}</span>}
</div>
)
}

动手:拆出三个组件

博客项目的组件结构如下:

src/
├── components/
│ ├── NavBar.jsx # 顶部导航栏
│ ├── BlogCard.jsx # 单篇文章卡片
│ └── CategoryFilter.jsx # 分类筛选按钮组
└── App.jsx

NavBar.jsx

实例

// 文件路径:src/components/NavBar.jsx
import '../App.css'

function NavBar() {
return (
<header className="navbar">
<a href="/" className="logo">RUNOOB Blog</a>
<nav>
<a href="/">首页</a>
<a href="#">关于</a>
</nav>
</header>
)
}

export default NavBar

App.jsx — 整合所有组件

实例

// 文件路径:src/App.jsx
import { useState, useMemo } from 'react'
import NavBar from './components/NavBar'
import BlogCard from './components/BlogCard'
import CategoryFilter from './components/CategoryFilter'
import './App.css'

function App() {
const [articles] = useState([
{ id: 1, title: 'React 入门完全指南', summary: '从零开始学习 React Hooks', date: '2024-05-10', category: 'React' },
{ id: 2, title: 'JS 异步编程详解', summary: '搞懂 Promise 和 async/await', date: '2024-05-08', category: 'JavaScript' },
{ id: 3, title: 'CSS Grid 布局实战', summary: '用 Grid 实现响应式布局', date: '2024-05-05', category: 'CSS' },
{ id: 4, title: 'React Hooks 深入', summary: '深入理解 useState 和 useEffect', date: '2024-05-03', category: 'React' },
{ id: 5, title: 'Flexbox 完全指南', summary: '一文学会弹性布局', date: '2024-05-01', category: 'CSS' },
])

const [activeCategory, setActiveCategory] = useState('全部')

const categories = useMemo(() => {
const cats = articles.map(a => a.category)
return ['全部', ...new Set(cats)]
}, [articles])

const filteredArticles = useMemo(() => {
if (activeCategory === '全部') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory])

return (
<div className="app">
<NavBar />

<main className="container">
<h2 className="section-title">最新文章</h2>

{/* 子组件:接收 props(categories, activeCategory)和回调(onCategoryChange) */}
<CategoryFilter
categories={categories}
activeCategory={activeCategory}
onCategoryChange={setActiveCategory}
/>

<p className="result-info">{filteredArticles.length}</p>

{filteredArticles.length === 0 ? (
<p className="empty-tip">该分类下暂无文章</p>
) : (
<div className="article-grid">
{filteredArticles.map(article => (
<BlogCard
key={article.id}
title={article.title}
summary={article.summary}
date={article.date}
category={article.category}
/>
))}
</div>
)}
</main>

<footer className="footer">
<p>© 2024 RUNOOB Blog. Powered by React.</p>
</footer>
</div>
)
}

export default App

本章小结

本章你掌握了 React 组件通信的核心:父传子通过 Props(函数参数),子传父通过 Props 回调函数。

博客项目的三个组件 NavBar、BlogCard、CategoryFilter 已就位,代码结构清晰了很多。

AI 思考中...

点我分享笔记

  • 昵称 (必填)
  • 邮箱 (必填)
  • 引用地址

AltStyle によって変換されたページ (->オリジナル) /