学习React思想

作者: 黄怡菲 | 来源:发表于2016-12-09 14:33 被阅读139次

    React和React-native在编程的思想上是完全一样的,所以要写出好的RN代码,学学React的思想很有必要。本文是学习facebook官方文章《Thinking in React》的记录,包括关键点的翻译和自己的理解。

    英文原文地址
    这片文章大部分是翻译和简化,小部分是自己理解。

    先看看需求

    假设我们需要实现这样一个可过滤的商品列表

    draft.png

    而且我们有这样的JSON API

    [
      {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"}
    ];
    

    将设计稿分解成Component层次

    使用矩形线框来确定Component和子Component。这里要遵循单一职责原则,一个Component只做一件事情。

    分解后的设计稿
    1. FilterableProductTable-橙色:根节点
    2. SearchBar-蓝色:处理用户输入
    3. ProductTable-绿色:根据用户输入展现商品列表
    4. ProductCategoryRow-蓝绿色:显示分类名称
    5. ProductRow-红色:显示单个商品信息

    如下为树状结构

    FilterableProductTable

    |- SearchBar

    |- ProductTable

    |- ProductCategoryRow

    |- ProductRow

    完成静态页面

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

    需要注意完成静态页面的时候不要使用state,因为state都是用于保存用户交互结果的。而且每个组件都只有render()方法,根组件FilterableProductTable将通过props获得数据模型,这种单向数据流使得React能很好的模块化,响应也很快。
    stateprops是React中的两种数据模型,理解两者的区别很重要。简单的理解props是外部传递进来的数据,state是用户和组件交互时产生的数据。更加详细的说明可以参考Facebook的另一篇官方文章

    小而全的state

    这个例子中的数据包括

    1. 商品列表
    2. 用户输入的搜索关键字
    3. 复选框的状态
    4. 过滤后的商品列表

    判断数据是否为state的标准

    1. 外部传入的不是state
    2. 不会变化的不是state
    3. 可以通过其他stateprops计算得到的不是state

    根据以上原则,是state的为

    1. 用户输入的搜索关键字
    2. 复选框的状态

    state的作用域

    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.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
            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;
        }.bind(this));
        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..." value={this.props.filterText} />
            <p>
              <input type="checkbox" checked={this.props.inStockOnly} />
              {' '}
              Only show products in stock
            </p>
          </form>
        );
      }
    });
    
    var FilterableProductTable = React.createClass({
      getInitialState: function() {
        return {
          filterText: '',
          inStockOnly: false
        };
      },
    
      render: function() {
        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>
        );
      }
    });
    
    
    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')
    );
    

    确定state的作用域就是要确定哪个Component应该持有这个state,这里有几个步骤。

    1. 找到所有render()中会用到这个stateComponent
    2. 找到一个Component包括包含了所有1中的Component
    3. 2中的Component或者它的上级Component持有这个state
    4. 如果你找不到一个有意义的Component持有这个state,就在这些节点之上创造一个新的Component来持有这个state

    在我们的例子中

    1. ProductTable需要根据状态来确定显示的列表项目,SearchBar也需要状态来显示搜索文本和复选框状态
    2. 所以包含两者的公共ComponentFilterableProductTable
    3. FilterableProductTable持有state也是有明确意义的

    所以我们在FilterableProductTable中持有state,然后将state作为props传递给ProductTableSearchBar

    反向数据流

    ar 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.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
            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;
        }.bind(this));
        return (
          <table>
            <thead>
              <tr>
                <th>Name</th>
                <th>Price</th>
              </tr>
            </thead>
            <tbody>{rows}</tbody>
          </table>
        );
      }
    });
    
    var SearchBar = React.createClass({
      handleChange: function() {
        this.props.onUserInput(
          this.refs.filterTextInput.value,
          this.refs.inStockOnlyInput.checked
        );
      },
      render: function() {
        return (
          <form>
            <input
              type="text"
              placeholder="Search..."
              value={this.props.filterText}
              ref="filterTextInput"
              onChange={this.handleChange}
            />
            <p>
              <input
                type="checkbox"
                checked={this.props.inStockOnly}
                ref="inStockOnlyInput"
                onChange={this.handleChange}
              />
              {' '}
              Only show products in stock
            </p>
          </form>
        );
      }
    });
    
    var FilterableProductTable = React.createClass({
      getInitialState: function() {
        return {
          filterText: '',
          inStockOnly: false
        };
      },
    
      handleUserInput: function(filterText, inStockOnly) {
        this.setState({
          filterText: filterText,
          inStockOnly: inStockOnly
        });
      },
    
      render: function() {
        return (
          <div>
            <SearchBar
              filterText={this.state.filterText}
              inStockOnly={this.state.inStockOnly}
              onUserInput={this.handleUserInput}
            />
            <ProductTable
              products={this.props.products}
              filterText={this.state.filterText}
              inStockOnly={this.state.inStockOnly}
            />
          </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')
    );
    

    这一部分在原文中稍微有些复杂。简而言之:stateFilterableProductTable中,但是真正的交互发生在SearchBar的字元素中,所以需要从子向父传递信息。这里主要是通过回调机制实现的。

    1. FilterableProductTable通过props.onUserInput传一个回调函数handleUserInputSearchBar
    2. 在文本或者复选状态发生变化时,通过onChange指定调用SearchBarhandleChange
    3. handleChange通过refs获取文本和复选框状态,并执行回调函数。refs的机制文中并没有详述。
    4. handleUserInput中改变state,并更新页面

    相关文章

      网友评论

        本文标题:学习React思想

        本文链接:https://www.haomeiwen.com/subject/ixbemttx.html