[译]Thinking in React

编者按

使用React的思想来构建应用对我在实际项目中以及帮助他人解决实际问题时起到了很大做用,因此我翻译此文来向那些正在或即将陷入React或React-Native深坑的同胞们表示慰问。网上已经有人翻译过,我想用更易读的语言翻译一次,这也是我首次如此一本正经的翻译技术文章给大众阅读,权当练习吧。html

原文地址:https://facebook.github.io/react/docs/thinking-in-react.htmlreact

转载还请注明出处以及原文地址,出于对做者和译者劳动成果的尊重吧,谢谢了个人哥。git

Thinking in React

做者:Pete Hunt 译者:Rex Rao (sohobloo)github

 

我认为React是使用JavaScript构建高性能大型Web应用的首选方案,咱们已经在Facebook和Instagram中普遍使用,哎哟,效果不错哟。架构

React的众多优点之一是——且看它如何让你能顺着思路构建应用。在此,我将引领你用React逐步构建出一个可搜索的商品列表应用。ide

从模型图开始

假设设计师已经为咱们提供了API并能够返回模拟的JSON数据。容我小小鄙视一下这位美工,由于原型图长成这个挫样:函数

咱们的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"}
];

第一步:拆分并构建界面的组件层次结构树

你应该作的第一件事是为你原型图全部组件和子组件画个边框、起个名。要是你跟设计师坐一块儿,找他们喝喝茶,说不定他们的Photoshop图层名恰巧能够用做你React组件的名字!(译者:我只能说,Too young too simple, sometimes naive!) 单元测试

但你怎么知道如何拆分一个组件呢?这和你平时决定是否是要新建一个函数或者类的道理同样同样的。其中有个叫作单一职责原则的原理,也就是说理想状态下一个组件只作一件事,当他须要作更多,那就应该继续拆拆拆。测试

若是你常常向用户展现JSON数据,你会发现只要你的数据模型建得好,你的界面乃至你的组件架构也会完美的与之映射。由于界面和数据模型倾向于支持相同的信息架构,这让界面拆分工做变简单了,拆分出的一个组件只对应展现数据模型中的一种数据就行。

你看,咱这简单的应用有5种组件。我用斜体标示出了每一个组件要展现的数据。

  1. FilterableProductTable (橙色): 包含整个示例
  2. SearchBar (蓝色): 接收用户输入(user input)
  3. ProductTable (绿色): 显示基于用户输入(user input)过滤的数据集 (data collection)
  4. ProductCategoryRow (青色): 显示分类( category)
  5. ProductRow (红色): 显示每一行商品(product)

ProductTable你会发现表头(含"Name"和"Price"标签)并无拆分红组件,这是出于一种存在争议的我的喜爱而已啦。这个例子中,既然渲染数据集 (data collection)ProductTable的职责,那就让它做为此组件的一部分好了。要是它再复杂一点的话(好比排序功能),那就另当别论独立成ProductTableHeader组件咯。

让咱们把从原型图中定义的组件组合成层次结构树。若是一个组件出如今另外一个组件中,那么这个组件就是它的子组件,so easy:

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

第二步:用React作个静态版

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

var ProductRow = React.createClass({
  render: function() {
    var name = this.props.product.stocked ?
      this.props.product.name :
      <span style={{color: 'red'}}>
        {this.props.product.name}
      </span>;
    return (
      <tr>
        <td>{name}</td>
        <td>{this.props.product.price}</td>
      </tr>
    );
  }
});

var ProductTable = React.createClass({
  render: function() {
    var rows = [];
    var lastCategory = null;
    this.props.products.forEach(function(product) {
      if (product.category !== lastCategory) {
        rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
      }
      rows.push(<ProductRow product={product} key={product.name} />);
      lastCategory = product.category;
    });
    return (
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Price</th>
          </tr>
        </thead>
        <tbody>{rows}</tbody>
      </table>
    );
  }
});

var SearchBar = React.createClass({
  render: function() {
    return (
      <form>
        <input type="text" placeholder="Search..." />
        <p>
          <input type="checkbox" />
          {' '}
          Only show products in stock
        </p>
      </form>
    );
  }
});

var FilterableProductTable = React.createClass({
  render: function() {
    return (
      <div>
        <SearchBar />
        <ProductTable products={this.props.products} />
      </div>
    );
  }
});


var PRODUCTS = [
  {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'}
];
 
ReactDOM.render(
  <FilterableProductTable products={PRODUCTS} />,
  document.getElementById('container')
);
View Code

有了组件层次结构,是时候表演真正的技术了实现你的应用了。最简单的方式是把数据渲染到界面上,可是不带交互功能。最好是分离这些步骤,由于构建一个静态版本更可能是须要你敲键盘而增长交互功能就须要你敲脑壳了。

将你的应用构建出一个静态版原本展现数据模型,你也许会须要构建组件来复用其余组件,用属性(props)传入数据。 属性(props)是一种将数据从父组件传入子组件的途径。 即使你对状态(state)模式很是熟悉,在静态版本中也不要使用状态哦。状态是留给交互来处理那些会变化的数据使用的。做为一个静态版请无视之。

你能够用自上而下或自下而上的方式构建应用。既能够从最顶层组件开始(好比从FilterableProductTable开始)也能够从最底层组件开始(如ProductRow)。在简单的示例中,自上而下每每更容易;而在大型项目中,使用自下而上更好,你还能方便的写单元测试呢!

这一步完成以后,你就有了一个能够展现你的数据模型的组件库。做为一个静态版本,每一个组件都只有一个 render()方法。顶层组件(FilterableProductTable)经过属性(prop)得到你的数据模型。若是此时你改变你的基础数据模型并再次调用ReactDOM.render(),界面会刷新。界面的刷新和变化了一目了然直接了当。React的单向数据流(又名单向绑定)让全部事情有序且快速。

若是在这一步中遇到问题,你能够参考React文档

 

小插曲儿: 属性(props)与状态(state)

React中有两类数据模型:属性和状态,了解他们的区别是颇有必要的!还不太清楚?来来来看这里React官方文档咋说的。

第三步:定义最小(完整)的界面状态值

界面想要动起来?数据必须变起来!React使用状态(state)来实现。

若想正确构建你的应用,首先你得考虑你的应用至少须要一组什么样的可变状态值。来跟我念口诀:取其精华,去其糟粕。找出你的应用的那组干货——绝对最小化的界面状态值组,而且其余任何须要均可以通这组值计算得出。好比你要构建一个待办列表,只须要维护一组待办项便可;你不须要再维护这组列表的个数的值,由于在你须要展现待办数时能够直接获取列表长度获得结果。

来看看咱们例子里有哪些数据:

  • 原始的商品列表
  • 用户输入的搜索文本
  • 勾选框的值
  • 筛选后的商品列表

让咱们逐条看看哪些是状态,对每一条数据三省吾身:

  1. 是不是父组件传入的属性?若是是的,估计不是状态。
  2. 是否会随时间变化改变?若是不会变,估计不是状态。
  3. 可否从其余状态或属性计算获得?若是能够,确定不是状态。

原始商品列表经过属性传进来,所以它不是状态。搜索框的值和勾选框的值能够改变并且其余东西也计算不出来这些值,看上去应该是状态。最后,筛选后的商品列表也不是状态,由于它能够经过原始商品列表、搜索框的值和勾选框的值计算得出。

最后得出咱们须要的状态:

  • 用户输入的输入框的值
  • 勾选框的值

第四步:给状态找个家

最小状态集新鲜出炉,接下来咱们须要定义哪些组件会变化,或者说拥有这些状态。

记住了: React数据老是单向且「下流」的——流向组件层次中的底层。可能并非一开始就看得出哪一个组件拥有什么状态。这经常是萌新最难理解的部分,因此就让老司机带带你吧:

对于你应用的每一条状态:

  • 找出每个须要基于此状态来渲染界面的组件。
  • 找到它们共同的爹(一个在组件层次中须要此状态的全部控件的顶层父组件)。
  • 它们共同的父组件或更高层级的组件均可以做为状态的持有者。
  • 若是你以为哪一个组件持有这个状态都很别扭,能够为了这个状态创造一个新的组件来持有,并把这个新组件加到它们共同父组件的上层结构中的任何合适位置。

针对咱们的应用,让咱们根据以上策略捋一捋:

  • ProductTable须要根据状态值来过滤商品列表,SearchBar须要显示搜索文本和勾选框状态值。
  • FilterableProductTable是它们的共同父组件。
  • 看起来搜索文本和勾选框值放在FilterableProductTable挺合适。

就这么愉快的决定了,把这些状态放FilterableProductTable里吧。首先在FilterableProductTable中增长getInitialState()(译者:ES6中若是用class构建组件,初始化状态的方法将发生改变方法并返回{filterText: '', inStockOnly: false}来对应应用的初始状态。而后将filterText和inStockOnly做为属性传给ProductTableSearchBar。最后就用属性来过滤ProductTable中的商品列表并把搜索文本设置到SearchBar的输入框中。

来看看你应用的表现如何:把filterText设置成"ball"而后刷新。厉害了个人哥,列表正确的更新了!

第五步:增长反向数据流

至此,咱们已经构建了一个能正确渲染属性和状态从组件层次自上而下传递的应用了。是时候表演真正的技术了支持数据反向传递了:底层组件须要更新FilterableProductTable里的状态。

React明确的数据传递能让你更容易搞清楚你的程序是怎么运做的,但比起传统的双向数据绑定你就须要敲稍微多一点的代码了。 React提供了一个叫ReactLink的插件来让这种模式变得和双向绑定同样方便,但本文的目的在于让一切明晰,暂不使用。

若是你尝试在当前版本的示例中输入或勾选,你会发现React彻底无视你的输入。 怎么回事难道有Bug?乖乖咱们故意的!由于咱们刚才把input的value属性设置成老是等于FilterableProductTable传进来的状态了。

然并卵,咱们须要用户的输入马上更新状态。既然控件只容许更新本身的状态,FilterableProductTable能够传一个每次状态须要发生变化时都会触发的回调函数回传到SearchBar。咱们能够用输入框的onChange事件来触发并在FilterableProductTable传入的回调函数中调用setState()来更新状态。

看上去好像很复杂的样子,其实只是多了几行代码而已,但这真真真的让你能看清数据是如何在你应用的身体里流来流去的。

没错就是这样

但愿这篇文章能在你用React构建组件或应用时给你点亮一盏明灯。虽然可能比之前要搬更多砖,但请你记住代码写出来是要能够给人阅读的,特别是那些标准统1、逻辑清晰的代码更赏心悦目。当你开始构建大型的控件库的时候,你会感激这种规则化、清晰化的风格,再加上代码的复用,你的代码行数会获得缩减。☺

相关文章
相关标签/搜索