在 React 中思考

React 可以改变你对所看到的设计和构建的应用的看法。当你使用 React 构建用户界面时,你首先会将其分解为称为组件的部分。然后,你将描述每个组件的不同视觉状态。最后,你会将组件连接在一起,以便数据流经它们。在本教程中,我们将引导你完成使用 React 构建可搜索产品数据表的思考过程。

¥React can change how you think about the designs you look at and the apps you build. When you build a user interface with React, you will first break it apart into pieces called components. Then, you will describe the different visual states for each of your components. Finally, you will connect your components together so that the data flows through them. In this tutorial, we’ll guide you through the thought process of building a searchable product data table with React.

从模型开始

¥Start with the mockup

想象一下,你已经拥有一个 JSON API 和来自设计师的模型。

¥Imagine that you already have a JSON API and a mockup from a designer.

JSON API 返回一些如下所示的数据:

¥The JSON API returns some data that looks like this:

[
{ category: "Fruits", price: "$1", stocked: true, name: "Apple" },
{ category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit" },
{ category: "Fruits", price: "$2", stocked: false, name: "Passionfruit" },
{ category: "Vegetables", price: "$2", stocked: true, name: "Spinach" },
{ category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin" },
{ category: "Vegetables", price: "$1", stocked: true, name: "Peas" }
]

模型看起来像这样:

¥The mockup looks like this:

要在 React 中实现 UI,你通常会遵循相同的五个步骤。

¥To implement a UI in React, you will usually follow the same five steps.

步骤 1:将 UI 分解为组件层次结构

¥Step 1: Break the UI into a component hierarchy

首先在模型中的每个组件和子级周围绘制框并命名它们。如果你与设计师合作,他们可能已经在他们的设计工具中命名了这些组件。问他们!

¥Start by drawing boxes around every component and subcomponent in the mockup and naming them. If you work with a designer, they may have already named these components in their design tool. Ask them!

根据你的背景,你可以考虑以不同的方式将设计拆分为组件:

¥Depending on your background, you can think about splitting up a design into components in different ways:

  • 编程 - 使用相同的技术来决定是否应该创建新函数或对象。单一职责原则 就是这样一种技术,也就是说,理想情况下,一个组件应该只做一件事。如果它最终增长,则应将其分解为更小的子级。

    ¥Programming—use the same techniques for deciding if you should create a new function or object. One such technique is the single responsibility principle, that is, a component should ideally only do one thing. If it ends up growing, it should be decomposed into smaller subcomponents.

  • CSS - 考虑创建类选择器的目的。(但是,组件的粒度有点小。)

    ¥CSS—consider what you would make class selectors for. (However, components are a bit less granular.)

  • 设计 - 考虑如何组织设计的各层。

    ¥Design—consider how you would organize the design’s layers.

如果你的 JSON 结构良好,你通常会发现它自然地映射到 UI 的组件结构。那是因为 UI 和数据模型通常具有相同的信息架构 - 即相同的形状。将你的 UI 分成多个组件,其中每个组件都与你的数据模型的一部分相匹配。

¥If your JSON is well-structured, you’ll often find that it naturally maps to the component structure of your UI. That’s because UI and data models often have the same information architecture—that is, the same shape. Separate your UI into components, where each component matches one piece of your data model.

此屏幕上有五个组件:

¥There are five components on this screen:

  1. FilterableProductTable(灰色)包含整个应用。

    ¥FilterableProductTable (grey) contains the entire app.

  2. SearchBar(蓝色)接收用户输入。

    ¥SearchBar (blue) receives the user input.

  3. ProductTable(淡紫色)根据用户输入显示和过滤列表。

    ¥ProductTable (lavender) displays and filters the list according to the user input.

  4. ProductCategoryRow(绿色)显示每个类别的标题。

    ¥ProductCategoryRow (green) displays a heading for each category.

  5. ProductRow(黄色)为每个产品显示一行。

    ¥ProductRow (yellow) displays a row for each product.

如果你查看 ProductTable(淡紫色),你会发现表头(包含 “名称” 和 “价格” 标签)不是它自己的组件。这是一个偏好问题,你可以选择任何一种方式。对于此示例,它是 ProductTable 的一部分,因为它出现在 ProductTable 的列表中。但是,如果此标头变得复杂(例如,如果你添加排序),你可以将其移动到它自己的 ProductTableHeader 组件中。

¥If you look at ProductTable (lavender), you’ll see that the table header (containing the “Name” and “Price” labels) isn’t its own component. This is a matter of preference, and you could go either way. For this example, it is a part of ProductTable because it appears inside the ProductTable’s list. However, if this header grows to be complex (e.g., if you add sorting), you can move it into its own ProductTableHeader component.

现在你已经确定了模型中的组件,将它们排列成层次结构。出现在模型中另一个组件中的组件应该在层次结构中显示为子级:

¥Now that you’ve identified the components in the mockup, arrange them into a hierarchy. Components that appear within another component in the mockup should appear as a child in the hierarchy:

  • FilterableProductTable

    • SearchBar

    • ProductTable

      • ProductCategoryRow

      • ProductRow

步骤 2:在 React 中构建静态版本

¥Step 2: Build a static version in React

现在你有了组件层次结构,是时候实现你的应用了。最直接的方法是构建一个版本,在不添加任何交互性的情况下从你的数据模型渲染 UI…然而!首先构建静态版本然后再添加交互性通常更容易。构建静态版本需要大量输入而不是思考,但添加交互性需要大量思考而不是大量输入。

¥Now that you have your component hierarchy, it’s time to implement your app. The most straightforward approach is to build a version that renders the UI from your data model without adding any interactivity… yet! It’s often easier to build the static version first and add interactivity later. Building a static version requires a lot of typing and no thinking, but adding interactivity requires a lot of thinking and not a lot of typing.

要构建渲染数据模型的应用的静态版本,你需要构建可重用其他组件并使用 属性 传递数据的 组件。 属性是一种将数据从父级传递给子级的方式。(如果你熟悉 状态 的概念,请完全不要使用状态来构建此静态版本。状态仅用于交互性,即随时间变化的数据。由于这是应用的静态版本 ,你不需要它。)

¥To build a static version of your app that renders your data model, you’ll want to build components that reuse other components and pass data using props. Props are a way of passing data from parent to child. (If you’re familiar with the concept of state, don’t use state at all to build this static version. State is reserved only for interactivity, that is, data that changes over time. Since this is a static version of the app, you don’t need it.)

你可以 “自上而下” 通过从层次结构中较高的组件(如 FilterableProductTable)开始构建或 “自下而上” 通过从较低的组件(如 ProductRow)开始构建。在更简单的示例中,自上而下通常更容易,而在较大的项目中,自下而上更容易。

¥You can either build “top down” by starting with building the components higher up in the hierarchy (like FilterableProductTable) or “bottom up” by working from components lower down (like ProductRow). In simpler examples, it’s usually easier to go top-down, and on larger projects, it’s easier to go bottom-up.

function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2">
        {category}
      </th>
    </tr>
  );
}

function ProductRow({ product }) {
  const name = product.stocked ? product.name :
    <span style={{ color: 'red' }}>
      {product.name}
    </span>;

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({ products }) {
  const rows = [];
  let lastCategory = null;

  products.forEach((product) => {
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category} />
      );
    }
    rows.push(
      <ProductRow
        product={product}
        key={product.name} />
    );
    lastCategory = product.category;
  });

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

function SearchBar() {
  return (
    <form>
      <input type="text" placeholder="Search..." />
      <label>
        <input type="checkbox" />
        {' '}
        Only show products in stock
      </label>
    </form>
  );
}

function FilterableProductTable({ products }) {
  return (
    <div>
      <SearchBar />
      <ProductTable products={products} />
    </div>
  );
}

const PRODUCTS = [
  {category: "Fruits", price: "$1", stocked: true, name: "Apple"},
  {category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"},
  {category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"},
  {category: "Vegetables", price: "$2", stocked: true, name: "Spinach"},
  {category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"},
  {category: "Vegetables", price: "$1", stocked: true, name: "Peas"}
];

export default function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

(如果这段代码看起来很吓人,请先通过 快速开始!)

¥(If this code looks intimidating, go through the Quick Start first!)

构建组件后,你将拥有一个渲染数据模型的可重用组件库。因为这是一个静态应用,组件将只返回 JSX。层次结构顶部的组件 (FilterableProductTable) 会将你的数据模型作为属性。这称为单向数据流,因为数据从顶层组件向下流到树底部的组件。

¥After building your components, you’ll have a library of reusable components that render your data model. Because this is a static app, the components will only return JSX. The component at the top of the hierarchy (FilterableProductTable) will take your data model as a prop. This is called one-way data flow because the data flows down from the top-level component to the ones at the bottom of the tree.

易犯错误

此时,你不应使用任何状态值。这是下一步!

¥At this point, you should not be using any state values. That’s for the next step!

步骤 3:找到 UI 状态的最小但完整的表示

¥Step 3: Find the minimal but complete representation of UI state

要使 UI 具有交互性,你需要让用户更改你的基础数据模型。你将为此使用状态。

¥To make the UI interactive, you need to let users change your underlying data model. You will use state for this.

将状态视为你的应用需要记住的最小变化数据集。构建状态最重要的原则是保持它 DRY(Don’t Repeat Yourself,不要重复自己)。找出应用所需状态的绝对最小表示,并按需计算其他所有内容。例如,如果你正在构建购物清单,则可以将条目存储为状态数组。如果你还想显示列表中的条目数,请不要将条目数存储为另一个状态值 - 而是读取数组的长度。

¥Think of state as the minimal set of changing data that your app needs to remember. The most important principle for structuring state is to keep it DRY (Don’t Repeat Yourself). Figure out the absolute minimal representation of the state your application needs and compute everything else on-demand. For example, if you’re building a shopping list, you can store the items as an array in state. If you want to also display the number of items in the list, don’t store the number of items as another state value—instead, read the length of your array.

现在想想这个示例应用中的所有数据片段:

¥Now think of all of the pieces of data in this example application:

  1. 原始产品清单

    ¥The original list of products

  2. 用户输入的搜索文本

    ¥The search text the user has entered

  3. 复选框的值

    ¥The value of the checkbox

  4. 过滤后的产品列表

    ¥The filtered list of products

其中哪些是状态?识别那些不是的:

¥Which of these are state? Identify the ones that are not:

  • 随着时间的推移它会保持不变吗?如果是这样,它不是状态。

    ¥Does it remain unchanged over time? If so, it isn’t state.

  • 它是通过属性从父级传递的吗?如果是这样,它不是状态。

    ¥Is it passed in from a parent via props? If so, it isn’t state.

  • 你可以根据组件中的现有状态或属性来计算它吗?如果是这样,那肯定不是状态!

    ¥Can you compute it based on existing state or props in your component? If so, it definitely isn’t state!

剩下的可能是状态。

¥What’s left is probably state.

让我们再一一过一遍:

¥Let’s go through them one by one again:

  1. 原始的产品列表作为属性传入,因此它不是状态。

    ¥The original list of products is passed in as props, so it’s not state.

  2. 搜索文本似乎是状态,因为它会随着时间的推移而变化,并且无法从任何东西中计算出来。

    ¥The search text seems to be state since it changes over time and can’t be computed from anything.

  3. 复选框的值似乎是状态,因为它会随着时间的推移而变化,并且无法从任何东西中计算出来。

    ¥The value of the checkbox seems to be state since it changes over time and can’t be computed from anything.

  4. 过滤后的产品列表不是状态,因为它可以通过获取原始产品列表并根据搜索文本和复选框值对其进行过滤来计算。

    ¥The filtered list of products isn’t state because it can be computed by taking the original list of products and filtering it according to the search text and value of the checkbox.

这意味着只有搜索文本和复选框的值是状态!做得很好!

¥This means only the search text and the value of the checkbox are state! Nicely done!

深入研究

属性与状态

¥Props vs State

React 中有两种类型的 “模型” 数据:属性和状态。两者非常不同:

¥There are two types of “model” data in React: props and state. The two are very different:

  • 属性就像你传递的参数 到一个函数。它们让父组件将数据传递给子组件并自定义其外观。例如,Form 可以将 color 属性传递给 Button

    ¥Props are like arguments you pass to a function. They let a parent component pass data to a child component and customize its appearance. For example, a Form can pass a color prop to a Button.

  • 状态就像一个组件的内存。 它允许组件跟踪某些信息并根据交互更改它。例如,Button 可能会跟踪 isHovered 状态。

    ¥State is like a component’s memory. It lets a component keep track of some information and change it in response to interactions. For example, a Button might keep track of isHovered state.

属性和状态是不同的,但它们一起工作。父组件通常会在状态中保留一些信息(以便它可以更改它),并将其作为属性传递给子组件。如果第一次阅读时仍然感觉差异模糊,那也没关系。它需要一些练习才能真正坚持下去!

¥Props and state are different, but they work together. A parent component will often keep some information in state (so that it can change it), and pass it down to child components as their props. It’s okay if the difference still feels fuzzy on the first read. It takes a bit of practice for it to really stick!

步骤 4:确定你的状态应该在哪里

¥Step 4: Identify where your state should live

确定应用的最小状态数据后,你需要确定哪个组件负责更改此状态或拥有该状态。记住:React 使用单向数据流,将数据从父级向下传递到子组件。可能不会立即清楚哪个组件应该拥有什么状态。如果你是这个概念的新手,这可能具有挑战性,但你可以按照以下步骤弄明白!

¥After identifying your app’s minimal state data, you need to identify which component is responsible for changing this state, or owns the state. Remember: React uses one-way data flow, passing data down the component hierarchy from parent to child component. It may not be immediately clear which component should own what state. This can be challenging if you’re new to this concept, but you can figure it out by following these steps!

对于应用中的每个状态:

¥For each piece of state in your application:

  1. 识别基于该状态渲染某些内容的每个组件。

    ¥Identify every component that renders something based on that state.

  2. 找到它们最接近的公共父组件 - 在层次结构中高于它们的组件。

    ¥Find their closest common parent component—a component above them all in the hierarchy.

  3. 决定状态应该在哪里:

    ¥Decide where the state should live:

    1. 通常,你可以将状态直接放入它们的共同父级中。

      ¥Often, you can put the state directly into their common parent.

    2. 你还可以将状态放入其共同父级之上的某个组件中。

      ¥You can also put the state into some component above their common parent.

    3. 如果你找不到一个组件在其中对拥有状态有意义,请创建一个新组件专门用于保存状态并将其添加到公共父组件之上的层次结构中的某个位置。

      ¥If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common parent component.

在上一步中,你在该应用中发现了两个状态:搜索输入文本和复选框的值。在这个例子中,它们总是一起出现,所以把它们放在同一个地方是有意义的。

¥In the previous step, you found two pieces of state in this application: the search input text, and the value of the checkbox. In this example, they always appear together, so it makes sense to put them into the same place.

现在让我们为他们制定策略:

¥Now let’s run through our strategy for them:

  1. 识别使用状态的组件:

    ¥Identify components that use state:

    • ProductTable 需要根据该状态(搜索文本和复选框值)过滤产品列表。

      ¥ProductTable needs to filter the product list based on that state (search text and checkbox value).

    • SearchBar 需要显示该状态(搜索文本和复选框值)。

      ¥SearchBar needs to display that state (search text and checkbox value).

  2. 找到它们的共同父级:两个组件共享的第一个父组件是 FilterableProductTable

    ¥Find their common parent: The first parent component both components share is FilterableProductTable.

  3. 决定状态所在的位置:我们将在 FilterableProductTable 中保留过滤器文本和选中状态值。

    ¥Decide where the state lives: We’ll keep the filter text and checked state values in FilterableProductTable.

所以状态值将存在于 FilterableProductTable 中。

¥So the state values will live in FilterableProductTable.

使用 useState() 钩子。 将状态添加到组件。钩子是一种特殊的函数,可让你 “接入” 到 React。在 FilterableProductTable 的顶部添加两个状态变量并指定它们的初始状态:

¥Add state to the component with the useState() Hook. Hooks are special functions that let you “hook into” React. Add two state variables at the top of FilterableProductTable and specify their initial state:

function FilterableProductTable({ products }) {
const [filterText, setFilterText] = useState('');
const [inStockOnly, setInStockOnly] = useState(false);

然后,将 filterTextinStockOnly 作为属性传递给 ProductTableSearchBar

¥Then, pass filterText and inStockOnly to ProductTable and SearchBar as props:

<div>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly} />
<ProductTable
products={products}
filterText={filterText}
inStockOnly={inStockOnly} />
</div>

你可以开始查看应用的行为方式。在下面的沙盒代码中将 filterText 的初始值从 useState('') 修改为 useState('fruit')。你将看到搜索输入文本和表格更新:

¥You can start seeing how your application will behave. Edit the filterText initial value from useState('') to useState('fruit') in the sandbox code below. You’ll see both the search input text and the table update:

import { useState } from 'react';

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);

  return (
    <div>
      <SearchBar 
        filterText={filterText} 
        inStockOnly={inStockOnly} />
      <ProductTable 
        products={products}
        filterText={filterText}
        inStockOnly={inStockOnly} />
    </div>
  );
}

function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2">
        {category}
      </th>
    </tr>
  );
}

function ProductRow({ product }) {
  const name = product.stocked ? product.name :
    <span style={{ color: 'red' }}>
      {product.name}
    </span>;

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({ products, filterText, inStockOnly }) {
  const rows = [];
  let lastCategory = null;

  products.forEach((product) => {
    if (
      product.name.toLowerCase().indexOf(
        filterText.toLowerCase()
      ) === -1
    ) {
      return;
    }
    if (inStockOnly && !product.stocked) {
      return;
    }
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category} />
      );
    }
    rows.push(
      <ProductRow
        product={product}
        key={product.name} />
    );
    lastCategory = product.category;
  });

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

function SearchBar({ filterText, inStockOnly }) {
  return (
    <form>
      <input 
        type="text" 
        value={filterText} 
        placeholder="Search..."/>
      <label>
        <input 
          type="checkbox" 
          checked={inStockOnly} />
        {' '}
        Only show products in stock
      </label>
    </form>
  );
}

const PRODUCTS = [
  {category: "Fruits", price: "$1", stocked: true, name: "Apple"},
  {category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"},
  {category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"},
  {category: "Vegetables", price: "$2", stocked: true, name: "Spinach"},
  {category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"},
  {category: "Vegetables", price: "$1", stocked: true, name: "Peas"}
];

export default function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

请注意,编辑表单还不起作用。上面的沙箱中有一个控制台错误,解释了原因:

¥Notice that editing the form doesn’t work yet. There is a console error in the sandbox above explaining why:

Console

在上面的沙盒中,ProductTableSearchBar 读取 filterTextinStockOnly 属性来渲染表格、输入和复选框。例如,以下是 SearchBar 填充输入值的方式:

¥In the sandbox above, ProductTable and SearchBar read the filterText and inStockOnly props to render the table, the input, and the checkbox. For example, here is how SearchBar populates the input value:

function SearchBar({ filterText, inStockOnly }) {
return (
<form>
<input
type="text"
value={filterText}
placeholder="Search..."/>

但是,你还没有添加任何代码来响应用户的输入操作。这将是你的最后一步。

¥However, you haven’t added any code to respond to the user actions like typing yet. This will be your final step.

步骤 5:添加逆向数据流

¥Step 5: Add inverse data flow

当前,你的应用可以正确渲染属性和状态在层次结构中向下流动。但是要根据用户输入更改状态,你需要支持以其他方式流动的数据:层次结构深处的表单组件需要更新 FilterableProductTable 中的状态。

¥Currently your app renders correctly with props and state flowing down the hierarchy. But to change the state according to user input, you will need to support data flowing the other way: the form components deep in the hierarchy need to update the state in FilterableProductTable.

React 使此数据流显式,但它需要比双向数据绑定多一点的输入。如果你尝试键入或选中上面示例中的框,你会看到 React 会忽略你的输入。这是故意的。通过编写 <input value={filterText} />,你已将 inputvalue 属性设置为始终等于从 FilterableProductTable 传入的 filterText 状态。由于永远不会设置 filterText 状态,因此输入永远不会改变。

¥React makes this data flow explicit, but it requires a little more typing than two-way data binding. If you try to type or check the box in the example above, you’ll see that React ignores your input. This is intentional. By writing <input value={filterText} />, you’ve set the value prop of the input to always be equal to the filterText state passed in from FilterableProductTable. Since filterText state is never set, the input never changes.

你希望这样做,以便每当用户更改表单输入时,状态都会更新以反映这些更改。该状态为 FilterableProductTable 所有,因此只有它可以调用 setFilterTextsetInStockOnly。要让 SearchBar 更新 FilterableProductTable 的状态,你需要将这些函数向下传递给 SearchBar

¥You want to make it so whenever the user changes the form inputs, the state updates to reflect those changes. The state is owned by FilterableProductTable, so only it can call setFilterText and setInStockOnly. To let SearchBar update the FilterableProductTable’s state, you need to pass these functions down to SearchBar:

function FilterableProductTable({ products }) {
const [filterText, setFilterText] = useState('');
const [inStockOnly, setInStockOnly] = useState(false);

return (
<div>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
onFilterTextChange={setFilterText}
onInStockOnlyChange={setInStockOnly} />

SearchBar 内部,你将添加 onChange 事件处理程序并从中设置父状态:

¥Inside the SearchBar, you will add the onChange event handlers and set the parent state from them:

function SearchBar({
filterText,
inStockOnly,
onFilterTextChange,
onInStockOnlyChange
}) {
return (
<form>
<input
type="text"
value={filterText}
placeholder="Search..."
onChange={(e) => onFilterTextChange(e.target.value)}
/>
<label>
<input
type="checkbox"
checked={inStockOnly}
onChange={(e) => onInStockOnlyChange(e.target.checked)}

现在应用完全可以工作了!

¥Now the application fully works!

import { useState } from 'react';

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);

  return (
    <div>
      <SearchBar 
        filterText={filterText} 
        inStockOnly={inStockOnly} 
        onFilterTextChange={setFilterText} 
        onInStockOnlyChange={setInStockOnly} />
      <ProductTable 
        products={products} 
        filterText={filterText}
        inStockOnly={inStockOnly} />
    </div>
  );
}

function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2">
        {category}
      </th>
    </tr>
  );
}

function ProductRow({ product }) {
  const name = product.stocked ? product.name :
    <span style={{ color: 'red' }}>
      {product.name}
    </span>;

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({ products, filterText, inStockOnly }) {
  const rows = [];
  let lastCategory = null;

  products.forEach((product) => {
    if (
      product.name.toLowerCase().indexOf(
        filterText.toLowerCase()
      ) === -1
    ) {
      return;
    }
    if (inStockOnly && !product.stocked) {
      return;
    }
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category} />
      );
    }
    rows.push(
      <ProductRow
        product={product}
        key={product.name} />
    );
    lastCategory = product.category;
  });

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

function SearchBar({
  filterText,
  inStockOnly,
  onFilterTextChange,
  onInStockOnlyChange
}) {
  return (
    <form>
      <input 
        type="text" 
        value={filterText} placeholder="Search..." 
        onChange={(e) => onFilterTextChange(e.target.value)} />
      <label>
        <input 
          type="checkbox" 
          checked={inStockOnly} 
          onChange={(e) => onInStockOnlyChange(e.target.checked)} />
        {' '}
        Only show products in stock
      </label>
    </form>
  );
}

const PRODUCTS = [
  {category: "Fruits", price: "$1", stocked: true, name: "Apple"},
  {category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"},
  {category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"},
  {category: "Vegetables", price: "$2", stocked: true, name: "Spinach"},
  {category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"},
  {category: "Vegetables", price: "$1", stocked: true, name: "Peas"}
];

export default function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

你可以在 添加交互性 部分了解有关处理事件和更新状态的所有信息。

¥You can learn all about handling events and updating state in the Adding Interactivity section.

从这往哪儿走

¥Where to go from here

这是一个非常简短的介绍,介绍了如何考虑使用 React 构建组件和应用。你现在可以 启动一个 React 项目 或本教程中使用的 深入研究所有语法

¥This was a very brief introduction to how to think about building components and applications with React. You can start a React project right now or dive deeper on all the syntax used in this tutorial.


React 中文网 - 粤ICP备13048890号