React 开发流程——利用 React 构建简单的可检索产品数据表

1,715 阅读7分钟
原文链接: blog.csdn.net

今天就是春节了,祝各位鸡年大吉,心想事成
感觉这两天错过了好几个亿
净往外赔钱了~马云爸爸才给了我2.08. 心塞
不过咱也算是参加过一个两亿的项目了
昨晚发现博客还增加了30+访问流量
没想到除夕夜还有这么多努力的人. 佩服 d===( ̄▽ ̄*)b
好了废话不多说


使用React构建可搜索产品数据表
是React官网上的一个demo,它不是很难
不过却能够很好的映射React的开发流程
而且把我们常用的语法基本都涉及了
可以让我们对于React有更进一步的理解
React官方传送门:Thinking in React

UI与JSON

首先我们从我们的UI设计师那里拿到了UI模拟图和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"}
];

category是产品类别
price是产品价格
stocked是有无库存
name是产品名称

拆分UI视图

首先我们要做的第一项工作就是拆分UI
把它们拆解成一个个组件,组件中还有子组件
并且赋给它们名字
这个UI可以拆分成五个组件

  1. FilterableProductTable (黄色)
    完整的产品表
  2. SearchBar (深蓝色)
    用户输入部分
  3. ProductTable (绿色)
    产品完整展示部分
  4. ProductCategoryRow (天蓝色)
    产品类别标头
  5. ProductRow (红色)
    产品信息

当然你也可以在细拆把ProductTable中的Name和Price拆出来
不过我觉得没必要搞得这么复杂
组件层次关系如下:

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

构建静态版本

静态的版本也就是页面最初应该显示的版本
构建它不需要复杂的想法,只是代码多了点
官网上的文档是这样说的

In simpler examples, it’s usually easier to Go top-down,
and on larger projects, it’s easier to go bottom-up and write tests as you build

意思就是像我们这样简单的例子中
通常由顶级层次组件向下来写更简单一些
而在大型的项目中
最佳实践是从下级层次往上写并且要做必要的测试
那在这里的demo我们就从FilterableProductTable组件开始写

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'}
];
var FilterableProductTable = React.createClass({
    render: function(){
        return (
            <div>
                <SearchBar/>
                <ProductTable products={this.props.products}/>
            </div>
        )
    }
});
var SearchBar = React.createClass({
    render: function(){
        return (
            <div>
                <input type="text"/><br/>
                <input type="checkbox"/>Only show products in stock<br/><br/>
            </div>
        )
    }
});
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>
                        <td><strong>Name</strong></td>
                        <td><strong>Price</strong></td>
                    </tr>
                </thead>
                <tbody>
                    {rows}
                </tbody>
            </table>
        )
    }
});
var ProductCategoryRow = React.createClass({
    render: function(){
        return (
            <tr>
                <td><strong>{this.props.category}</strong></td>  
            </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>
        )
    }
});
ReactDom.render(
    <FilterableProductTable products={PRODUCTS}/>,
    document.getElementById('root')
)

很好的体现了React单向数据流的特点
将JSON数据交给父级
然后父级再讲数据传递下去
这里比较关键的代码就是ProductTable中的这部分了

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

利用了数组的形式将静态列表的组件项存储下来
使用变量lastCategory,当当前遍历的product中category与lastCategory不同时
就向数组中添加ProductCategoryRow组件

售罄产品过滤

我们先来考虑点击那个复选框来重置是否显示售罄产品过滤
很容易的就可以想到必须要设置一个状态
这个状态就是是否只显示有库存的产品(或者不显示售罄产品)
然后为复选框绑定change事件改变状态

var FilterableProductTable = React.createClass({
    getInitialState: function(){
        return {
            inStockOnly: false
        }
    },//设置初始状态:产品默认展示全部
    checkHandler: function(){
        this.setState({
            inStockOnly: !this.state.inStockOnly
        });
    },//复选框要绑定的事件处理函数:改变inStockOnly状态
    render: function(){
        return (
            <div>
                <SearchBar checkHandler={this.checkHandler}/> //将事件处理函数作为数据传递给子级
                <ProductTable products={this.props.products} inStockOnly={this.state.inStockOnly}/>
                //将状态state作为属性props传递给子级
            </div>
        )
    }
});
var SearchBar = React.createClass({
    render: function(){
        return (
            <div>
                <input type="text"/><br/>
                <input type="checkbox" onChange={this.props.checkHandler}/>Only show products in stock<br/><br/>
                //为复选框绑定change事件处理函数
            </div>
        )
    }
});
var ProductTable = React.createClass({
    render: function(){
        var rows = [];
        var lastCategory = null;
        var products = this.props.inStockOnly ?
                       this.props.products.filter(function(product){
                           return product.stocked;
                       }) : this.props.products;
        //通过判断状态inStockOnly来决定是否过滤products数组
        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>
                        <td><strong>Name</strong></td>
                        <td><strong>Price</strong></td>
                    </tr>
                </thead>
                <tbody>
                    {rows}
                </tbody>
            </table>
        )
    }
});

关键字过滤产品

进度已经过去一大半了
剩下一个问题就是我们在输入框中输入字符时
同样需要对产品进行“过滤”
现在我们需要另外的状态——输入框字符改变
还需要一个事件处理函数来改变这个状态

var FilterableProductTable = React.createClass({
    getInitialState: function(){
        return {
            inStockOnly: false,
            filterText: '' //表示输入框中字符
        }
    },
    checkHandler: function(){
        this.setState({
            inStockOnly: !this.state.inStockOnly
        });
    },
    textHandler: function(text){
        this.setState({
            filterText: text
        });
    }, //输入框字符改变状态随之改变,但是还不能获取输入框中字符,所以设置一个参数test
    render: function(){
        return (
            <div>
                <SearchBar checkHandler={this.checkHandler} textHandler={this.textHandler}/>
                //将事件处理函数作为数据传递给SearchBar组件
                <ProductTable products={this.props.products} inStockOnly={this.state.inStockOnly} filterText={this.state.filterText}/>
                //将filterText状态传递给ProductTable组件
            </div>
        )
    }
});
var SearchBar = React.createClass({ 
    Handler: function(){
        this.props.textHandler(this.refs.input.value);
    }, //为了将获取的输入框内字符传入textHandler作为参数,外部包装一个函数
    render: function(){
        return (
            <div>
                <input type="text" ref="input" onChange={this.Handler}/><br/>
                // 绑定change事件处理函数
                <input type="checkbox" onChange={this.props.checkHandler}/>Only show products in stock<br/><br/>
            </div>
        )
    }
});
var ProductTable = React.createClass({
    render: function(){
        var rows = [];
        var lastCategory = null;
        var products = this.props.products;
        var inStockOnly = this.props.inStockOnly;
        var filterText = this.props.filterText;
        products = inStockOnly ?
                   products.filter(function(product){
                       return product.stocked;
                   }) : products;
        products = filterText ?
                   products.filter(function(product){
                       return product.name.indexOf(filterText) !== -1;
                   }) : products;//通过字符限制一步过滤
        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>
                        <td><strong>Name</strong></td>
                        <td><strong>Price</strong></td>
                    </tr>
                </thead>
                <tbody>
                    {rows}
                </tbody>
            </table>
        )
    }
});

完整版本

完整的脚本代码如下

var React = require('react');
var ReactDom = require('react-dom');
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'}
];
var FilterableProductTable = React.createClass({
    getInitialState: function(){
        return {
            inStockOnly: false,
            filterText: ''
        }
    },
    checkHandler: function(){
        this.setState({
            inStockOnly: !this.state.inStockOnly
        });
    },
    textHandler: function(text){
        this.setState({
            filterText: text
        });
    },
    render: function(){
        return (
            <div>
                <SearchBar checkHandler={this.checkHandler} textHandler={this.textHandler}/>
                <ProductTable products={this.props.products} inStockOnly={this.state.inStockOnly} filterText={this.state.filterText}/>
            </div>
        )
    }
});
var SearchBar = React.createClass({ 
    Handler: function(){
        this.props.textHandler(this.refs.input.value);
    },
    render: function(){
        return (
            <div>
                <input type="text" ref="input" onChange={this.Handler}/><br/>
                <input type="checkbox" onChange={this.props.checkHandler}/>Only show products in stock<br/><br/>
            </div>
        )
    }
});
var ProductTable = React.createClass({
    render: function(){
        var rows = [];
        var lastCategory = null;
        var products = this.props.products;
        var inStockOnly = this.props.inStockOnly;
        var filterText = this.props.filterText;
        products = inStockOnly ?
                   products.filter(function(product){
                       return product.stocked;
                   }) : products;
        products = filterText ?
                   products.filter(function(product){
                       return product.name.indexOf(filterText) !== -1;
                   }) : products;
        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>
                        <td><strong>Name</strong></td>
                        <td><strong>Price</strong></td>
                    </tr>
                </thead>
                <tbody>
                    {rows}
                </tbody>
            </table>
        )
    }
});
var ProductCategoryRow = React.createClass({
    render: function(){
        return (
            <tr>
                <td><strong>{this.props.category}</strong></td>  
            </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>
        )
    }
});
ReactDom.render(
    <FilterableProductTable products={PRODUCTS}/>,
    document.getElementById('root')
)

UI视图切分为组件模块
将JSON数据放入父级组件
再将数据props流入子级组件
设置状态位,然后通过事件处理函数改变状态state
state改变,触发DOM的不断渲染
这个例子诠释了React的组件化、单向数据流的特点

==主页传送门==