引言
我们认为,React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。它在 Facebook 和 Instagram 上表现优秀。
先试想一下,如果让你做一个数据搜索表格页面,你会怎么做?草图如下:
Json数据:
1 | {category: '体育用品', price: '$49.99', stocked: true, name: 'Football'}, |
设计流程
设计组件的原则;根据json数据,创建组件。如果你学过其它的编程语言,可以把组件理解为函数或者是对象,然后根据单一功能原则来判定组件的范围。说人话就是一个组件它应该只负责一个功能。如果你的组件需要有很多的功能,则应该把该组件拆封成更小的组件,然后把它组装起来变成你想要的功能。
上图中一共有五种颜色的方法,它代表五个组件。
- 最上级的组件
FilterableProductTable
(橙色): 是整个例子应用的整体 - 搜索框
SearchBar
(蓝色):接收用户输入的数据 - 商品表
ProductTable
(绿色):用来暂时商品的列表以及筛选后的列表 - 产品分类的标题
ProductCategoryRow
(天蓝色): 为每一个产品类别展示标题 - 商品
ProductRow
(红色): 每一行展示一个产品
其中商品表
ProductTable
的Name和Price没有独立成一个组件。这只是一种偏好,就本例子来说,因为表头只是起到渲染数据结合的作用,所以这个于ProductTable
是一致,所以仍然把它保留为ProductTable
的一部分。但是如果你的表头过于复杂,例如有筛选、排序、数据导出等功能,那么应该把它独立成一个TableHeader
组件就非常有必要了。层级划分如下:
FilterableProductTable
可以过滤的表单SearchBar
搜索栏ProductTable
商品展示表ProductCategoryRow
商品类别的标题ProductRow
商品
- 最上级的组件
先使用React创建一个静态版本的页面,最容易的方法是先创建一个不含交互功能的UI界面。最好将UI和交互这两个过程分开设计。原因是编写静态页面版本的时候,需要编写大量的代码,而不需要考虑过多的细节;而添加交互功能的时候,则需要考虑大量的细节,而不需要编写太多的代码。所以,将这两个过程分开更为合适。
在构建静态版本时,需要创建一些会重用的组件,然后就涉及到数据的传递。数据传递有props和state,而props是父组件向子组件传递数据的方式。即使你熟悉了state的概念,也完全不应该使用state构建静态版本。state代表了随时间会产生变化的数据,应该在实现交互时使用。所以构建静态应用的时候,不要使用它。
在构建应用时,可以采用自上而下,或者自下而上构建应用。自上而下意味着首先编写层级较高的组件(比如本文中的
FilterableProductTable
)。自下而上意味着从最基本的组件开始编写(比如ProductRow
组件)。当你的应用比较简单时,使用自上而下方式更为方便;对于较为大型的项目来说,自下而上地构建,并同时为低层组件编写测试是更加简单的方式。由于目前只需要构建一个静态版本的应用,所以我们的组件只需要提供
render()
方法用于渲染即可。最顶层的组件FilterableProductTable
通过props接收数据参数。如果你的数据模型发生了变化,则再次调用ReactDOM.render()
,UI就会相应的更新。数据模型变化、调用Render()
方法、UI就会相应变化。React的单向数据流(单向绑定)的思想使得组件模块化,易于快速开发。state
和props
之间的区别是什么?props
(“properties” 的缩写)和state
都是普通的 JavaScript 对象。它们都是用来保存信息的,这些信息可以控制组件的渲染输出,而它们的一个重要的不同点就是:props
是传递给组件的(类似于函数的形参),而state
是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。下面是一些不错的资源,可以用来进一步了解使用
props
或state
的最佳时机:确定需要使用的State
使用State有一个原则,只保留应用所需要的可变的State,其它的数据由他们计算而出。例如你编写一个任务清单应用,应该用一个数组来保存所有事情,而不要再定义一个State保存任务个数。当需要任务个数的时候,应该使用数组的length属性即可。
本例子有如下的几个数据:
- 所有产品的列表
- 用户输入的关键字
- 复选框是否被勾选
- 经过筛选后的产品列表
现在需要通过下面的三个问题,来判断是否需要使用State存储:
- 该数据是否通过props父组件传递来的?如果是的话,那么它不应该使用State存储。
- 该数据是否会随着时间的推移而保持不变?如果是的,那么它也不应该使用State
- 你能否通过其他的props或者state计算出该数据?如果是话,那么它也不是使用State
包含所有产品的列表,它是由父组件的props传入。所以它不是State,如下:
1
2
3<ProductTable
products={this.props.products}
/>搜索词和复选框应该使用State,因为它们无法由其它数据计算,而且会随着时间改变
1
2
3
4
5
6// 保存搜索关键字
handleFilterTextChange(filterText) {
this.setState({
filterText: filterText
})
}1
2
3
4
5
6// 保存复选框状态
handleInStockOnlyChange(inStockOnly) {
this.setState({
inStockOnly: inStockOnly
})
}经过筛选后的列表,是可以由原始产品列表和搜索词和复选框计算出来的,所以它不应该使用State
综上所述,属于State有:
- 用户输入的搜索词
- 复选框是否选中的值
确定State存放的位置
现在已经在第三步的时候确定了使用哪些State了。接下来,我们还需要确定哪些组件改变了这些State,或者说拥有这些State。
注意:React中的数据流都是单向的。所谓单向是指不管是父组件或是子组件,都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。而且它顺着组件层级从上往下传递。
所以哪个组件拥有State的问题,对于初学者来说比较难以理解,尽管如此但是还是可以通过下面的几个步骤来判断:
对于应用中的每一个State:
- 找到根据这个State进行渲染的所有组件
- 找到他们的共同的父组件
- 他们的共同父组件或者比这个共同父组件层级更高,应该拥有这个State
- 如果你找不到一个合适的组件来存放State,那么就直接创建一个新的组件来存放该State,并将这个新组建位置高于共同所有者组件层级的位置
根据以上五个步骤,我们例子的State位置判断的结果如下:
- ProductTable需要根据State筛选产品列表。
SearchBar
需要展示搜索词和复选框状态 - 他们俩共同的父组件是
FilterableProductTable
- 所以,搜索词和复选框的值应该很自然的放在
FilterableProductTable
组件中
根据上面三个步骤,我们已经决定了把State存放在
FilterableProductTable
组建中。因此我们需要开始添加State了:将
this.state = {filterText: '', inStockOnly: false}
添加到FilterableProductTable
的construtor
中设置初始值。1
2
3
4
5
6
7constructor(props) {
super(props);
this.state = {
filterText: 'H',
inStockOnly: false
}
}将
filterText
搜索关键词和inStockOnly
复选框勾选作为 props 传入ProductTable
和SearchBar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15return (
<div>
<SearchBar
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
onFilterTextChange={this.handleFilterTextChange}
onInStockOnlyChange={this.handleInStockOnlyChange}
/>
<ProductTable
products={this.props.products}
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
</div>
);最后,用这些 props 筛选
ProductTable
中的产品信息,并设置SearchBar
的表单值。
添加反向数据流
在做这件事之前,先要明白一个概念,什么是反向数据流?前面我们学过,目前React可以通过props和State传值。但是State是只能是某个组件内使用,其它的组件也不知道State里有什么内容,这个称为单向数据流。所以,现在如果其它的组件想读取或者修改这个State的值,就称为反向数据流了。概要的说:反向数据流是处于较低层级的表单组件,需要更新较高层级的
FilterableProductTable
中的State中React通过一种比传统的双向绑定更加繁琐的方法来实现反向数据传递。尽管如此,但这种显示声明的方法更有助于人们理解程序的运作方式。
如果你在这时尝试在搜索框输入或勾选复选框,React不会产生任何的响应。这个是正常的,因为我们之前已经将Input的值设置为从
FilterableProductTable
的State传递而来的“固定值”重新梳理一下需要实现的功能:
- 每当用户改变表单的值,我们需要改变State来反映用户的当前输入。
- 由于State只能由拥有它的组件进行更改,那么
FilterableProductTable
必须要提供一个触发State改变的回调函数onInStockOnlyChange
(callback)传递给SearchBar
。然后可以在SearchBar
的输入框中使用onChange
事件来监听用户的输入变化,并通知FilterableProductTable
传递给SearchBar
的回调函数onInStockOnlyChange
- 该回调函数,将会调用
setState()
,从而更新应用
源代码地址
带注释的源代码:https://gist.github.com/shanlanCoding/ba8b016e871b22ce505c7aa6696d7bb1