UDN-企业互联网技术人气社区

板块导航

浏览  : 653
回复  : 2

[实例] React.js编程思想

[复制链接]
山外青山的头像 楼主
发表于 2015-9-7 16:24:31 | 显示全部楼层 |阅读模式
本帖最后由 山外青山 于 2015-9-7 16:35 编辑

React.js编程思想
     JavaScript框架层出不穷,在很多程序员看来,React.js是创建大型、快速的Web应用的最好方式。这一款由Facebook出品的JS框架,无论是在Facebook还是在Instagram中,它的表现都非常出色。
     使用React.js的好处多多,但是其中非常好的一部分是它能够让你重新思考如何创建一个web应用。在本文中,我们将通过一步一步的创建一个产品数据表,并以此过程带领你思考和领悟React.js的编程思想。
步骤零:创建一些伪数据
假设现在我们已经有了一个JSON API和一些伪数据。如下图所示。当然,现在的设计看起来非常糟糕,但是它可以体现出我们将要创建的应用结构:
1H43140V-0.png

同时,我们可以通过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打散为层级组件
      你需要做的第一件事情是在每一个组件(以及子组件)周围画上一个框,然后给每个组件起一个名字。如果设计图是由一个设计师给你的,他通常已经帮你完成了这件事情,因此你可以跑去问他设计图中各个层的名字,并以这些名字为你的React组件命名。

     但是我们怎么知道哪些部分应该是一个组件呢?想象一下,加入你现在不是在创建一个应用,而是在创建一个新的函数或者对象,你会怎么做。在这里,我们 最好遵循”单一任务原则”,也就是说,一个组件最好只是做一件事情。如果一个组件的任务有所增加,那么你应该考虑是不是要把这个组件拆成几个更小的组件。

     由于你经常需要讲一个JSON数据模型展示给用户,因此你需要检查这个模型结构是否正确以便你的UI(在这里指的就是你的组件结构)是否能够正确的 映射到这个模型上。这是因为用户界面和数据模型在信息构造方面都要一直,这意味着将你可以省下很多将UI分割成组件的麻烦事。你需要做的仅仅只是将数据模 型分隔成一小块一小块的组件以便它们都能够表示成组件。
1H43151A-1.png

在上面的示意图中,你可以看到在我们这个简单的应用中,包含着5个组件:
  • FilterableProductTable(橙色): 包含着整个例子
  • SearchBar(蓝色): 接收用户的输入
  • ProductTable(绿色): 基于用户输入展示和过滤数据集合
  • ProductCategoryRow(青色): 为每一种类型展示一个头部数据
  • ProductRow(红色): 为每种产品展示一行

     如果你注意看ProductTable,你将会看到表头(就是包含着”Name”和”Price”标签的部分)并不是单独一个组件。这并不是什么硬性规定,无论你是否将这部分单独隔离出来都没有错。但是在这个例子中,我们将它作为ProductTable的一个部分,因为它是渲染数据集合的一个部分,而这是ProductTable的任务。然而,如果表头变得很复杂(例如,如果我们在其中添加了排序功能),那么你应该毫不犹豫的将它作为一个单独的ProductTableHeader组件。
现在我们已经理清楚了我们的应用中的组件。我们可以简单的将它们做一个层级排序,如下所示:
  1. - FilterableProductTable
  2.    - SearchBar
  3.    - ProductTable
  4.      - ProductCategoryRow
  5.      - ProductRow
复制代码
步骤二:使用React创建一个静态版本的应用
  1. /** @jsx React.DOM */

  2. var ProductCategoryRow = React.createClass({
  3.     render: function() {
  4.         return (<tr><th colSpan="2">{this.props.category}</th></tr>);
  5.     }
  6. });

  7. var ProductRow = React.createClass({
  8.     render: function() {
  9.         var name = this.props.product.stocked ?
  10.             this.props.product.name :
  11.             <span style={{color: 'red'}}>
  12.                 {this.props.product.name}
  13.             </span>;
  14.         return (
  15.             <tr>
  16.                 <td>{name}</td>
  17.                 <td>{this.props.product.price}</td>
  18.             </tr>
  19.         );
  20.     }
  21. });

  22. var ProductTable = React.createClass({
  23.     render: function() {
  24.         var rows = [];
  25.         var lastCategory = null;
  26.         this.props.products.forEach(function(product) {
  27.             if (product.category !== lastCategory) {
  28.                 rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
  29.             }
  30.             rows.push(<ProductRow product={product} key={product.name} />);
  31.             lastCategory = product.category;
  32.         });
  33.         return (
  34.             <table>
  35.                 <thead>
  36.                     <tr>
  37.                         <th>Name</th>
  38.                         <th>Price</th>
  39.                     </tr>
  40.                 </thead>
  41.                 <tbody>{rows}</tbody>
  42.             </table>
  43.         );
  44.     }
  45. });

  46. var SearchBar = React.createClass({
  47.     render: function() {
  48.         return (
  49.             <form>
  50.                 <input type="text" placeholder="Search..." />
  51.                 <p>
  52.                     <input type="checkbox" />
  53.                     Only show products in stock
  54.                 </p>
  55.             </form>
  56.         );
  57.     }
  58. });

  59. var FilterableProductTable = React.createClass({
  60.     render: function() {
  61.         return (
  62.             <div>
  63.                 <SearchBar />
  64.                 <ProductTable products={this.props.products} />
  65.             </div>
  66.         );
  67.     }
  68. });


  69. var PRODUCTS = [
  70.   {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
  71.   {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
  72.   {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
  73.   {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
  74.   {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
  75.   {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
  76. ];

  77. React.renderComponent(<FilterableProductTable products={PRODUCTS} />, document.body);
复制代码

     既然现在我们的组件已经都创建完毕了,是时候开始实现我们的应用了。最简单的方式是使用数据模型来渲染一个有UI但是暂时没有交互的应用。在这里之所以把交互单独拿出来,是因为如果只是构建UI的话,非常直观而且几乎不需要什么思考,但是一旦涉及到交互,则会复杂的多。


     在创建一个静态版本的应用时,你需要创建一个可以使用给其他组件的组件,同时你需要通过props来传递数据。在React中,props是一种从 父组件将数据传递给子组件的方式。当然,在React中还有一个叫做state的概念,但是在创建一个静态版本的应用时,千万不要使用state。 state仅仅只在有交互时使用,这意味着,数据会随时发生变化。因此,在这里不应该使用state。

     你可以从上到下或者从下到上开始。这就是说,你可以从最外面的组件开始,或者从最里面的组件开始。一般来说,在简单应用中,一半是从上到下,而在大型的项目中,一半是从下到上开始。

     在这一步的最后,你会拥有一个可重用的组件库,这个组件库将会渲染你的数据模型。由于这个版本是一个静态的版本,因此所有的组件中仅仅只有render()方法。最外层的组件(FilterableProductTable)将会接收你的数据模型作为prop。如果你对底层数据模型做了一些修改,并且调用renderComponent()方法,相应的UI将会发生变化。由于React是单向数据流(也可以说是单向数据绑定),同时具有很强的模块化,这些变化查看起来将会非常容易,这也是React运行这么快的原因之一。

     在React中存在两种”模型”数据:props和state。理解二者之间的区别很重要。

步骤三:识别应用UI的状态(state)
     为了让UI变得可交互,你需要能够在数据模型发生改变时触发一些UI的变化。React使用state使得这项工作变得简单。

     为了能够正确的创建应用,首先要思考的第一件事情是应用状态的集合。标准很简单:不要重复你自己。例如如果你创建了一个TODO列表,你只需要创建 一个TODO项目列表,而不需要专门分出一个状态参数来记录TODO项目的数量。当你想要渲染这个TODO数量时,你只需要直接获取TODO数组的长度即可。

     现在思考一下例子中的数据。我们拥有:

  • 原始的产品列表
  • 用户输入的搜索关键字
  • 复选框的值
  • 过滤之后的产品列表

     现在我们来看看上面这些数据,看看哪一个是state。问自己三个问题:

  • 这个数据是否来自父组件中的props?如果是,那么它不是state。
  • 它会随时间改变吗?如果不是,那么它不是state。
  • 它能够基于组件中的任何state或者props计算出来吗?如果是,那么它不是state。

     原始的产品列表作为props传递到组件中,因此它不是state。搜索字段和复选框因为会随时间变化并且不能够由其他state或者props计算出,因此是state。最后,产品的过滤列表不是state,因为它可以根据原始数据、搜索字段以及复选框一起计算出。

因此,我们的state包括:
  • 用户输入的搜索字段
  • 复选框的值

步骤四:弄清楚state应该存在于什么地方
  1. /** @jsx React.DOM */

  2. var ProductCategoryRow = React.createClass({
  3.     render: function() {
  4.         return (<tr><th colSpan="2">{this.props.category}</th></tr>);
  5.     }
  6. });

  7. var ProductRow = React.createClass({
  8.     render: function() {
  9.         var name = this.props.product.stocked ?
  10.             this.props.product.name :
  11.             <span style={{color: 'red'}}>
  12.                 {this.props.product.name}
  13.             </span>;
  14.         return (
  15.             <tr>
  16.                 <td>{name}</td>
  17.                 <td>{this.props.product.price}</td>
  18.             </tr>
  19.         );
  20.     }
  21. });

  22. var ProductTable = React.createClass({
  23.     render: function() {
  24.         var rows = [];
  25.         var lastCategory = null;
  26.         this.props.products.forEach(function(product) {
  27.             if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
  28.                 return;
  29.             }
  30.             if (product.category !== lastCategory) {
  31.                 rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
  32.             }
  33.             rows.push(<ProductRow product={product} key={product.name} />);
  34.             lastCategory = product.category;
  35.         }.bind(this));
  36.         return (
  37.             <table>
  38.                 <thead>
  39.                     <tr>
  40.                         <th>Name</th>
  41.                         <th>Price</th>
  42.                     </tr>
  43.                 </thead>
  44.                 <tbody>{rows}</tbody>
  45.             </table>
  46.         );
  47.     }
  48. });

  49. var SearchBar = React.createClass({
  50.     render: function() {
  51.         return (
  52.             <form>
  53.                 <input type="text" placeholder="Search..." value={this.props.filterText} />
  54.                 <p>
  55.                     <input type="checkbox" value={this.props.inStockOnly} />
  56.                     Only show products in stock
  57.                 </p>
  58.             </form>
  59.         );
  60.     }
  61. });

  62. var FilterableProductTable = React.createClass({
  63.     getInitialState: function() {
  64.         return {
  65.             filterText: '',
  66.             inStockOnly: false
  67.         };
  68.     },

  69.     render: function() {
  70.         return (
  71.             <div>
  72.                 <SearchBar
  73.                     filterText={this.state.filterText}
  74.                     inStockOnly={this.state.inStockOnly}
  75.                 />
  76.                 <ProductTable
  77.                     products={this.props.products}
  78.                     filterText={this.state.filterText}
  79.                     inStockOnly={this.state.inStockOnly}
  80.                 />
  81.             </div>
  82.         );
  83.     }
  84. });


  85. var PRODUCTS = [
  86.   {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
  87.   {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
  88.   {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
  89.   {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
  90.   {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
  91.   {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
  92. ];

  93. React.renderComponent(<FilterableProductTable products={PRODUCTS} />, document.body);
复制代码

     现在我们已经弄清楚了state集合。下面我们要做的事情就是弄清楚这些state应该存在什么地方。

记住:React的数据是通过层级组件单向流动。弄清楚哪个组件应该拥有state并不是一件容易的事情。这往往是React新手最容易弄错的地方。

首先,对于应用中的每一个state,你需要:
  • 找出那些需要基于state渲染的组件
  • 找出一个公共的组件(也就是能够包含所有可能涉及state的组件的组件)
公共组件以及之外的组件应该拥有这个state
如果你找不出一个拥有state的组件,创建一个简单的可以包含这个state的组件,然后将它添加到公共组件之上

     具体到例子中的应用:
     ProductTable需要基于state过滤产品列表,而SearchBar需要展示搜索字段和复选框的状态
     公共组件是FilterableProductTable
     很明过滤字段和复选框值都存在于FilterableProductTable之中
     到目前为止,我们已经弄清楚了我们的state位于FilterableProductTable中。首先,我们需要在FilterableProductTable中添加一个getInitialState()方法,这个方法将会返回{filterText: ", inStockOnly: false}来反映应用的初始状态。其次,将filterText和inStockOnly作为一个prop传递给ProductTable和SearchBar。最后,在ProductTable中使用这些props去过滤,并在SearchBar中设置相应的值。

步骤五:添加反向数据流
  1. /** @jsx React.DOM */

  2. var ProductCategoryRow = React.createClass({
  3.     render: function() {
  4.         return (<tr><th colSpan="2">{this.props.category}</th></tr>);
  5.     }
  6. });

  7. var ProductRow = React.createClass({
  8.     render: function() {
  9.         var name = this.props.product.stocked ?
  10.             this.props.product.name :
  11.             <span style={{color: 'red'}}>
  12.                 {this.props.product.name}
  13.             </span>;
  14.         return (
  15.             <tr>
  16.                 <td>{name}</td>
  17.                 <td>{this.props.product.price}</td>
  18.             </tr>
  19.         );
  20.     }
  21. });

  22. var ProductTable = React.createClass({
  23.     render: function() {
  24.         console.log(this.props);
  25.         var rows = [];
  26.         var lastCategory = null;
  27.         this.props.products.forEach(function(product) {
  28.             if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
  29.                 return;
  30.             }
  31.             if (product.category !== lastCategory) {
  32.                 rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
  33.             }
  34.             rows.push(<ProductRow product={product} key={product.name} />);
  35.             lastCategory = product.category;
  36.         }.bind(this));
  37.         return (
  38.             <table>
  39.                 <thead>
  40.                     <tr>
  41.                         <th>Name</th>
  42.                         <th>Price</th>
  43.                     </tr>
  44.                 </thead>
  45.                 <tbody>{rows}</tbody>
  46.             </table>
  47.         );
  48.     }
  49. });

  50. var SearchBar = React.createClass({
  51.     handleChange: function() {
  52.         this.props.onUserInput(
  53.             this.refs.filterTextInput.getDOMNode().value,
  54.             this.refs.inStockOnlyInput.getDOMNode().checked
  55.         );
  56.     },
  57.     render: function() {
  58.         return (
  59.             <form>
  60.                 <input
  61.                     type="text"
  62.                     placeholder="Search..."
  63.                     value={this.props.filterText}
  64.                     ref="filterTextInput"
  65.                     onChange={this.handleChange}
  66.                 />
  67.                 <p>
  68.                     <input
  69.                         type="checkbox"
  70.                         value={this.props.inStockOnly}
  71.                         ref="inStockOnlyInput"
  72.                         onChange={this.handleChange}
  73.                     />
  74.                     Only show products in stock
  75.                 </p>
  76.             </form>
  77.         );
  78.     }
  79. });

  80. var FilterableProductTable = React.createClass({
  81.     getInitialState: function() {
  82.         return {
  83.             filterText: '',
  84.             inStockOnly: false
  85.         };
  86.     },

  87.     handleUserInput: function(filterText, inStockOnly) {
  88.         this.setState({
  89.             filterText: filterText,
  90.             inStockOnly: inStockOnly
  91.         });
  92.     },

  93.     render: function() {
  94.         return (
  95.             <div>
  96.                 <SearchBar
  97.                     filterText={this.state.filterText}
  98.                     inStockOnly={this.state.inStockOnly}
  99.                     onUserInput={this.handleUserInput}
  100.                 />
  101.                 <ProductTable
  102.                     products={this.props.products}
  103.                     filterText={this.state.filterText}
  104.                     inStockOnly={this.state.inStockOnly}
  105.                 />
  106.             </div>
  107.         );
  108.     }
  109. });


  110. var PRODUCTS = [
  111.   {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
  112.   {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
  113.   {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
  114.   {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
  115.   {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
  116.   {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
  117. ];

  118. React.renderComponent(<FilterableProductTable products={PRODUCTS} />, document.body);
复制代码

     到目前为止,我们的应用已经基本完成了。现在我们需要使用另一种方法来支持数据流:层级深处的组件需要更新FilterableProductTable的state。
在React中完成这件事情非常直观,但是这和传统的双向数据绑定有一些不同。虽然React提供了一个叫做ReactLink的插件来帮助我们做类似于双向绑定的事情,但是为了能够明确其中的具体过程,我们在这里不使用这个插件。

     到目前为止,如果你进行了一些输入或者选中了一些复选框,你会发现React并没有做出反应。这很正常,因为我们将value这个prop的值总是设置为等于来自FilterableProductTable中的state。

     现在来明确一下我们想要做的事情。我们想要确保无论用户在何时做了修改,应用都能正确的反应state。因为组件只可以改变自己的state,FilterableProductTable将会给SearchBar传递一个回调函数,这个毁掉函数将会在state发生变化时被触发。我们可以在输入框上使用onChange事件来进行触发。接着,由FilterableProductTable传递的回调函数将会调用setState(),应用将会被更新。

     尽管听起来很复杂,但是其实需要写的代码非常少。同时,这也清晰的向我们展示了数据流是怎样穿过整个应用的。

总结
     到这里,我们的应用就算是创建完成了。本文可能并不会教你很多关于React的用法,但是你可以从中学习到React应用和模块创建的基本思想。React的代码非常具有模块化,而且易于阅读。现在就开学习使用React来创建不可思议的web应用吧。
本文参考自thinking in react,原文地址http://facebook.github.io/react/docs/thinking-in-react.html


发表于 2015-9-17 18:10:04 来自手机 | 显示全部楼层
LZ是闲人,天天发帖,坚定完毕
使用道具 举报

回复

发表于 2015-9-21 17:14:18 | 显示全部楼层
纯粹路过,没任何兴趣,仅仅是看在老会员的份上回复一下
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部