Thinking in React (组件拆分原理)

来源:互联网 发布:导师睡研究生知乎 编辑:程序博客网 时间:2024/06/08 10:49

自己的理解加上对原文的翻译,因为水平的限制,只是尝试翻译以提高自己。见者勿怪,欢迎指正


Thinking in React (组件拆分原理)

  在我看来,React是一种利用JavaScript来建立大而快的应用。它已经在Facebook和Instagram得到广泛的应用。

  React中最重要的一部分是让你思考怎么建立你的应用。在这片文章,我将带领你思考一个利用React来创建一个可搜索的产品数据表。

从一个例子开始

  假设现在我们有一个JSON API和相关的实例。

Mockup

JSON API内容如下:

[  {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组件的工作就显得微不足道。只是要把它拆分成能够精确描述你的一种数据模型的组件。

Component diagram

如上图,看到在我们的简单的应用中分离出了五个组件。用斜体标示除了每个组件代表的数据。

1. FilterableProductTable (橙色):最大的父组件,来包含其他所有组件。

2. SearchBar (蓝色): 接受用户输入的数据

3. ProductTable (绿色):根据用户输入来过滤数据

4. ProductCategoryRow (青色): 产品 目录

5. ProductRow (red): 每一种 产品 的列表


   查看 ProductTable 会发现表头(包含“Name”和“Price”文本)并没有自己的组件。这是一种个人的偏好,无论哪种拆分方式都存在着争议。在这个例子中,我把这个表头作为  ProductTable 的一部分,是因为它也是 ProductTable 负责渲染的数据集合中的一部分。但是,如果表头比较复杂的时候(比如增加排序)它当然也要自己的 ProductTableHeader组件。

  我们已经在上边的例子中规划好了组件,现在就需要来构建结构树了。同样简单的是,如果一个组件被包含在里一个组件中,那么在结构树中应该作为子节点:

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

第二步:建立一个静态版本(写死的数据,只使用render来构建dom)


  现在我们知道组件结构,下边开始要实现你的应用了。最简单的方法是建立一个把你的数据渲染到UI但是组件之间没有交互的版本(静态版本)。解耦这些程序很简单,因为一个静态版本需要的仅仅是无思考的打字,但是如果添加交互的话,就需要很多的思考、很少的代码量。来看看为什么:

  建立一个静态版本,去渲染你的数据模型的时候,你想在建立的模型中重用其它组件和使用数据就要使用 props 关键字。props 是将父组件的数据传递给子组件是使用的。如果你熟悉state 关键字的使用原则--不要使用 state 来建立静态版本。state 仅用来服务交互性,也就是state绑定的数据每时每刻都在改变。建立一个静态版本你不需要用到它(props绑定的数据为静态固定数据)。

  你构建应用的时候,可以从顶层到底层,也可以从底层到顶层。也就是你可以先构建结构树中顶层的父组件(FilterableProductTable)或者从底层的子组件(ProcuctRow)。在简单的应用中,通常从顶层开始更简单。但是相对大点的应用,从底层开始更容易测试。

  到了这一步你会拥有一个可重用的组件库来渲染你的数据模型。静态版本的这些组件仅仅拥有render()方法。结构树中顶层的组件(FilterableProductTable)把你的数据初始成为props。如果你在数据模型中更新数据,前端UI就会再次更新。很容易看到你的UI更新并且是什么原因导致的更新,因为在React的单项数据流动使得代码模块化,容易推断这些不负责的变化,并且更新UI速度很快。

  这一步如果你还有什么不懂,可以简单阅读下React docs。


一个简单小插曲:props vs state

  在React中模型化的数据有两种形式:props和state。理解这两个关键字的差异很重要。有什么不清楚的可以阅读 the official React docs。


第三步:确定你的UI状态符合最小限度但是完整原则

  为了使你的UI界面具有交互性你需要使相关的数据能够触发变化。React使用state将这些变得简单。

  为了正确建立你的应用,你需要考虑应用需要的易变状态的最小集合。关键所在就是DRY--即避免重复。找出那些应用需要和计算需求变化时的状态的绝对最小表示。比如,你构建一个TODO列表,只需要关注TODO的items数组,不要关注数组长度的变化;相反的,当你想统计TODO时就只需要关注items数组长度即可。

  思考我们的例子中的数据组成:

     1.原始的未经过滤的产品列表

     2. 用户可以输入的搜素框

     3. 复选框的值

     4. 过滤后的 产品列表

  下边我们就一一对应这些数据,看那些是应该用state绑定的。对每一个数据我们需要简单问三个问题:

     1. 是否是从一个父组件用props传递过来的?如果是,它就不能用state

     2. 数据是否会随时可能变化?如果不是,它也不能用state

     3. 在你的组件中是否可以根据其它state或props绑定的数据来计算这个数据,如果能,它也不能用state

  没有经过过滤的产品列表时通过props传递过来的,所以它不能用state;

  可输入的搜索框和复选框的值看起来是可以state的,因为它们随时可能改变并且不会被其它数据影响;

  最后,过滤后的产品列表不能用state,因为它可以根据原始的产品列表和过滤条件计算出来;

  所以最后确定可以使用state的是:

     1. 用户输入的搜索框的值;

     2. 复选框的值;


第四步:确定你应该在哪里用state绑定数据


OK,我们已经确定了应用状态的最小集合。下一步我们需要确定哪些组件发生改变,或者拥有这些状态。

谨记:React是全部应用的单项数据流动的组件结构(初始化的交互数据都在父组件,然后通过props传递)。有可能我们不能够直接弄清楚哪些组件应该用state绑定数据。这也是初学者经常遇到的挑战,所以可以根据下边的步骤来区分:

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

  1. 确定每一个组件都使用了state绑定的数据渲染了些东西;

  2. 找到一个共同的父组件(一个单独的在所有组件之上的父组件,注意这些组件是应该有交互性的,就是说需要用到state来绑定数据的);

  3. 这个共同的父组件或者另一个高层次的组件应该使用state;

  4. 如果你还不能找到哪个组件应该使用state,就创建一个新的组件来hold住state,然后把这个组件作为那个共同的组件的父组件(即将其添加在共同父组件的层次结构之上);

让我们使用以上原则来思考我们的例子:

  1. ProductTable 需要依据state绑定的数据来过滤产品列表,而 SearchBar 需要展示出搜索文本和选中状态;

  2. 共同的父组件为 FilterableProductTable;

  3. 所以从概念上看,state绑定的过滤条件和选中状态的值应该在 FilterableProductTable中;


PL,我们已经确定在FilterableProductTable使用state了。首先,在FilterableProductTable中添加一个方法getInitialState()并返回{filterText: '', inStockOnly: false}来初始化你的应用状态。然后分别在ProductTable和SearchBar中用props传递数据filterText和inStockOnly。最后,使用传递过来的数据来过滤ProductTable的产品列表,同时在SearchBar设置表单字段的值(同上都是用来初始化表单字段的值)。

第五步:添加反向数据流(就是将改变从子组件通知给父组件)


  到目前为止,我们已经将数据流动和用props传递的回调函数正确的渲染到了应用中。现在就需要将子组件的改变反馈到父组件,然后父组件再通知给其它子组件:即将子组件SearchBar的改变通知给FilterableProductTable更新state。

  React使数据流动更加清晰,以方便你了解你的应用的工作流程,但是它的确相比双向数据流动需要稍多一点的码字。React也提供了一个叫 ReactLink的扩展,来支持双向绑定,这里就不再赘述。

  如果你在当前示例的这个版本尝试输入或是选中按钮,你会发现React忽略了你的输入。这是故意的,因为我们已经将input的属性value等于从父组件FilterableProductTable用state绑定的数据(通过调用回调函数实现)。

  我们来思考是怎么实现的。我们想要确定当用户更改输入,页面数据会更新来体现用户的输入。由于组件应该只更新自己的state,所以FilterableProductTable会传递一个回调函数给SearchBar,这样当改变时就会调用回调函数然后更改state绑定的数据。可以使用onChange事件绑定这个回调函数,来监听输入的改变。回调函数最终传递给FilterableProductTable,然后激活setState()方法,最后是应用更新。

 虽然看起来很多情况下真的要多些代码。但是它的确清晰的表达出你的应用的数据流动。

0 0