React 哲学
React 最棒的部分之一是引导我们思考如何构建一个应用。在这篇文档中,我们将会通过React构建一个可搜索产品数据的表格来更深刻的领会React哲学。
从设计稿开始
假设我们已经有了一个返回JSON的API,以及设计师提供的组件设计稿。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPjsKVC8-1607868746795)(https://react.docschina.org/static/1071fbcc9eed01fddc115b41e193ec11/d4770/thinking-in-react-mock.png)]
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"}
]
第一步:将设计好的ui划分成组件层级
首先,你需要在设计稿上方框圈出每一个组件(包括它们的子组件),并且以合适的名称命名。如果你是和设计师一起完成此任务,那么他们可能已经做过类似的工作,所以请和他们沟通!他们Photoshop的图层名称可能最终就是你编写的React组件的名称!
但你如何确定应该将哪些部分划分到一个组件中呢?你可以将组件当做一种函数或者对象来考虑,根据单一功能原则来判断组件的范围。也就是说,一个组件原则上只能负责一个功能。如果他需要负责更多的功能,这时候就要应该考虑将它拆分为更小的组件。
在实践中,因为你经常是在向用户展示JSON的数据模型,所以如果你的模型设计得乔当,UI或者说是组件结构便会和数据模型一一对应,这是因为UI和数据模型都会倾向于遵守相同的信息结构。将UI分离成组件,其中每个组件需要与数据模型的某部分匹配。
你会看见我们应用中包括五个组件。
- FilterableProductTable(橙色):整个应用的整体
- SearchBar(蓝色):接受所有的用户输入
- ProductTable(绿色):展示数据并根据用户输入筛选结果
- ProductCategoryRow(天蓝色):为每一个产品类别展示标题
- ProductRow(红色):每一行展示一个产品
你可能注意到,ProductTable的表头(包含“Name”和“Price”的那一部分)并未单独成为一个组件。这仅仅是一种编好选择。如果这部分功能过于复杂,那么将它作为一个独立的ProctTableHeader组件就显得有必须要了。
- FilterableProductTable
- SearchBar
- ProductTable
- ProductCategoryRow
- ProductRow
第二步:用React创建了一个静态版本
现在我们一级确定了组件层级,可以编写对应的应用了。最容易的方式,是先用已有的数据模型渲染一个不包含交互功能的UI。最好将渲染UI和添加监护这两个过程分开。这是因为编写一个应用的静态版本时,往往需要编写大量代码,而不需要考虑太多交互细节;添加交互功能时则要考虑大量细节,而不需要编写编写太多代码。所以这里两个过程分开进行更为合适。
在构建应用的静态版本时,我们需要创建一些会用重用其他组件的组件,然后通过props传入所需的数据。props是父组件向子组件传递数据的方式。及时你已经熟悉了state的概念,也完全不应该使用state构建静态版本。stae代表了随时间会产生变化的数据。应该仅在实现交互时使用。所以构建应用的静态版本时,你不会用到它。
你可以自上而下或者自下而上的构建应用。自上而下意味着首先编写层级较高的组件,自下而上以为从最基本的组件开始编写。当你应用比较简单的时候,使用自上而下的方式更方便;对于较为大型的项目来说,自下而上的构建,并同时为底层组件编写测试更加简单的方式。
到此为止,你应该已经有了一个可重用的组件库来渲染你的数据模型,由于我们构建的是静态版本,所以这些组件目前只需要一共render()
方法用于渲染。最顶层的组件通过props接受你的数据模型。如果你的数据模型发生了改变,再次调用ReactDOM.render(),UI就会相应的被更新。数据模型变化和调用render()
方法、ui相应变化,这个过程并不复杂。因此很容易看清楚UI是如何被更新,以及是在哪里被更新的。React单向数据流(单向绑定)的思想是的组件模块化,易于快速开发。
补充说明:有关props和state
在React中,有两类“模型”数据:props和state。清楚的理解两者的区别是十分重要的。
第三步:确定UIstate的最小(且完整)表示
想要使你的UI具备交互功能,需要有触发基础数据模型改变的能力。React通过实现state来完成这个任务。
为了正确的构建应用,你首先需要找出应用所需的state的最小表示,并根据需要计算出其他所有数据。其中关键正式DRY:Don’t Repeat yourself。只保留应用所需的可变state的最小集合,其他数据均由他们计算产生。比如,你需要编写一个任务清单应用,你只需要保存一个包含所有事项的数组,而无需额外保存一个单独的state变量(用于储存)任务个数。当你需要展示任务个数的时候,只需要展示任务个数时,只需要利用该数组的length属性既可。
我们的实例应用应该拥有如下数据:
- 包含所有产品的原始列表
- 用户输入的搜索词
- 复选框是否选中的值
- 经过搜索筛选的产品列表
通过问自己一下三个问题,你可以逐个检查相应数据是否属于state:
- 该数据是否是由父组件props传递而来?如果是,那么它应该不是state。
- 该数据是否随时间的推移变化而保持不变?如果是,那它不应该是state。
- 你能否根据其他state或props计算出该数据的值,如果是,那么它也不是state。
包含所有产品的原始列表是经由props传入的,所以它不是state;搜索词和复选框的值应该是state,因为它们是随时间会发生改变并且无法由其他数据计算而来;经过搜索筛选的产品列表不是state,因为它们的结果可以由产品的原始列表根据搜索词和复选框的选择计算出来。
综上所述,属于state的有:
- 用户输入的搜索词
- 复选框是否选中的值
第四步:确定state的位置
我们已经确定了应用所需的state的最小集合。接下来,我们需要确定哪个组件能够改变这些state,或者说拥有这些state。
注意:React中的数据流是单向的,并顺着组件组件层级从上往下传递。哪个组件应该拥有某个state这件事情,对于初学者来说往往是最难理解的部分。尽管这可能在一开始不是那么清晰,但是你可以通过尝试以下步骤来判断:
对于应用中的每一个state:
- 找根据这个state进行渲染的所有组件
- 找到他们共同的所有者组件(在组件层级上高于所有需要改state的组件)
- 该共同所有者组件或者比它层级更高的组件应该拥有state。
- 如果你找不到一个合适的位置来存放该state,就可以直接创建一个新的组件来存放该state,并将这一新组件置于高于共同所有者组件层级的位置。
第五步:添加反向数据流
到目前为止,我们已经借助自上而下传递的props和state渲染一个应用。现在,我们将尝试让数据反向传递:处于较低层级的表单组件更新较高层级的组件中的state。
React通过一种比传统的双向绑定略微繁琐的方法来实现反向数据传递。尽管如此,但这种需要显式声明的方法更有助于人们理解程序的运作方式。
如果你在这时尝试在搜索框输入或勾选复选框,React 不会产生任何响应。这是正常的,因为我们之前已经将 input 的值设置为了从 FilterableProductTable 的 state 传递而来的固定值。
让我们重新梳理一下需要实现的功能:每当用户改变表单的值,我们需要改变 state 来反映用户的当前输入。由于 state 只能由拥有它们的组件进行更改,FilterableProductTable 必须将一个能够触发 state 改变的回调函数(callback)传递给 SearchBar。我们可以使用输入框的 onChange 事件来监视用户输入的变化,并通知 FilterableProductTable 传递给 SearchBar 的回调函数。然后该回调函数将调用 setState(),从而更新应用。
这就是全部了
希望这篇文档能够帮助你建立起构建 React 组件和应用的一般概念。尽管你可能需要编写更多的代码,但是别忘了:比起写,代码更多地是给人看的。我们一起构建的这个模块化示例应用的代码就很易于阅读。当你开始构建更大的组件库时,你会意识到这种代码模块化和清晰度的重要性。并且随着代码重用程度的加深,你的代码行数也会显著地减少。😃