资讯专栏INFORMATION COLUMN

React思维方式·译

helloworldcoding / 2394人阅读

摘要:搜索文本和多选框因为会发生变化,且不能通过计算得出,所以是状态。最后,过滤过的产品列表,可以通过原始产品列表搜索文本和多选框值计算出来,因此它不是状态。从传入的回调函数会调用,从而更新组件。

在使用JavaScript开发大型、快速的网页应用时,React是我们的首选。在Facebook和Instagram,React很好地减少了我们的工作量。
React最强大部分之一,是让你在开发应用的同时,按照开发应用的思路去思考。这篇文章,将指导你通过几个步骤,用React开发一个可搜索数据表

从一个模型开始

假设我们已经有了一个JSON API和一个设计师设计的模型。看起来像这样:

我们JSON API返回的数据看起来像这样:

</>复制代码

  1. [
  2. {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  3. {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  4. {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  5. {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  6. {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  7. {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
  8. ];
第一步:将UI分解为组件结构

首先,在模型图上框处每个组件(和子组件),并给组件命名。如果与设计师一起工作,他可能已经心中有数,不妨和他聊聊。他们Photoshop图层名可能就能成为你的组件名!

但如何划分父组件子组件呢?就像创建一个函数或对象一样。其中之一是单一职责原则,理想情况下,一个组件仅做一件事。如果一个组件太大,它应该被分解为更小的子组件。

如果经常把JSON数据模型展现给用户,你会发现,若模型创建正确,UI(以及组件结构)能很好的反映数据。这是因为UI和数据模型一定程度都是依附于信息架构的,这意味着,将UI分解为组件并不是最重要的,最重要的是,被分解的组件能准确表现你数据模型的一部分。

可以看出,我们的app有五个组件,我们用斜体表示每个组件所代表的数据。

FilterableProducTable(可过滤产品表,橙色):包含整个示例

SearchBar(搜索栏,蓝色):接收用户输入

ProductTable(产品表,绿色):根据用户输入,展示和过滤数据集

ProductCategoryRow(产品分类行,蓝绿色):显示为每个分类的头部

ProductRow(产品行,红色):每个产品显示为一行

观察ProductTable,你会发现表头(包含“Name”和“Price”标签部分)未被划分为一个组件。表头是否做为组件属于个人偏好,至于使用哪种方式,以下可以作为参考。本例中,表头作为ProductTable的一部分,因为表头属于渲染的数据集的一部分,所以表头在ProductTable的职责范围内。但如果表头变得更为复杂(如为添加排序功能),将表头分离为ProductTableHeader更合理。

现在,我们已经将模型划分为了组件,接着要安排组件的层次结构。这并不难,模型中一个组件出现在另一个组件的内部,在层次结构呈现为它的一个子级。

FilterableProductTable

SearchBar

ProductTable

ProductCategeoryRow

ProductRow

第二步:创建React静态版本

</>复制代码

  1. class ProductCategoryRow extends React.Component {
  2. render() {
  3. return {this.props.category};
  4. }
  5. }
  6. class ProductRow extends React.Component {
  7. render() {
  8. var name = this.props.product.stocked ?
  9. this.props.product.name :
  10. {this.props.product.name}
  11. ;
  12. return (
  13. {name}
  14. {this.props.product.price}
  15. );
  16. }
  17. }
  18. class ProductTable extends React.Component {
  19. render() {
  20. var rows = [];
  21. var lastCategory = null;
  22. this.props.products.forEach(function(product) {
  23. if (product.category !== lastCategory) {
  24. rows.push();
  25. }
  26. rows.push();
  27. lastCategory = product.category;
  28. });
  29. return (
  30. {rows}
  31. NamePrice
  32. );
  33. }
  34. }
  35. class SearchBar extends React.Component {
  36. render() {
  37. return (
  38. {" "}
  39. Only show products in stock
  40. );
  41. }
  42. }
  43. class FilterableProductTable extends React.Component {
  44. render() {
  45. return (
  46. );
  47. }
  48. }
  49. var PRODUCTS = [
  50. {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  51. {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  52. {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  53. {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  54. {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  55. {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
  56. ];
  57. ReactDOM.render(
  58. ,
  59. document.getElementById("container")
  60. );

现在,你已经有了组件的层次结构,是时候实现这个app了。最简单的方式,利用数据模型渲染一个无交互的UI版本。创建组件静态版本需要敲很多键盘,无需过多思考,而添加交互效果需要很多思考,不需要敲很多键盘,这正是极好的解耦过程。

用数据模型创建app的静态版本,你在创建组件时会想着复用其它组件,想着用属性来传递数据,而属性是父级向子级传递数据的方式。即便你熟悉状态的概念,创建静态版本时也不要用任何状态。状态仅用作交互,状态数据随时间而变化,因为静态版本无需任何交互,所以没必要使用任何状态。

在层级结构中,既可以自上而下开始创建组件(从FilterableProductTable开始),也可以自下而上开始创建组件(从ProductRow开始)。简单的例子中,自上而下的方式往往更简单,对于大型项目来说,自下而上的方式更简单,更容易测试。

该步完成时,你已经有了一些可复用的组件,这些组件渲染了你的数据模型。静态版本的组件仅有render()方法。顶层组件(FilterableProductTable)会把数据模型作为一个属性。如果数据模型发生变化,再次调用ReactDOM.render(),UI会被更新。你可以很容易的看出UI是如何被更新的,哪儿发生了改变。React的单向数据流(也叫单向数据绑定),使得一切变得模块化,变得快速。

简述:属性与状态

属性和状态是React中的两种数据模型,理解两者的区别非常重要。如果你无法确定两者的区别,请浏览官方文档

第三步:确定描述UI的最少状态

为了让UI可以交互,需要能够触发数据模型发生改变。React的状态能让此变得简单。

为了能正确的创建app,你要考虑app需要的最少状态。关键在于不重复,使用最少的状态,计算出所有你需要的数据。例如,你创建一个TODO列表,只需要保留一个TODO项数组,不需要再使用一个变量来保存TODO项数量,当你需要渲染TODO项数目时,使用TODO项数组长度即可。

考虑示例应用中所有的数据,我们有:

原始产品列表

用户输入的搜索文本

多选框的值

过滤过的产品列表

简单地问三个问题,让我们过一遍,看看哪些是状态:

是否为从父组件传入的属性?如果是,或许不是状态

是否是随时间不变的?如果是,或许不是状态

是否能通过组件中的其他状态或属性计算出来?如果是,不是状态

原始产品列表是从属性传入的,因此不是状态。搜索文本和多选框因为会发生变化,且不能通过计算得出,所以是状态。最后,过滤过的产品列表,可以通过原始产品列表、搜索文本和多选框值计算出来,因此它不是状态。

最终,我们的状态有:

用户输入的搜索文本

多选框的值

第四步:确定状态应该放置的位置

</>复制代码

  1. class ProductCategoryRow extends React.Component {
  2. render() {
  3. return ({this.props.category});
  4. }
  5. }
  6. class ProductRow extends React.Component {
  7. render() {
  8. var name = this.props.product.stocked ?
  9. this.props.product.name :
  10. {this.props.product.name}
  11. ;
  12. return (
  13. {name}
  14. {this.props.product.price}
  15. );
  16. }
  17. }
  18. class ProductTable extends React.Component {
  19. render() {
  20. var rows = [];
  21. var lastCategory = null;
  22. this.props.products.forEach((product) => {
  23. if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
  24. return;
  25. }
  26. if (product.category !== lastCategory) {
  27. rows.push();
  28. }
  29. rows.push();
  30. lastCategory = product.category;
  31. });
  32. return (
  33. {rows}
  34. NamePrice
  35. );
  36. }
  37. }
  38. class SearchBar extends React.Component {
  39. render() {
  40. return (
  41. {" "}
  42. Only show products in stock
  43. );
  44. }
  45. }
  46. class FilterableProductTable extends React.Component {
  47. constructor(props) {
  48. super(props);
  49. this.state = {
  50. filterText: "",
  51. inStockOnly: false
  52. };
  53. }
  54. render() {
  55. return (
  56. );
  57. }
  58. }
  59. var PRODUCTS = [
  60. {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  61. {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  62. {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  63. {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  64. {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  65. {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
  66. ];
  67. ReactDOM.render(
  68. ,
  69. document.getElementById("container")
  70. );

OK,我们已经确定了app的最少状态。接下来,我们要确定哪个组件是可变的,或者说状态属于哪个组件。

记住,React在层级及结构中,数据向下流动。这或许不能马上清楚状态究竟属于哪个组件。这经常也是初学者认识React最有挑战性的部分。所以,按照以下步骤,弄清这个问题:

对于应用中的每个状态:

确认每个需要这个状态进行渲染的组件

找到它们共有的父组件(层级结构中,所有需要这个状态的组件之上的组件)

让共有父组件,或者比共有父组件更高层级的组件持有该状态

如果你找不到合适的组件持有该状态,创建一个新的组件,简单持有该状态。把这个组件插入层级结构共有父组件之上

将这个策略用到我们的应用中:

ProductTable要根据状态过滤产品列表,SearchBar要显示搜索文本和多选框状态

共有父组件是FilterableProductTable

FilterableProductTable持有过滤文本和多选框值很合理

Cool,就这样决定把状态放置到FilterableProductTable。首先,在FilterableProductTableconstructor中添加示例属性this.state = {filterText: "", inStockOnly: false} ,来初始化状态。然后,将filterTextisStockOnly作为属性传入ProductTableSearchBar。最后,使用这些属性过滤ProductTable中的行,和设置SearchBar中表单的值

第五步:添加反向数据流

</>复制代码

  1. class ProductCategoryRow extends React.Component {
  2. render() {
  3. return ({this.props.category});
  4. }
  5. }
  6. class ProductRow extends React.Component {
  7. render() {
  8. var name = this.props.product.stocked ?
  9. this.props.product.name :
  10. {this.props.product.name}
  11. ;
  12. return (
  13. {name}
  14. {this.props.product.price}
  15. );
  16. }
  17. }
  18. class ProductTable extends React.Component {
  19. render() {
  20. var rows = [];
  21. var lastCategory = null;
  22. this.props.products.forEach((product) => {
  23. if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
  24. return;
  25. }
  26. if (product.category !== lastCategory) {
  27. rows.push();
  28. }
  29. rows.push();
  30. lastCategory = product.category;
  31. });
  32. return (
  33. {rows}
  34. NamePrice
  35. );
  36. }
  37. }
  38. class SearchBar extends React.Component {
  39. constructor(props) {
  40. super(props);
  41. this.handleChange = this.handleChange.bind(this);
  42. }
  43. handleChange() {
  44. this.props.onUserInput(
  45. this.filterTextInput.value,
  46. this.inStockOnlyInput.checked
  47. );
  48. }
  49. render() {
  50. return (
  51. this.filterTextInput = input}
  52. onChange={this.handleChange}
  53. />
  54. this.inStockOnlyInput = input}
  55. onChange={this.handleChange}
  56. />
  57. {" "}
  58. Only show products in stock
  59. );
  60. }
  61. }
  62. class FilterableProductTable extends React.Component {
  63. constructor(props) {
  64. super(props);
  65. this.state = {
  66. filterText: "",
  67. inStockOnly: false
  68. };
  69. this.handleUserInput = this.handleUserInput.bind(this);
  70. }
  71. handleUserInput(filterText, inStockOnly) {
  72. this.setState({
  73. filterText: filterText,
  74. inStockOnly: inStockOnly
  75. });
  76. }
  77. render() {
  78. return (
  79. );
  80. }
  81. }
  82. var PRODUCTS = [
  83. {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  84. {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  85. {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  86. {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  87. {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  88. {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
  89. ];
  90. ReactDOM.render(
  91. ,
  92. document.getElementById("container")
  93. );

目前为止,我们已经用属性和状态,创建了具有在层级结构中至上而下的数据流。现在,是时候支持反向的数据流动,让底层表单组件能够更新FilterableProductTable中的状态。

React的单向数据流动使得程序更加清晰易懂,但相比双向数据绑定,确实需要多打些代码。

如果你尝试在当前示例中,输入文本或选择多选框,会发现React忽略了你的输入。这是故意的,因为我们要设置inputvalue属性要恒等于FilterableProductTable传入的状态。

思考下我们期望的状况。我们希望,表单中无论何时发生改变,用户的输入要能够更新状态。因为组件应该只更新自己的状态,所以FilterableProductTable会将回调函数传递给SearchBarSearchBar会在要更新状态时调用回调函数。我们用onChange事件通知状态更新。从FilterableProductTable传入的回调函数会调用setState(),从而更新组件。

虽然听起来有点复杂,但是也就添加几行代码。它非常清晰地表现出了app中的数据流动。

就这样

希望此文能带给你使用React创建组件和应用的思路。尽管你可能需要打比平时更多的代码,但记住,代码看着总是远比打着的时候多,并且这些代码都是模块化、清晰而易读的。当你开始开发大型组件库的时候,你将会庆幸React的数据流清晰和模块化的特性,并且随着代码的复用,代码规模会开始缩小。

原文链接

thinking-in-react

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81609.html

相关文章

  • ()React hooks:它不是一种魔法,只是一个数组——使用图表揭秘提案规则

    摘要:它并不是实际在内部的工作方式,而且它只是一个提案,在未来都会有可能发生变化。这意味着,数据的存储是独立于组件之外的。因此,有一个诀窍就是你需要思考作为一组需要一个匹配一致的指针去管理的数组染陌译。 原文地址:https://medium.com/@ryardley/... 译文:染陌 (Github) 译文地址:https://github.com/answershuto/Blog 转...

    fjcgreat 评论0 收藏0
  • 】渲染Elements

    摘要:注不做翻译是中最小的构建部件。在里渲染让我们看一下在下面有在你文件中无处不在的标签我们会把这元素成为元素因为的所有东西都会放在这个元素里面。通过方法,我们能吧渲染到我们根节点上。更新被渲染的是不可变的。 下面是react官方文档的个人翻译,如有翻译错误,请多多指出原文地址:https://facebook.github.io/re...特别感谢Hevaen,同时也向豪大React群所有...

    LoftySoul 评论0 收藏0
  • [] 前端攻略-从路人甲到英雄无敌二:JavaScript 与不断演化的框架

    摘要:一般来说,声明式编程关注于发生了啥,而命令式则同时关注与咋发生的。声明式编程可以较好地解决这个问题,刚才提到的比较麻烦的元素选择这个动作可以交托给框架或者库区处理,这样就能让开发者专注于发生了啥,这里推荐一波与。 本文翻译自FreeCodeCamp的from-zero-to-front-end-hero-part。 继续译者的废话,这篇文章是前端攻略-从路人甲到英雄无敌的下半部分,在...

    roadtogeek 评论0 收藏0
  • 1月份前端资源分享

    摘要:更多资源请文章转自月份前端资源分享视频前端技术论坛融合不可错过的迷你库测试框架实例教程为你详细解读请求头的具体含意解析的库如果要用前端框架,开发流程是怎样的与有什么区别正确使用的方法是什么流程图插件小如何让元素只能输入纯文本前端技术中 更多资源请Star:https://github.com/maidishike... 文章转自:https://github.com/jsfront...

    solocoder 评论0 收藏0
  • []学习如何去学习 JavaScript - 5 个你应该如何花在学习 JS 上时间的建议

    摘要:拥抱异步编程纵观发展史也可以说成开发的发展史,你会发现异步彻底改变了这场游戏。可以这么说,异步编程已成为开发的根基。这也是你应尽早在上投入大量时间的一处核心知识点,这其中包含和等重要概念。这也是最突出的一项贡献。 原文地址:Medium - Learning How to Learn JavaScript. 5 recommendations on how you should spend ...

    wanglu1209 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<