此文章是翻译Thinking in React这篇React(版本v15.5.4)官方文档。

Thinking in React

在我们看来,React 是使用JavaScript 构建大型快速Web 应用的首选。它已经在Facebook 和Instagram 中被广泛应用了。

React 最大的方面是使你如何考虑你的应用,在构建它们时。在这篇文档中,我们将带你浏览通过使用React 构建一个searchable product data的思维过程。

Start With A Mock

设想你已经根据我们的设计有一个模拟JSON API。这个Mock 看上去如下:

start-with-a-mock

我们的JSON API 返回的数据如下:

  1. [
  2. {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  3. {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  4. {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  5. {category: "Electroincs", price: "$99.99", stocked: true, name: "iPod Touch"},
  6. {category: "Electroincs", price: "$399.99", stocked: false, name: "iPhone 5"},
  7. {category: "Electroincs", price: "$199.99", stocked: true, name: "Nexus 7"}
  8. ];

Step 1: Break The UI Into A Component Hierarchy

第一件事在mock 中给每一个组件(子组件)画框并进行命名。如果你和一个设计师工作,他们已经做好这些了,所以去和他们讨论!Photoshop 图层名字可以作为你的React 组件名字!

但是你怎么知道什么是它们自己的组件?只需要使用相同的技术,你是否决定创建一个新的函数或者对象这个中技术。其中一个技术是single responsibility principle,也就是说,理论上一个组件只做一件事。如果组件变大,它应该被拆分成几个小组件。

由于你经常展示一个JSON 数据模型给用户,你会发现如果你的模型被正确构建,那么你的UI(和你的组件框架)会非常好的匹配。那是因为UI 和数据模型总是依附相同的信息架构(information architecture),也就是意味着分离你的UI 成为组件是非常容易的。只要正确地的将你的每一个数据模型对应组件就可以了。

step-1

你会发现在我们这个简单的app 中有5 个组件。我们使用斜体来表示每一个组件数据。

  1. FiterableProductTable(orange):包含整个例子
  2. SearchBar(blue):接受所有的用户输入
  3. ProdutTable(green):根据用户输入来展示过滤的数据集合
  4. ProductCategoryRow(turquoise):展示每一个分类的标题
  5. ProductRow(red):每一个产品展示一行

如果你看ProductTable,你会发现表格头(包含”Name” 和”Price” 标签)并不是单独一个组件。这是一个偏好问题,选择哪种方式目前还存在争议。例如,我们将它作为ProductTable 的一部分,因为因为它是ProductTable 的负责的渲染数据的一部分。无论怎样,如果它的头变复杂了(例如,如果我们添加了一个排序),最好的方式的作为一个单独的ProductTableHeader组件。

现在我们已经确认了在模拟中的组件,让我们来安排它们为层次结构。这是非常容易的。在模拟中,一个组件在另一个组件中展示,可以作为层级中的子节点:

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

Step 2: Build A Static Version in React

CodePen 中查看Thinking In React: Step 2

现在你有了自己的组件层级,是时候实现你的app 了。最简单的方式的是个构建一个没有交互的只是将数据渲染成UI。最好解耦这些过程,因为构建一个静态版本需要许多代码不需要思考,添加交互需要更多思考和少量的代码。我们会看到为什么。

为了构建一个静态的app 去渲染你的数据模型(data model),你想要构建复用其他的组件并且使用props 去传递数据的组件。props 是从父组件传递给子组件数据的一种方式。如果你了解state 的概念,在构建静态版本时,一点也不需要使用state(don’t use state at all)。State 被留作交互时,数据随着时间而变动。因为是静态版本,所以你不需要state。

你可以自上而下(top-down)或自下而上(down-top)构建。也就是,你可以从位于高层级的组件(例如,FilterableProductTable)或者是低层级的组件(ProductRow)开始。在简单的例子中,通常是自上而下,而在大型项目中,通常是从下而上这样便于编写测试用例。

在此最后,你有了许多可复用的组件来渲染你的数据模型。因为是静态版本的应用,所以这些组件中仅有render() 方法。在顶层组件(FilterableProductTable)将会接受你的数据模型作为prop。如果你改变你的基础数据模型(underlying data model)并且又调用了ReactDOM .render(),这个UI 将会被更新。这是很容易看清你的UI 是如何更新,并在哪里做出改变,因为没有什么复杂的事情。React 是 单向数据流one-way data flow(也叫做单向绑定one-way bind)来保证一切模块化和快速的。

如果你在执行这一步的时候需要帮助,可以简单浏览React doc

A Brief Interlude: Props vs State

在React 中有props 和state 这两种模型数据。了解它们的不同是非常重要的;如果你想了解它们的不同,可以浏览the offical React docs

Step 3: Identify The Minimal(but complete)Representation Of UI State

为了能够使的UI 进行交互,你需要能够触发你的基础数据模型的改变。

为了正确的构建你的app,你需要考虑你的app 需要的最小的可变的state 的集合。这个的关键是DRY:Don’t Repeat Yourself。找出你的应用需要的绝对最小的state,你需要依赖它计算出其它所有的数据。例如,如果你正在构建TODO list,你需要有一个TODO items 数组;不需要为计数而保留一个单独的state 变量,当你想要渲染这个TODO 的数量,只需要使用TODO items 数组的长度就可以了。

考虑我们在实例应用程序中所有的数据块。我们有:

  • 最初的产品列表(The original list of products
  • 用户键入的搜索文本(The search text the user has entered
  • checkbox 的值(The value of the checkbox
  • 产品过滤列表(The filtered list of products

让我们通过每一个,来找到那一个是state。简单地问每一个数据块的三个问题:

  1. 它是父组件通过props 传递的吗?如果是,它很可能不是state。
  2. 它随着时间不改变吗?如果是,它很可能不是state。
  3. 在组件中,你能够基于其他的state 或props 来计算出它吗?如果是,它不是state。

The original list of products是通过props 进行传递的,所以它不是state 。The search text and the checkbox 似乎是state 因为它们随着时间而改变,并且不能通过其它数据来计算获取。最后,The filtered list of products 不是state,因为它可以通过结合the original list of productsthe searche textvalue of the checkbox 计算出来。

所以最终,我们的state 是:

  • 用户键入的搜索文本(The search text the user has entered
  • checkbox 的值(The value of the checkbox

Step 4: Identify Where Your State Should Live

查看位于CodePen 上的Thinking In React: Step 4 代码。

OK,现在我们已经确认了app state 的最小集合。Next,我们需要确认哪一个组件改变或拥有这个state。

记住:React 在组件层级中都是关于单向的向下的数据流。它可能并不立即清楚那一个组件应该拥有什么state。这个通常对新手去了解是充满挑战的,所以根据接下来的步骤来搞清楚它:

对于你应用中的每一个state :

  • 确认每一个组件基于那个state 来进行渲染。
  • 找到一个共同的所有者组件(一个单独的组件,高于所有的需要那个state的层级中组件)。
  • 要么是这个共同的拥有者,要么是在这个公共拥有者之上的组件拥有这个state。
  • 如果你不能找到拥有这个state 的组件,可以简单的创建一个组件来拥有这个state 并将其添加到需要这个state 的所有组件的层级之上。

让我们在我们的应用中应用这个策略:

  • ProductTable 需要这个state 去过滤产品列表(product list),并且SearchBar 需要去展示搜索文字(search text)和选中状态(checked)state。
  • 这个通用的拥有组件是FilterableProductTable
  • 在概念上来说是有道理的,这个filter text 和checked value 位于FilterableProductTable 组件中。

Cool,所以我们决定我们的state 位于FilterableProductTable。首先在FilterableProductTableconstruct 中添加实例属性this.state = {filterText: '',inStockOnly: false} 去表示初始化应用的state。然后,将filterTextinStockOnly作为prop 传递给SearchBarProductTable。最后,使用这些props 过滤ProductTable 中的row 以及在SearchBar 中设置表单域的值。

你可以看你的应用是如何运行的:设置filterTextball 并刷新你的应用,你会看到table 中数据正确更新了。

Step 5: Add Inverse Data Flow

查看位于CodePen 上的Thinking In React: Step 5 代码。

到现在为止,我们已经建立了一个应用程序,进行正确渲染将props 和state 作为功能在层级中向下流动。现在,是时候添加支持数据从另一个方法流动:在层级中最深出的表单组件去更新位于FilterableProductTable 中的state。

Reack 为了使数据流明确,使它能够容易的了解你的程序是如何工作的,但是它需要更多的数据相比传统的双线数据绑定(two-way data binding)

如果在这个例子版本中进行键入信息或操作多选框,你会发现React 忽略了你的输入。这是故意的,因为我们设置inputvalue 一直等于在FiterableProductTable 中的state

让我们考虑一下我们想要发生什么。我们要确认无论何时我们改变表单,我们更新state 去响应用户的输入。因为组件仅能更新它们自己的state,FilterableProductTable 去传入一个回调函数到SearchBar 去启动无论什么时候state 应该被更新。我们使用onChange 事件去通知它。被FilterableProductTable 传入的回调函数,将调用setState() ,去更新应用。

虽然听上去听复杂,实际上只是几行代码。明确你的数据如果在应用中流转。

And That’s It

希望,他能够给你一个思路,在React 中如何思考构建组件和应用。虽然它可能比你之前使用更多的代码,记住代码可读性远超过它的编写,它是非常容易阅读这个模块化,明确的代码。当你开始构建大量的组件库,你会欣赏这个明确的、模块化的,同时随着代码复用,你的代码行数将开始收缩。:)