16. React动态渲染列表

初始代码(点击展开)
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";

const pizzaData = [
  {
    name: "Focaccia",
    ingredients: "Bread with italian olive oil and rosemary",
    price: 6,
    photoName: "pizzas/focaccia.jpg",
    soldOut: false,
  },
  {
    name: "Pizza Margherita",
    ingredients: "Tomato and mozarella",
    price: 10,
    photoName: "pizzas/margherita.jpg",
    soldOut: false,
  },
  {
    name: "Pizza Spinaci",
    ingredients: "Tomato, mozarella, spinach, and ricotta cheese",
    price: 12,
    photoName: "pizzas/spinaci.jpg",
    soldOut: false,
  },
  {
    name: "Pizza Funghi",
    ingredients: "Tomato, mozarella, mushrooms, and onion",
    price: 12,
    photoName: "pizzas/funghi.jpg",
    soldOut: false,
  },
  {
    name: "Pizza Salamino",
    ingredients: "Tomato, mozarella, and pepperoni",
    price: 15,
    photoName: "pizzas/salamino.jpg",
    soldOut: true,
  },
  {
    name: "Pizza Prosciutto",
    ingredients: "Tomato, mozarella, ham, aragula, and burrata cheese",
    price: 18,
    photoName: "pizzas/prosciutto.jpg",
    soldOut: false,
  },
];

function App() {
  return (
    <div className="container">
      <Header />
      <Menu />
      <Footer />
    </div>
  );
}

function Header() {
  const style = {};

  return (
    <header className="header">
      <h1 style={style}>Fast React Pizza Co.</h1>
    </header>
  );
}

function Menu() {
  return (
    <main className="menu">
      <h2>Our Menu</h2>
      <Pizza
        name="Pizza Spinaci"
        ingredient="Tomato, mozarella, spinach, and ricotta cheese"
        photoName="pizzas/spinaci.jpg"
        price={10}
      />
      <Pizza
        name="Pizza Funghi"
        ingredient="Tomato, mushrooms"
        price={12}
        photoName="pizzas/funghi.jpg"
      />
    </main>
  );
}

function Pizza(props) {
  console.log(props);
  return (
    <div className="pizza">
      <img src={props.photoName} alt={props.name}></img>
      <div>
        <h3>{props.name}</h3>
        <p>{props.ingredients}</p>
        <span>{props.price + 3}</span>
      </div>
    </div>
  );
}

function Footer() {
  const hour = new Date().getHours();
  const openHour = 12;
  const closeHour = 22;
  const isOpen = hour >= openHour && hour <= closeHour;

  console.log(isOpen);

  return (
    <footer className="footer">
      {new Date().toLocaleDateString()}. We're currently open.
    </footer>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

今天我想深入探讨React开发中最常见的任务之一:渲染列表。这是将是你在项目中会做上百次的事情。

“渲染列表”是什么意思?

简单来说,渲染列表就是获取一个数据数组,并为数组中的每个元素创建一个组件。我们不是手动编写每个组件,而是希望React根据我们的数据动态生成它们。

例如,假设我们有一个披萨对象的数组,我们想在UI中将每个披萨显示为一个组件。如果我们数组中有四个披萨,我们应该看到四个组件。如果我们在数组中添加两个更多的披萨,两个新组件应该自动出现。

JavaScript .map() 方法的威力

这就是React的魅力所在 – 你不需要为此学习任何特殊的React语法。你只需要使用现有的JavaScript知识,特别是.map()方法。

让我们修改一下代码:

const pizzaData = [
  {
    name: "Focaccia",
    ingredients: "Bread with italian olive oil and rosemary",
    price: 6,
    photoName: "pizzas/focaccia.jpg",
    soldOut: false,
  },
  {
    name: "Pizza Margherita",
    ingredients: "Tomato and mozarella",
    price: 10,
    photoName: "pizzas/margherita.jpg",
    soldOut: false,
  },
  // 更多披萨...
];

function Menu() {
  return (
    <main className="menu">
      <h2>Our Menu</h2>
      <ul className="pizzas">
        {pizzaData.map((pizza) => (
          <Pizza pizzaObject={pizza} key={pizza.name} />
        ))}
      </ul>
    </main>
  );
}

这里发生了什么?我们获取pizzaData数组并使用.map()将每个披萨对象转换为一个Pizza组件。然后React按顺序渲染所有这些组件。

Pizza组件

我们的Pizza组件接收披萨对象作为prop并显示其属性:

function Pizza(props) {
  return (
    <li className="pizza">
      <img src={props.pizzaObject.photoName} alt={props.pizzaObject.name} />
      <div>
        <h3>{props.pizzaObject.name}</h3>
        <p>{props.pizzaObject.ingredients}</p>
        <span>{props.pizzaObject.price}</span>
      </div>
    </li>
  );
}

至关重要的key属性

你注意到我们.map()函数中的key={pizza.name}了吗?这在React中渲染列表时是必不可少的。

当React渲染列表时,它需要一种方法来识别哪些项目已更改、添加或删除。key属性帮助React跟踪每个组件,使其能够高效地更新UI。

你的key应该是:

  • 在兄弟组件中唯一
  • 稳定(不变的)
  • 可预测的

在我们的例子中,我们使用披萨名称作为key,假设每个披萨都有唯一的名称。在实际应用中,你可能会使用数据库ID或其他唯一标识符。

如果你忘记包含key,React会在控制台中给你一个警告:

“警告:列表中的每个子项都应该有一个唯一的’key’属性。”

使用语义化HTML

注意我们如何将披萨列表包装在<ul>元素(无序列表)中,并且每个Pizza组件都包装在<li>元素(列表项)中。这是正确的语义化HTML,对于可访问性和SEO非常重要。

<ul className="pizzas">
  {pizzaData.map((pizza) => (
    <Pizza pizzaObject={pizza} key={pizza.name} />
  ))}
</ul>

为什么不用forEach()?

你可能想知道为什么我们使用.map()而不是.forEach()。关键区别是.map()创建并返回一个新数组,而.forEach()只是循环遍历数组而不返回任何内容。

在JSX中,我们需要有元素来渲染,而.map()正好给我们提供了React可以渲染的JSX元素数组。

最终代码

我们修改后完整代码和效果可能如下所示:

完整代码(点击展开)
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";

const pizzaData = [
  {
    name: "Focaccia",
    ingredients: "Bread with italian olive oil and rosemary",
    price: 6,
    photoName: "pizzas/focaccia.jpg",
    soldOut: false,
  },
  {
    name: "Pizza Margherita",
    ingredients: "Tomato and mozarella",
    price: 10,
    photoName: "pizzas/margherita.jpg",
    soldOut: false,
  },
  {
    name: "Pizza Spinaci",
    ingredients: "Tomato, mozarella, spinach, and ricotta cheese",
    price: 12,
    photoName: "pizzas/spinaci.jpg",
    soldOut: false,
  },
  {
    name: "Pizza Funghi",
    ingredients: "Tomato, mozarella, mushrooms, and onion",
    price: 12,
    photoName: "pizzas/funghi.jpg",
    soldOut: false,
  },
  {
    name: "Pizza Salamino",
    ingredients: "Tomato, mozarella, and pepperoni",
    price: 15,
    photoName: "pizzas/salamino.jpg",
    soldOut: true,
  },
  {
    name: "Pizza Prosciutto",
    ingredients: "Tomato, mozarella, ham, aragula, and burrata cheese",
    price: 18,
    photoName: "pizzas/prosciutto.jpg",
    soldOut: false,
  },
];

function App() {
  return (
    <div className="container">
      <Header />
      <Menu />
      <Footer />
    </div>
  );
}

function Header() {
  const style = {};

  return (
    <header className="header">
      <h1 style={style}>Fast React Pizza Co.</h1>
    </header>
  );
}

function Menu() {
  return (
    <main className="menu">
      <h2>Our Menu</h2>
      <ul className="pizzas">
        {pizzaData.map((pizza) => (
          <Pizza pizzaObj={pizza} key={pizza.name}/>
        ))}
      </ul>
      {/* <Pizza
        name="Pizza Spinaci"
        ingredient="Tomato, mozarella, spinach, and ricotta cheese"
        photoName="pizzas/spinaci.jpg"
        price={10}
      />
      <Pizza
        name="Pizza Funghi"
        ingredient="Tomato, mushrooms"
        price={12}
        photoName="pizzas/funghi.jpg"
      /> */}
    </main>
  );
}

function Pizza(props) {
  console.log(props);
  return (
    <li className="pizza">
      <img src={props.pizzaObj.photoName} alt={props.pizzaObj.name}></img>
      <div>
        <h3>{props.pizzaObj.name}</h3>
        <p>{props.pizzaObj.ingredients}</p>
        <span>{props.pizzaObj.price}</span>
      </div>
    </li>
  );
}

function Footer() {
  const hour = new Date().getHours();
  const openHour = 12;
  const closeHour = 22;
  const isOpen = hour >= openHour && hour <= closeHour;

  console.log(isOpen);

  return (
    <footer className="footer">
      {new Date().toLocaleDateString()}. We're currently open.
    </footer>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

总结

渲染列表是React的基本技能,其美妙之处在于它的简单性。通过利用JavaScript的.map()方法,我们可以根据数据动态渲染组件,使我们的应用程序更加灵活和可维护。

记住这些关键点:

  • 使用.map()将数据数组转换为组件数组
  • 始终为每个项目提供唯一的key属性
  • 在适当的情况下使用语义化HTML(如<ul>和<li>)
  • 考虑将整个对象作为props传递,以获得更简洁的代码


Comments

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注