在我们的看来,React是使用js创建大型快速网站应用的首要方法。它在Facebook和Instagram的使用已经为我们展现了它自己。
React的一个很好的地方就在于当你创建应用的时候它使你思考如何创建。在这篇文档里,我们会带着你使用React创建一个查询产品数据表的流程。
以一个仿制品开始
想象我们已经有了一个JSON接口和一个设计师设计的仿制品。它的样子就像这样:
我们的JSON接口返回的数据就像这样:
[
{category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
{category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
{category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
{category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
{category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
{category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];
步骤1:将UI分解成组件
首先你将要做的就是在每一个组件周围画上方框并且给它们命名。如果你和设计师一起工作,他们也许已经做了这件事,所以和他们聊一聊。他们的photoshop图层的名字也许就可以作为你的React组件的名字。
但是你如何知道它自己的组件是什么?就使用你是否需要创建一个新的函数或者对象来决定。一个类似的手法就是单个功能原则,也就是说,理想情况下一个组件应该只做一件事。如果它功能不断丰富,它应该被分解成更小的子组件。
只要你经常给用户显示JSON数据模型,你会发现如果你的数据模型是正确建立的,你的UI会被很好的映射。那是因为UI和数据模型趋于依附于同样的信息体系,这就意味着将UI分离成组件通常是很容易的。把UI分裂成一个个组件就意味着每一个组件就对应你的一部分数据模型。
你可以看到这里我们有5个组件在我们的应用里。我们已经将每一个组件所代表的数据用斜体表示。
- FilterableProductTable(橙色):包含整个例子
- SearchBar(蓝色):接受所有用户输入
- ProductTable(绿色):基于用户输入显示和过滤数据
- ProductCategoryRow(蓝绿色):显示每个category的标题
- ProductRow(红色):每一个Product显示一行
如果你看着ProductTable,你就会看到表标题(包含“Name”和“Price”标签)并没有作为一个组件。这是个人喜好问题,选择哪种方式目前还存在争议。对于这个例子,我们把表标题作为ProductTable组件的一部分因为因为这是ProductTable组件的责任,它需要渲染数据。然而,如果这个标题变得复杂了(也就是如果我们添加了分类排序的功能),那么它就会作为单独的ProductTableHeader组件。
现在我们已经在原型图里确认了我们的组件,让我们将它们安排成一个层级结构。这样很简单。在其他组件中出现的组件应该作为一个子节点出现在层级里。
步骤2:创建一个静态版本
现在你的组件在层级里了,现在是时候完成这个应用了。最简单的方式就是创建一个静态版本包含你的数据模型以及渲染UI界面但是没有交互性。最好使这些流程之间分离解耦因为创建一个静态版本更多的是需要写代码而不太需要逻辑思考,而添加交互需要许多逻辑思考不需要太多代码。我们来看看为什么。
创建一个应用的静态版本来渲染你的数据模型,你需要创建可以重用其他组件并且使用props传递数据的组件。props是一种从父组件传递数据到子组件的方法。如果你比较熟悉state的概念,请不要使用state来创建这个静态版本。state只能预留给交互的,也就是随时间的变化来改变数据。因为现在只是做一个静态版本,所以你不需要state。
你可以自顶向下或者自底向上。就是说,你要么从层级顶端来创建组件(也就是从FilterableProductTable组件开始)要么从最底端的组件开始(ProductRow组件)。在简单的例子里,通常自顶向下比较简单,并且项目越大,自底向上就越简单还可以边写边测试。
在这一步的最后,你会有一个渲染你的数据模型的可重用组件库。这些组件将只有render()方法因为这是一个静态的版本。在层级顶端的组件(FilterableProductTable)会把数据模型作为props传入。如果你改变了基本数据模型并且重新调用了ReactDOM.render(),UI就会更新。这样可以很清楚地看到UI是怎么更新的并且从哪里发生变化因为没有什么复杂的东西。React的单向数据流(也叫单向绑定)让所有东西模块化并且高效。
参考React文档如果你在执行这一步的时候需要帮助。
一个小插曲:props vs state
在React中有两种类型的“模型”数据:props和state。理解他们两个之间的区别很重要;读一读文档如果你不确定他们的区别。
步骤3:确认UI state的最小限度(但完整的)表示
想让你的UI拥有交互性,你需要能够触发你的基础数据模型的变化。React可以依靠state完成这点。
想要正确建立你的应用,你首先需要思考你的应用需要的最小限度的可变的state。关键就是DRY:不要重复你自己。想出应用需要的state绝对最小限度的表示并且计算所有你需要的请求。举个例子,如果你在创建一个TODO列表,只要保存TODO列表里的数据到一个数组里;不要为了列出的数目保存一个单独的state变量。当你想要渲染TODO时,只需要获取TODO数组的长度即可。
想一下在例子应用里所有数据。我们有:
- 最初的产品列表
- 用户输入的搜搜文本
- 选择框的值
- 过滤后的产品列表
让我们仔细检查每一个数据找出哪一个是state。对每一个数据思考三个问题:
- 它是否从父组件来并通过props传递?如果是,它也许不是state。
- 是否随时间变化它依然不改变?如果是,它也许不是state。
- 你是否能基于组件里其他state或者props计算出它?如果是,它不是state。
原始的产品列表作为props传递,所以他们不是state。搜索文本和选择框当它们随时间改变而且不能通过其他值计算出来,它们看起来像是state。最后,过滤后的产品列表不是state因为联合原始的列表还有搜索文本和选择框的值就可以计算出过滤后的产品列表的值。
所以,最终,state就是:
- 用户输入的搜索文本
- 选择框的值
步骤4:确认state应该位于哪里
现在我们已经确认了最小限度的state。下一步,我们需要确认哪一个组件会改变或者拥有这个state。
记住:React是单向数据流。也许不能很快确定哪一个组件拥有state。这一步对于新手来说往往是最具挑战性的部分,所以请跟随以下步骤来解决:
对于应用里每一个state:
- 确认每一个基于state渲染UI的组件
- 找到一个共同的组件拥有者(层级中在所有组件之上的单个组件并且需要state)
- 要么是共同拥有者要么是另外一个更高位置的组件拥有state
- 如果你找不到拥有state的那个组件,那就新创建一个组件来拥有state并且把它添加到在共同拥有者之上的层级的某处
让我们对我们的应用使用这个策略:
- ProductTable需要基于state过滤产品列表,SearchBar需要显示搜索文本和选择框状态
- 共同拥有者组件是FilterableProductTable
- 搜索文本和选择框值应该在FilterableProductTable组件中
酷,我们已经确认了我们的state存在于FilterableProductTable组件中。首先,添加一个实例属性this.state={filterText: '', inStockOnly: false}到FilterableProductTable组件的构造函数constructor里反映出state的初始值。然后,给ProductTable组件和SearchBar组件传递filterText和inStockOnly作为一个prop来传递。最后,使用这些props去过滤ProductTable里的行并且在SearchBar的表单域里设置值。
现在你可以看看你的应用会怎样运行:设置filterText为“ball”然后刷新应用。你会看到数据表会正确地刷新。
步骤5:添加相反的数据流
到目前为止,我们创建了一个能够通过利用props和state从层级自顶向下传递的函数正确渲染UI的应用。现在是时候支持另外一种数据流了:在底层中的表单组件需要在FilterableProductTable组件里更新state。
React使得这种数据流很明白的让你理解程序如果工作,但是它比传统的双向绑定需要更多的代码。
在现在的例子中如果你试图输入或选中选择框,你会发现React忽略了你的输入。这是故意的,我们已经设置input的value属性总是和FilterableProductTable组件传递过来的state值一样。
来思考我们想达到一种什么效果。我们想要确保无论用户怎样修改表单,我们都会更新state来反映用户输入。组件应该只更新自己的state,FilterableProductTable组件会传递回调函数给SearchBar组件无论何时state应该被更新的时候回调函数就会被调用。我们可以在input上使用onChange事件来获取通知。FilterableProductTable组件传递的回调函数会去调用setState(),然后应用会被更新。
虽然这些听起来很复杂,但是只是几行代码而已。而且数据在应用中的流动是很简单明确的。
就这些了
希望如此,这个文档给你一些思路关于怎么使用React创建组件和应用。虽然也许它比你以前的代码量大,但是记住代码的阅读总是比写代码重要,并且模块化的代码很简单明确。当你开始创建大量的组件库的时候,你会感激这种模块化和简单明确,而且通过代码重用,你的代码量会开始减少。:)