翻译--Thinking in React

无聊翻译篇react入门文章,去年学习react时看了一遍,很不错的一篇文章。html

https://reactjs.org/docs/thin...react

部分为意译,旨在让newcomers 容易理解。web

()内均为译者注

React会是我快速构建大型webapp的首要js框架选择。
其在FacebookInstagram上的实践给予了咱们充足的自信。json

React众多闪光点中的一个就是让你开始思考如何设计、构建应用。(主要就是react是数据驱动设计,因此如何设计state成了很重要的一部分)api

本文,将以一个商品下拉列表加搜索框的例子来展现react架构


Start With A Mock
本例子大概长这样,数据源来自构建的一个mock,以json api方式进行访问。
app

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

第一步:拆分组件,分析组件结构

译者注:组件结构拆分没有一个标准,例子里是拆的很细,实际工做中通常是统一规范较重要,可读性至上。webapp

首先拆分红多个组件跟子组件,而且命名。这里已经用虚线框标识了。工做中,如何有设计或者产品辅助你的话,这个工做可能不须要你来作,交给他们便可。他们会用ps以图层的方式拆分好组件。(大厂吗?!)模块化

这里的组件是已经拆分好了,可是若是本身拆分,该如何作呢?答案是用必定的标准进行规范。好比你能够选择的一个标准就是:单一职责原则。即一个组件只负责一件事情。(这个事情范围就广了,好比一个动做,一个请求。原则就是方便管理与维护)若是还能细分,就再拆成更小层次的组件。(react就是一层层组件嵌套,各类组件与子组件)

咱们通常常常用json data model 返回给用户,在react中,只要data model格式正确,界面ui(组件)就渲染得很舒服了,通常相差不到哪儿去。这是由于uidata model 倾向于遵循相同的架构。跟这些带来的好处相比,拆分组件的基础工做就显得微不足道了。把界面打散拆分红多个组件,每一个组件表明data model的某一部分。(这一大段啥意思呢?就是夸了一下react数据驱动的好处)

看上图,咱们把ui拆成了5个组件,下面是各个组件对应的职责,斜体突出表示下。
1.FilterableProductTable(orange):contains the entirety of the example
(包裹全部子组件的外壳)
2.SearchBar(blue):处理用户交互
3.ProductTable(green):用户交互后过滤出的商品列表数据展现
4.ProductCategoryRow(turquiso 亮青色):显示商品列表分类名称
5.ProductRow(red):展现每一个商品信息

这边注意观察下组件ProductTable下有两个label做为header--“Name”“Price”。这两个label没有做为单独的组件如ProductCategoryRowProductRow存在,这里仅仅是做为ProductTable的一部分存在。固然你能够将其做为单独一个子组件开辟出来,只是在这个例子里不必,若是这个header再复杂一点,好比加上点击排序功能,那么能够再建一个子组件--ProductTableHeader

如今ui已经拆分完成,来分一下组件层次。
(其实就是个树状图,不少状况下,你的json data model 长啥样,组件层次基本上就差不离了)

·FilterableProductTable

·SearchBar
 ·ProductTable
         ·ProductCategoryRow
         ·ProductRow

(小问题,上面提到的ProductTableHeader若是存在,应该放在树状图的哪一个位置呢?)

第二步:构建静态页面

class ProductCategoryRow extends React.Component {
  render() {
    const category = this.props.category;
    return (
      <tr>
        <th colSpan="2">
          {category}
        </th>
      </tr>
    );
  }
}

class ProductRow extends React.Component {
  render() {
    const product = this.props.product;
    const name = product.stocked ?
      product.name :
      <span style={{color: 'red'}}>
        {product.name}
      </span>;

    return (
      <tr>
        <td>{name}</td>
        <td>{product.price}</td>
      </tr>
    );
  }
}

class ProductTable extends React.Component {
  render() {
    const rows = [];
    let lastCategory = null;
    
    this.props.products.forEach((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>
    );
  }
}

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

class FilterableProductTable extends React.Component {
  render() {
    return (
      <div>
        <SearchBar />
        <ProductTable products={this.props.products} />
      </div>
    );
  }
}


const 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')
);

如今组件拆分好了,json data model有了,开始实现界面代码吧。先作一个最简单的版本,只有界面,没有交互。交互留到后面作,这样分开作的好处是先作静态界面只用堆代码而不须要考虑逻辑交互,交互逻辑到后面作。(事情一件件作,也正符合组件拆分的标准之一,single responsibility principle,单一职责)

实现静态版本从构建组件开始,实现构建复用的基础之一就是经过使用props。何谓props?props就是将数据从树状图由上到下传递的快递员。(或者说从parent到child,这个parent或child是相对的,针对不一样的两个组件,随时变化的,因此用树状图来理解舒服点)若是你有必定的react基础,熟悉state的话,(state也能传递数据)在这里先不要考虑用state,只有在交互的时候,随时间变化的数据须要用到state。

你能够从上到下或者由下至上构建组件,意思就是你能够先构建最上面的FilterableProductTable或者最下面的ProductRow。在简单的项目中,通常从上到下构建组件更简单。大点稍微复杂点的项目中能够由下至上构建组件,这样也方便编写测试实例。(简单例子怎样都行,复杂的项目,都是一个个组件嵌套的,缺什么补什么,通常不存在思考这个,除非整个项目是由你来从零架构的)

如今咱们已经构建了一组可用于渲染data mod的复用组件构成的组件库。每一个组件内只有一个方法:render(),由于如今还只是静态页面。树状图最上端的组件FilterableProductTable会把data model 打包成一个props。若是你对data model进行更改并再次调用ReactDom.render(),ui界面就会更新。代码很简单,很容易观察ui如何更新以及在哪里进行更改。React另外一个特性:单向数据流(也叫单项绑定 one way binding)使代码模块化且运行快速。

小注:Props vs State

react中有两类存储data model的对象,props跟state。了解二者的区别仍是很重要的。
详细:【?】https://reactjs.org/docs/inte...

第三步:定义完整而简洁的state

交互就是ui界面能对data model 的变化做出相应。React经过state使其实现变得简单。

继续构建app前,须要肯定好完整且简洁的state。遵循的规则就是:DRY(don't repeat yourself)。举个例子,在作TODO List例子时,咱们通常会用到List Count这个属性,但不必将Count做为state的一个字段存在,由于经过计算List的length也能获取到Count。(只保留必要的属性字段在state中)

针对咱们的应用,现有的数据以下:
a·原始商品列表数据
b·用户输入搜索文本
c·复选框选中与否
d·过滤后的商品列表数据

那么如何肯定以上哪些是应该做为state的部分而存在的呢?能够简单的问问本身下面这三个问题:

1.该数据是经过props传递的吗?若是是,那就应该不属于state
2·该数据不是实时变化(即不随交互而变化)的,那就应该不属于state
3·能够经过其余state跟props计算而出(如上面说的List Count),那就应该不属于state

而后带着这三个问题,咱们来看看上面四个数据。静态页面版本中已经看出,a是经过props传递的,因此a不属于state,b跟c是根据用户输入来肯定的,随交互而变化,因此bc应该属于state的一部分,d能够经过abc结合计算而得,因此虽然会变化但也不属于state。

总结,state:
·用户输入文本
·复选框选中与否

state = {
      filterText:'',
      isStockOnly:false
}

第四步:构建state

class ProductCategoryRow extends React.Component {
  render() {
    const category = this.props.category;
    return (
      <tr>
        <th colSpan="2">
          {category}
        </th>
      </tr>
    );
  }
}

class ProductRow extends React.Component {
  render() {
    const product = this.props.product;
    const name = product.stocked ?
      product.name :
      <span style={{color: 'red'}}>
        {product.name}
      </span>;

    return (
      <tr>
        <td>{name}</td>
        <td>{product.price}</td>
      </tr>
    );
  }
}

class ProductTable extends React.Component {
  render() {
    const filterText = this.props.filterText;
    const inStockOnly = this.props.inStockOnly;

    const rows = [];
    let lastCategory = null;

    this.props.products.forEach((product) => {
      if (product.name.indexOf(filterText) === -1) {
        return;
      }
      if (inStockOnly && !product.stocked) {
        return;
      }
      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>
    );
  }
}

class SearchBar extends React.Component {
  render() {
    const filterText = this.props.filterText;
    const inStockOnly = this.props.inStockOnly;

    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={filterText} />
        <p>
          <input
            type="checkbox"
            checked={inStockOnly} />
          {' '}
          Only show products in stock
        </p>
      </form>
    );
  }
}

class FilterableProductTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      filterText: '',
      inStockOnly: false
    };
  }

  render() {
    return (
      <div>
        <SearchBar
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
        <ProductTable
          products={this.props.products}
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
      </div>
    );
  }
}


const 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')
);

如今state已经肯定好了,开始要与组件交互关联了,知道state的那部分应该放在哪一个组件中。

再提醒一句:React是单向数据流导向的,从树状图的上端往下。

哪一个组件应该有怎样的state对于react新手来说多是很困惑的一点,下面几点可能对你会有帮助:

·肯定哪些组件在渲染过程当中要用到state
·可能多个组件同时须要用到state的一部分,那边找到一个它们共同的parent component,把state放在这个组件里
·若是已有的组件中找不到这样的parent component,那就建立一个。
(意译)

依照以上标准分析下咱们的应用:
·ProductTable 显示过滤后的商品数据,这须要经过state跟原始数据(在props中),SearchBar须要显示过滤文本跟复选框勾选状况。
·上面二者的common parent component就能够是FilterableProductTable。
·因此讲state中的filterText跟checkvalue放在FilterableProductTable,没毛病。

咱们state中也就这两,因此放在哪一个组件也肯定了,开始码代码了。
首先,在FilterableProductTable中的构造函数里初始化state对象,而后将state里的内容做为props传递到对应的child component中去(交互=》触发预先定义的事件=》state变化=》做为state内容载体的props传递到对应组件=》具体组件render=》用户看到交互结果)

第五步:实现交互事件(add inverse data flow ,难以描述)

class ProductCategoryRow extends React.Component {
  render() {
    const category = this.props.category;
    return (
      <tr>
        <th colSpan="2">
          {category}
        </th>
      </tr>
    );
  }
}

class ProductRow extends React.Component {
  render() {
    const product = this.props.product;
    const name = product.stocked ?
      product.name :
      <span style={{color: 'red'}}>
        {product.name}
      </span>;

    return (
      <tr>
        <td>{name}</td>
        <td>{product.price}</td>
      </tr>
    );
  }
}

class ProductTable extends React.Component {
  render() {
    const filterText = this.props.filterText;
    const inStockOnly = this.props.inStockOnly;

    const rows = [];
    let lastCategory = null;

    this.props.products.forEach((product) => {
      if (product.name.indexOf(filterText) === -1) {
        return;
      }
      if (inStockOnly && !product.stocked) {
        return;
      }
      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>
    );
  }
}

class SearchBar extends React.Component {
  constructor(props) {
    super(props);
    this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
    this.handleInStockChange = this.handleInStockChange.bind(this);
  }
  
  handleFilterTextChange(e) {
    this.props.onFilterTextChange(e.target.value);
  }
  
  handleInStockChange(e) {
    this.props.onInStockChange(e.target.checked);
  }
  
  render() {
    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={this.props.filterText}
          onChange={this.handleFilterTextChange}
        />
        <p>
          <input
            type="checkbox"
            checked={this.props.inStockOnly}
            onChange={this.handleInStockChange}
          />
          {' '}
          Only show products in stock
        </p>
      </form>
    );
  }
}

class FilterableProductTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      filterText: '',
      inStockOnly: false
    };
    
    this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
    this.handleInStockChange = this.handleInStockChange.bind(this);
  }

  handleFilterTextChange(filterText) {
    this.setState({
      filterText: filterText
    });
  }
  
  handleInStockChange(inStockOnly) {
    this.setState({
      inStockOnly: inStockOnly
    })
  }

  render() {
    return (
      <div>
        <SearchBar
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
          onFilterTextChange={this.handleFilterTextChange}
          onInStockChange={this.handleInStockChange}
        />
        <ProductTable
          products={this.props.products}
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
      </div>
    );
  }
}


const 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')
);

目前为止,经过state,props已经构建好了咱们的页面,如今实现交互事件。

React使用的单向数据流跟单向数据绑定使react的工做易于理解,虽然这相比双向绑定的确须要写多点代码。(使黑魔法再也不神秘,react不须要黑魔法)

下面这段过程总结,我以为仍是我上面注解的那段拿过来:

交互=》触发预先定义的事件=》state变化=》做为state内容载体的props传递到对应组件=》具体组件render=》用户看到交互结果

(数据绑定体如今,state一旦发生变化,跟state关联的数据都将重现计算,而经过数据驱动的页面也将从新渲染。)

that's all

欢迎讨论~
感谢阅读~

我的公众号:

相关文章
相关标签/搜索