自己的理解加上对原文的翻译,因为水平的限制,只是尝试翻译以提高自己。见者勿怪,欢迎指正
Thinking in React (组件拆分原理)
在我看来,React是一种利用JavaScript来建立大而快的应用。它已经在Facebook和Instagram得到广泛的应用。
React中最重要的一部分是让你思考怎么建立你的应用。在这片文章,我将带领你思考一个利用React来创建一个可搜索的产品数据表。
从一个例子开始
假设现在我们有一个JSON API和相关的实例。
JSON API内容如下:
[ {category: "Sporting Goods",price: "$49.99",stocked: true,name: "Football"},{category: "Sporting Goods",price: "$9.99",name: "Baseball"},price: "$29.99",stocked: false,name: "Basketball"},{category: "Electronics",price: "$99.99",name: "iPod Touch"},price: "$399.99",name: "iPhone 5"},price: "$199.99",name: "Nexus 7"} ];
第一步:把实例的UI拆分成具有层次感的组件
首先在实例建立一个盒子来包容所有的组件和子组件,并且给这些组件命名。如果你和一名设计师一起工作,你就得通知他。他的Photoshop构建的层的命名也可能应用你的命名。但是你怎么知道什么时候这部分代码应该有自己的组件呢?根据是否要创建一个新的函数或者对象的原理,也适用于React构建组件。这些原理中的一种叫做单一责任原则,也就是一种组件只做一件事情。如果它最终增长,它就应该拆分成更小的组件。
如果你经常给用户演示一个JSON数据模型,你会发现如果你的UI模型构建的很好,模型将能够很好的映射出数据。这是因为用户界面和数据模型倾向于应用同一种信息架构,相比下来拆分UI组件的工作就显得微不足道。只是要把它拆分成能够精确描述你的一种数据模型的组件。
如上图,看到在我们的简单的应用中分离出了五个组件。用斜体标示除了每个组件代表的数据。
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的是:
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()方法,最后是应用更新。
虽然看起来很多情况下真的要多些代码。但是它的确清晰的表达出你的应用的数据流动。