Dividir componentes — Props y callbacks de eventos

La componentización es la idea de diseño más central de React. En este capítulo aprenderás a dividir la página en pequeños componentes reutilizables y a implementar la comunicación entre padre e hijo mediante Props y funciones callback.


¿Por qué dividir componentes?

App.jsx se vuelve cada vez más largo, con cientos de líneas de código apiñadas, difíciles de mantener y reutilizar.

Principios para dividir componentes:Cada componente se encarga de una sola cosa; combinados, realizan funciones complejas.。

¿Qué granularidad es adecuada?

Área de la página¿Debería dividirse en componentes?Razón
Barra de navegaciónSíToda página la necesita; función independiente
Tarjeta de artículoSíCada artículo de la lista tiene la misma estructura
Grupo de botones de filtro por categoríaSíTiene su propio estado e interacción
Contenedor de envoltura simplenoSolo para maquetación, sin comportamiento independiente

De padre a hijo: Props

En React,Props son los parámetros de la función.—Cuando el componente padre llama al componente hijo, le pasa los datos a través de los atributos.

Ejemplo

// Ruta del archivo: src/components/BlogCard.jsx
// Props es el primer parámetro de la función; normalmente se usa con desestructuración
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

El componente padre pasa los datos como si escribiera atributos HTML:

Ejemplo

// Ruta del archivo: src/App.jsx
import BlogCard from './components/BlogCard'

function App() {
  const articles = [
    { id: 1, title: 'Introducción a React', summary: 'Aprende React desde cero', date: '2024-05-10', category: 'React' },
    { id: 2, title: 'Explicación detallada de Promise', summary: 'Entendiendo la asincronía', 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 esflujo de datos unidireccional: los datos fluyen del componente padre al hijo. El componente hijo no puede modificar los Props recibidos. Si necesita modificarlos, el componente padre debe manejarlo mediante una función callback.


De hijo a padre: funciones callback

Cuando el componente hijo necesita notificar al padre «ha ocurrido algo», el padre debe pasar unafunción callbackcomo Props.

Ejemplo

// Ruta del archivo: src/components/CategoryFilter.jsx
function CategoryFilter({ categories, activeCategory, onCategoryChange }) {
  return (
    <div className="filter-bar">
      {categories.map(cat => (
        <button
          key={cat}
          className={activeCategory === cat ? 'active' : ''}
          // Al hacer clic, llama a la función callback pasada por el componente padre
          onClick={() => onCategoryChange(cat)}
        >
          {cat}
        </button>
      ))}
    </div>
  )
}

export default CategoryFilter

El componente padre pasa la función callback:

Ejemplo

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

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

  // Función callback: responde al evento de clic del componente hijo
  function handleCategoryChange(cat) {
    setActiveCategory(cat)
  }

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

Comparación de comunicación entre React y Vue3

EscenarioVue3React
De padre a hijoProps(defineProps)Props (parámetros de función)
De hijo a padreEvento emitProps función callback
Dirección de los datosUnidireccionalUnidireccional

Vue3 usa el mecanismo emit, React usa funciones callback: en esencia es lo mismo, solo cambia la sintaxis.


Desestructuración de Props y valores predeterminados

Ejemplo

// Props con valores predeterminados
function BlogCard({
  title = 'Artículo sin nombre',        // Valores predeterminados de desestructuración ES6
  summary = 'Sin resumen',
  date = '',
  category = 'Sin categoría'
}) {
  return (
    <div className="card">
      <span className="tag">{category}</span>
      <h3>{title}</h3>
      <p>{summary}</p>
      {date && <span className="date">{date}</span>}
    </div>
  )
}

Manos a la obra: dividir en tres componentes

La estructura de componentes del proyecto de blog es la siguiente:

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

NavBar.jsx

Ejemplo

// Ruta del archivo: src/components/NavBar.jsx
import '../App.css'

function NavBar() {
  return (
    <header className="navbar">
      <a href="/" className="logo">EJEMPLO Blog</a>
      <nav>
        <a href="/">Inicio</a>
        <a href="#">Acerca de</a>
      </nav>
    </header>
  )
}

export default NavBar

App.jsx — Integrando todos los componentes

Ejemplo

// Ruta del archivo: 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: 'Guía completa de introducción a React', summary: 'Aprende React Hooks desde cero', date: '2024-05-10', category: 'React' },
    { id: 2, title: 'Explicación detallada de programación asíncrona en JS', summary: 'Entiende Promise y async/await', date: '2024-05-08', category: 'JavaScript' },
    { id: 3, title: 'Práctica de diseño con CSS Grid', summary: 'Maquetación responsive con Grid', date: '2024-05-05', category: 'CSS' },
    { id: 4, title: 'React Hooks en profundidad', summary: 'Comprensión profunda de useState y useEffect', date: '2024-05-03', category: 'React' },
    { id: 5, title: 'Guía completa de Flexbox', summary: 'Aprende maquetación flexible en un artículo', date: '2024-05-01', category: 'CSS' },
  ])

  const [activeCategory, setActiveCategory] = useState('Todos')

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

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

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

      <main className="container">
        <h2 className="section-title">Artículos recientes</h2>

        {/* Componente hijo: recibe props (categories, activeCategory) y callback (onCategoryChange) */}
        <CategoryFilter
          categories={categories}
          activeCategory={activeCategory}
          onCategoryChange={setActiveCategory}
        />

        <p className="result-info">común{filteredArticles.length}artículo</p>

        {filteredArticles.length === 0 ? (
          <p className="empty-tip">No hay artículos en esta categoría</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 EJEMPLO Blog. Powered by React.</p>
      </footer>
    </div>
  )
}

export default App

Resumen del capítulo

En este capítulo has dominado la esencia de la comunicación entre componentes de React: de padre a hijo mediante Props (parámetros de función), y de hijo a padre mediante Props como función callback.

Los tres componentes del proyecto de blog, NavBar, BlogCard y CategoryFilter, ya están en su lugar, y la estructura del código es mucho más clara.

Otras extensiones