美文网首页
用React的方式思考

用React的方式思考

作者: 孙和David | 来源:发表于2016-01-01 16:15 被阅读581次

    作者:Pete Hunt

    翻译:孙和

    原文链接

    构建大型、反应迅捷的web app,我首选react。我们在facebook和instagram中都用了它,扩展的不错。

    当你构建APP时,React重塑了你的思考方式。我会在这篇文章中展现,用react构建应用的思考过程。

    首先,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"}
    ];
    

    第一步:把UI分解成组件树
    你要做的第一件事就是,在设计图上,把组件和子组件用线框出来,还要给他们命名。

    thinking-in-react-components.png

    如何划分组件呢?方法同划分函数或者对象一样。你要遵循单一功能原则,就是说,一个组件只做一件事。如果组件不断膨胀,就要把它再拆解成更小的子组件。

    如果你的数据模型构建的好,那么你的UI和组件结构就会相应的更清晰明了。这是因为UI和数据模型遵循同样的信息架构,这意味着如果数据模型足够清晰,那么把UI分解成组件就是小事一桩。只需要把它拆分成组件,每个组件代表数据模型的一部分。

    在这个APP中,我们有5个组件:
    1、FilterableProductTable:包含整个APP
    2、SearchBar:接收用户输入
    3、ProductTable:根据用户输入,显示和筛选数据集合
    4、ProductCategoryRow:显示每一个分类的标题
    5、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:'$49.99',stocked:true,name:'Baseball'},
    {category:'Sporting Goods',price:'$49.99',stocked:false,name:'Basketball'},
    {category:'Electronics',price:'$49.99',stocked:true,name:'iPod Touch'},
    {category:'Electronics',price:'$49.99',stocked:false,name:'iPhone 5'},
    {category:'Electronics',price:'$49.99',stocked:true,name:'Nexus 7'}
    ];
    ReactDOM.render(
       <FilterableProductTable products={PRODUCTS} />,
        document.getElementById('container')
    );
    

    构建一个可以渲染数据模型的静态APP,你要构建很多组件。这些组件重用了其他组件,并且用props传递数据。可以利用props,从父组件向子组件传递数据。在构建静态版时,一定不能用state。state只用于交互,就是说,表示不断变化的数据。

    你可以自底向上或者自顶向下构建APP,如果自底向上,你先构建ProductRow;如果自顶向下,你先构建FilterableProductTable。简单情形下,一般自顶向下。大型项目中,一般自底向上,并在构建过程中,编写测试用例。

    完成这一步,你会有一些可重用的组件,这些组件渲染你的数据模型。这些组件只有render方法,因为你的APP是静态的。组件树顶端的FilterableProductTable将你的数据模型作为prop。如果你改变数据模型,重新调用ReacDOM.render()方法,UI会更新。React的单向数据流让一切模块化、迅捷。

    第三步:找出UI state的最小完备集
    为了给UI添加交互,你需要触发数据模型的变化。React用state轻松实现了这一点。
    首先,你要想好可变状态的最小集合。关键是DRY,不要重复自己。找出状态的最小集合,用这个集合计算其他需要的状态。例如,你构建一个TODO List,只要保留TODO items 数组,不要为计数保留一个额外的状态变量。当你需要计数时,只需计算TODO items 数组的长度。

    考虑我们所有的数据,我们有:原始产品列表,用户键入的搜索文字,checkbox 的值,筛选过的产品列表。

    为了确定哪一个是state,只要问三个问题:
    1、它是否通过props从父组件传入?如果是,可能不是state
    2、它不断变化吗?如果不是,那么可能不是state
    3、它可以用其他state或者props计算出来吗?如果是,那么不是state

    原始产品列表,作为props传入,不是state;
    搜索文本和checkbox是state,因为他们不断变化,并且不能通过其他东西计算出来;
    筛选后的产品列表不是state,因为它可以通过原始产品列表和搜索文字或checkbox的取值计算出来。

    最终,state是:
    用户输入的搜索文本
    checkbox的值

    第四步:确定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的最小集。下面,我们要确定哪个组件改变或拥有这些state。
    记住:react的核心是沿着组件树流动的单向数据。弄清楚哪一个组件拥有哪一个state,是最有挑战的部分。
    对于每一个state:
    确定每一个根据这个state渲染的组件;
    找到总组件(组件树中,所有需要这个state的组件,上面的那个组件);
    要么是总组件,要么是组件树中更高层的组件,拥有这个state;
    如果你找不到可以合理拥有这个state的组件,创建一个新的组件,这个组件只是为了持有这个state,并把这个组件加到组件树中,在总组件之上

    根据这一策略:
    ProductTable需要根据state筛选产品列表,SearchBar需要展示搜索文本和选中状态;
    总组件是FilterableProductTable;
    筛选文本和选择值放在FilterableProductTable组件中很合适

    棒!我们已经决定把state放在FilterableProductTable中。首先,在FilterableProductTable中添加getInitialState()方法,返回反映初始状态的对象{filterText:'',inStockOnly:false}。然后,把filterText和inStockOnly作为prop传到ProductTable和SearchBar中。最后,用这些props在ProductTable中筛选rows,在SearchBar中设置表单域的值。

    第五步:添加逆向数据流

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

    目前,我们已经构建了一个可以正确渲染的APP,在渲染过程中,props和state函数沿着组件树自上而下流动。现在,我们要支持数据反向流动:组件树底层的form组件更新组件树顶层的FilterableProductTable组件state。

    React 让这一数据流显式表现出来,这样,在理解程序如何运转时会更容易。但是,相对于传统的双向数据绑定,你需要书写更多。React提供了一个叫ReactLink的插件,让这种模式像双向数据绑定一样方便,但在本文中,我采用显式的方式。

    在当前版本中,如果你键入或者勾选,不会有任何反应。因为我们已经将input中的value prop和从FilterableProductTable中传入的state设置为相等。

    我们希望当用户改变表单时,我们能更新state来反应用户的输入。由于组件只能更新自己的state,FilterableProductTable将会传递一个回调函数给SearchBar,每当state更新时,这个回调函数就会触发。我们可以用inputs上的onChange事件,监听这一变化。每当onChange事件被触发,由FilterableProductTable传入的回调函数会调用setState()方法,app就会更新。

    相关文章

      网友评论

          本文标题:用React的方式思考

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