React其实在我看来就是一个利用 javascript
来快速构建大型 web
应用的工具. 它在 Facebook
和 Instagram
的项目里都体现了很好地扩展性. 而在使用react
的过程中最重要的一点就是在于你如何看待你即将要做的这个web
应用它到底具有什么样的结构. 接下来我们通过一个搜索商品的小应用, 来讲说说如何通过 React
开发 web 应用
.
准备数据
假如我们已经有了一个查询商品数据的 API
, 它会返回 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"}
];
还有一个大概mock
页面:
第一步 UI
这里我们先来说说如何把上面的 mock 页面进行拆解, 并且给他们起一个比较可读的名字, 如果你和一个设计人员一起干活估计他们已经给你起好名字了, 不过你要告诉他们请将这些名字和你的命名保持一致.
不过在切分的时候如何把握粒度呢, 其实这个就好比写代码的时候什么时候是一个函数搞定,还是什么时候一个对象搞定的问题, 我们都可以拿单一职责原则来说事儿. 最好能够将一个组件拆分到最小尺度.
或者可以看出我们只不顾是将 JSON
数据展现给用户, 而 JSON
的数据结构恰恰和 UI
的组件结构的划分有着一定的相似之处. 我们完全可以想象成将 UI 的组件的划分作为一种 JSON
数据结构的映射关系. 之后在具体展示的时候就是讲对应部分的数据展示到对应的 UI 组件上的过程.
最后通过我们的精心划分, 我们可以发现有5个部分构成:
- FilterableProductTable (orange): 最外面这个框框表示整个 app 本身
- SearchBar (blue): 表示用户的输入
- ProductTable (green): 基于用户输入的信息展示搜索数据
- ProductCategoryRow (turquoise): 展示每一个类别的头部
- ProductRow (red): 展示每一个商品;
这里看看这个 ProductTable
, 你会发现那个表的头部并没有单独创建自己的组件.其他呢也就是因为简单没有那么处理.如果将来会变得复杂完全可以把它们拉出去作为单独的组件处理.
现在呢我们已经将 mock 的 UI 里完全把各自对应的组件抽象出来了.接着我们把它们组织起来,这个简单,对应的组件如果在 mock 的 UI 里输入某个组件,那么它就是另外一个组件的子组件.这样就形成了结构树:
- FilterableProductTable
- SearchBar
- ProductTable
- ProductCategoryRow
- ProductRow
第二步利用 React 创建一个静态的版本
源码看这里: https://gist.github.com/anonymous/13d737aefb7841691ca560e1a97c6eec
通过上面的代码,我们已经有了第一版, 通常呢第一版我们会先罗列出组件体系结构, 然后利用数据结构渲染出 UI. 而不会去care 交互方面的东西, 因为第一版我们不想去考虑过多的这些相对复杂的内容,只是一味地码字堆砌出大概的框架.
在数据和 UI 绑定的过程中我们通过使用 props
来传递数据.props
是一种通过父组件传递数据到子组件的方式. 可能你想到为什么没有使用 state
, 这里我们不会使用 state,因为它只是用来在处理交互的时候维护 ap 状态用的.
在上面的这个创建静态版本的过程中你已经拥有了一系列的可重用的库,他们只用render()
方法, 然后数据就通过顶层的组件 props
往下传递,一旦数据发生变化, 再次调用 render()
函数 UI 就会更新. 这个过程我们可以很容易看出什么时候 UI 会被刷新,什么地方的改变会引发 UI 的改变. 而这些都得益于 React 的 反向数据流, 是它让这一切都变得可见可控.
休息一下,咱们来看看这个 props 和 state
在 React 中有两种数据模型- props 和 state, 如果你此时犯迷糊回过头咱们再去看看文档
第三步实现交互保存 UI 状态
实现 UI 的交互,就需要通过触发一些事件来修改 app 的数据对象. 在 React 中我们通过 state
这个对象来完成.
为了能够正确的处理并描述 app 的状态, 你需要设计并确定一个最小的数据对象集合,他们是用来保存 app 的状态. 这里就要提到 DRY 原则. 你只需要找出一个最小化的对象集合它就能够描述清楚 app 状态.例如要做一个 todo 的 app, 你只需要保存这些 todo 的项目就好,根本没有必要吧当前有多少 todo 的项目的数字拿出来单独保存. 这个你只需要最后那这个 array 的 length 就好了.
我们通过上的静态 UI 实现过程, 可以得到当前这个app 当中有些这样的数据片段:
- 商品 list
- 搜索框
- 复选框
- 过滤条件
那这些东西中什么可以作为 state
来用呢? 我们需要考虑下面3个问题:
- 如果它的值来自于父类的,并且是通过
props
来的值,它基本上不是 state 范畴 - 它基本上不会发生变化, 那它也不是
- 你可以基于当前组件中其他的 state 或者 props 算出这个值,那它也不是 state.
所以, 对于商品的 list 来说,它完全是由 props 参数带来的所以它不会是,搜索框和复选框应该是, 因为他们每次在交互之后会发生变化. 最后那个过滤条件并不是,因为它可以通过其他的条件算出来.
最终, 我们的 app state:
- 搜索框
- 复选框
看第二步的的变化: https://github.com/xiangzhuyuan/thinking-in-react/pull/2/files?diff=unified
第四步 实现 State
在理清楚最小的 state 之后,接下来我们要分析哪些组件是可变的, 或者他们拥有自己是 state. 还是要提醒就是 React 它完全是单向的数据流, 然后贯穿整个组件树. 可能刚开始不会立即得到一个清晰的 state 结果,这个也是对于初学者造成的一个困惑. 不要着急我们通过一步步的分析得出结论:
对于 app 里每一个可拆分的 state 片段来说:
- 每一个片段都可以找到对应的组件,然后渲染点什么东西.
- 找出一个公共的组件拥有者(一个单独的组件它对于 state 来说都是必须要的)
- 不管是公共的还是更高层的组件都应该有自己的state
- 如果你发现 某段 state 找不到合适的组件,那就创建一个组件,然后注册到上面的公共组件上,
让我们来通过上面的分析来实现当前的 app:
-
ProductTable
它需要基于当前的状态过滤显示 list, 而searchbar
需要显式当前的搜索条件和选中的值 - 这个公共的组件所有者是
FilterableProductTable
- 从概念上来说, 过滤条件和选中值应该属于这个公共的
FilterableProductTable
.
知道了上面这些结论, 我们就能够得出结论就是 state 它是属于这个 FilterableProductTable
. 首选添加一个 getInitialState()
方法给 FilterableProductTable
. 然后返回一个{filterText:'',isStockOnly:false}
, 用来初始化 app. 然后将 filterText
和 isStockOnly
作为 props
给 ProductTable
, SearchBar
. 最终, 这些属性能够起到过滤的作用在 ProductTable 里.
第五步添加反向数据流
到这里,我们已经能够正确地让 props 和 state 通过函数来完成组件结构从上至下的传递. 这里我们也是希望能够支持数据的另外一个流向:从组件深处出发的一个状态更新操作. React 把他实现的很明确就是让你知道它到底是怎么工作的.不过相对于双向数据绑定它需要更多的工作要做.
如果你尝试输入条件或者选中过滤条件,会发现程序它其实不会做出对应的反应.这个是很正常的因为从来状态里的输入条件和过滤提交都是从 state 里传递给 FilterableProductTable 的. 这里让我们想想看我们希望它发生什么, 我们应该对于用户的输入做出相应的状态 state 修改. FilterableProductTable
应该给 SearchBar
一个回调用来处理用户的输入,输入之后做出更新的操作.如我们可以在 input
标签上用 onChange
事件来监听,之后发出回调给 FilterableProductTable, 让他来通过setState()
更新状态完成更新. 听上去很复杂其实用几行代码就可以搞定.
看看代码的变化: https://github.com/xiangzhuyuan/thinking-in-react/pull/3/files
总结
希望上面的这些可以给你一个比较清晰的认识,关于如何通过 react 来实现组件,然后通过组件搞定一个 app. 记住可读的代码可重要了, 想想看我们这些代码有多么的独立,明确.当你以后再做大项目的时候, 写各种组件的时候你就会感激幸亏有这样的代码,可读性,重用性等等.