v19.2Latest

React 思维方式

React 可以改变你审视设计稿和构建应用的方式。当你使用 React 构建用户界面时,首先会将其拆分成称为组件的片段。然后,你会描述每个组件的不同视觉状态。最后,将组件连接起来,使数据在它们之间流动。在本教程中,我们将引导你完成使用 React 构建一个可搜索产品数据表格的思考过程。

从设计稿开始

假设你已经有一个 JSON API 和设计师提供的设计稿。

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

设计稿如下所示:

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

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

在设计稿中围绕每个组件和子组件绘制方框并为其命名。如果你与设计师合作,他们可能已经在设计工具中命名了这些组件。问问他们!

根据你的背景,你可以通过不同的方式思考如何将设计拆分为组件:

  • 编程——使用与决定是否创建新函数或对象相同的技术。其中一种技术是 关注点分离,也就是说,理想情况下,一个组件应该只关注一件事。如果它最终变得庞大,则应分解为更小的子组件。
  • CSS——考虑你会为哪些元素创建类选择器。(不过,组件的粒度通常稍大一些。)
  • 设计——考虑如何组织设计的图层。

如果你的 JSON 结构良好,你通常会发现它自然地映射到 UI 的组件结构。这是因为 UI 和数据模型通常具有相同的信息架构——即相同的形状。将 UI 拆分为组件,每个组件对应数据模型的一部分。

此屏幕上有五个组件:

  1. FilterableProductTable(灰色)包含整个应用。
  2. SearchBar(蓝色)接收用户输入。
  3. ProductTable(淡紫色)根据用户输入显示和筛选列表。
  4. ProductCategoryRow(绿色)显示每个类别的标题。
  5. ProductRow(黄色)显示每个产品的行。

如果你查看ProductTable(淡紫色),会发现表头(包含“名称”和“价格”标签)并不是一个独立的组件。这取决于个人偏好,两种方式都可以。在此示例中,它是 ProductTable的一部分,因为它出现在ProductTable的列表内部。但是,如果这个表头变得复杂(例如,如果你添加了排序功能),你可以将其移动到自己的ProductTableHeader组件中。

现在你已经识别出设计稿中的组件,将它们排列成层次结构。在设计稿中出现在另一个组件内部的组件,在层次结构中应显示为子组件:

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

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

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

要构建一个渲染数据模型的静态应用版本,你需要构建 组件,这些组件复用其他组件并使用 props传递数据。Props 是一种从父组件向子组件传递数据的方式。(如果你熟悉state的概念,在构建此静态版本时完全不要使用 state。State 仅保留用于交互性,即随时间变化的数据。由于这是应用的静态版本,你不需要它。)

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

(如果这段代码看起来令人畏惧,请先阅读快速入门!)

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

注意

在此阶段,你不应使用任何状态值。那是下一步的工作!

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

为了让 UI 具有交互性,你需要允许用户更改底层数据模型。你将为此使用状态

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

现在思考这个示例应用程序中的所有数据片段:

  1. 原始产品列表
  2. 用户输入的搜索文本
  3. 复选框的值
  4. 筛选后的产品列表

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

  • 它是否随时间保持不变?如果是,则不是状态。
  • 它是否通过 props 从父组件传入?如果是,则不是状态。
  • 能否基于组件中现有的状态或 props 计算它?如果是,它绝对不是状态!

剩下的很可能就是状态。

让我们再逐一检查它们:

  1. 原始产品列表作为 props 传入,因此它不是状态。
  2. 搜索文本似乎是状态,因为它随时间变化且无法从任何其他数据计算得出。
  3. 复选框的值似乎是状态,因为它随时间变化且无法从任何其他数据计算得出。
  4. 筛选后的产品列表不是状态,因为它可以通过获取原始产品列表并根据搜索文本和复选框的值进行筛选来计算得出。

这意味着只有搜索文本和复选框的值是状态!干得漂亮!

Deep Dive
Props 与 State

步骤 4:确定 state 应该存放在哪里

在确定了应用的最小 state 数据之后,你需要确定哪个组件负责改变这个 state,或者说拥有这个 state。记住:React 使用单向数据流,数据沿着组件层次结构从父组件向下传递到子组件。可能不会立即清楚哪个组件应该拥有什么 state。如果你对这个概念不熟悉,这可能具有挑战性,但你可以按照以下步骤来弄清楚!

对于应用中的每一个 state:

  1. 识别所有基于该 state 渲染内容的组件。
  2. 找到它们最近的共同父组件——在层次结构中位于它们所有组件之上的一个组件。
  3. 决定 state 应该存放在哪里:
    1. 通常,你可以直接将 state 放入它们的共同父组件中。
    2. 你也可以将 state 放入它们共同父组件之上的某个组件中。
    3. 如果找不到一个拥有该 state 有意义的组件,可以创建一个专门用于持有该 state 的新组件,并将其添加到共同父组件之上的层次结构中的某个位置。

在上一步中,你在这个应用中找到了两个 state:搜索输入文本和复选框的值。在这个例子中,它们总是一起出现,因此将它们放在同一个地方是有意义的。

现在让我们针对它们运行我们的策略:

  1. 识别使用 state 的组件:
    • ProductTable需要基于该 state(搜索文本和复选框值)来过滤产品列表。
    • SearchBar需要显示该 state(搜索文本和复选框值)。
  2. 找到它们的共同父组件: 两个组件共享的第一个父组件是 FilterableProductTable
  3. 决定 state 存放在哪里:我们将把过滤文本和选中状态值保存在FilterableProductTable中。

因此,state 值将存放在FilterableProductTable中。

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

然后,将filterTextinStockOnly作为 props 传递给ProductTableSearchBar

你可以开始看到你的应用将如何运行。在下面的沙盒代码中,将filterText 的初始值从 useState('')编辑为useState('fruit')。你将看到搜索输入文本和表格都会更新:

注意,表单编辑功能目前还无法工作。上面的沙盒中有一条控制台错误解释了原因:

控制台

您为表单字段提供了一个 `value` 属性,但没有提供 `onChange` 处理函数。这将渲染一个只读字段。

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

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

步骤 5:添加反向数据流

目前,您的应用程序能够正确地通过属性和状态沿着层级向下渲染。但是,为了根据用户输入改变状态,您需要支持数据向另一个方向流动:层级深处的表单组件需要更新 FilterableProductTable中的状态。

React 使这种数据流变得明确,但它比双向数据绑定需要多写一些代码。如果您尝试在上面的示例中输入文本或勾选复选框,您会发现 React 忽略了您的输入。这是有意为之的。通过编写<input value={filterText} />,您已将 inputvalue属性设置为始终等于从FilterableProductTable 传入的 filterText状态。由于filterText状态从未被设置,输入框永远不会改变。

您希望实现的是,每当用户更改表单输入时,状态就会更新以反映这些变化。状态由 FilterableProductTable拥有,因此只有它可以调用setFilterTextsetInStockOnly。为了让SearchBar 更新 FilterableProductTable 的状态,您需要将这些函数传递给 SearchBar

SearchBar内部,您将添加onChange事件处理程序,并在其中设置父组件的状态:

现在应用程序完全正常工作了!

你可以在添加交互性章节中学习所有关于处理事件和更新状态的内容。

下一步

这只是关于如何使用 React 构建组件和应用程序的思考方式的简要介绍。你可以立即开始一个 React 项目,或者深入学习本教程中使用的所有语法