【React】实例:实现一个可搜索的产品数据表格

本文摘自 react哲学

后台数据

[
  {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

在这里插入图片描述

将UI划分成组件

划分原则:一个组件只做一件事情,如果一个组件做的事情太多,考虑将它拆分成多个组件。
在这里插入图片描述
好了,组件层级现在清楚了。
接下来,可能会有两个疑问:刚开始接触react时我脑子里常常有这俩疑问

  • 应该 从上往下开发,还是从下往上开发?
    如果应用比较简单,可以从上往下;如果应用比较复杂,最好从下往上。

  • 写组件时,什么时候应该用state,什么时候应该用props
    对不起,我答不上来。
    但如果两步走写组件,这个问题可能好回答一些。
    哪两步?先 实现静态页面,再 添加交互功能。

实现静态页面

实现静态页面时,不用考虑交互功能,也就是说,此时没有动态数据。
state是变化的数据,这就意味着,在实现静态页面时,如果要传递数据,只能用props

  • ProductRow
    显示产品的nameprice属性,且stockedfalse时,name属性字体染红。
class ProductRow extends React.Component{
    render(){
        let {name,price,stocked} = this.props.product;
        const style = {
            color:'red'
        }
        name = stocked?name:<span style={style}>{name}</span>;
        return (
            <tr>
                <td>{name}</td>
                <td>{price}</td>
            </tr>
        )
    }
}
  • ProductCategoryRow
class ProductCategoryRow extends React.Component{
    render(){
        const category = this.props.category;
        return (
            <tr>
                <th colSpan='2'>{category}</th>
            </tr>
        )
    }
}
  • ProductTable
class ProductTable extends React.Component{
    render(){
        const rows = [];
        let lastCategory = null;
        this.props.products.forEach(product => {
            if(product.category!==lastCategory){
                rows.push(
                    <ProductCategoryRow 
                        key={product.category}
                        product={product}/>
                )
            }
            rows.push(<ProductRow 
                        key={product.name}
                        product={product} />);
            lastCategory = product.category;
        });
        return (
            <table>
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Price</th>
                    </tr>
                </thead>
                <tbody>
                    {rows}
                </tbody>
            </table>
        )
    }
}

这里之所以可以根据 product.category是否等于lastCategory 来进行分组,得益于后台数据已经分好类排好序了。
在这里插入图片描述
如果没有分好类排好序,可以这么写:

class ProductTable extends React.Component{
    render(){
        const map = new Map();
        this.props.products.forEach(product => {
            const {category,name} = product;
            let rows = map.get(category);
            if(!rows){
                rows = [<ProductCategoryRow key={category}
                                            category={category}/>];
                map.set(category,rows)
            }
            rows.push(
                <ProductRow key={name}
                            product={product} />
            );
            
        });
        const allRows = [...map.values()].reduce((accum,item)=>[...accum,...item],[]);
        return (
            <table>
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Price</th>
                    </tr>
                </thead>
                <tbody>
                    {allRows}
                </tbody>
            </table>
        )
    }
}
  • SearchBar
class SearchBar extends React.Component{
    render(){
        const {filterText,isStockOnly} = this.props;
        return (
            <form>
                <input type='text' 
                        placeholder="Search..."/>
                <p>
                    <input type='checkbox'  /> Only show products in stock?
                </p>
            </form>
        )
    }
}
  • FilterProductTable
class FilterProductTable 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: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
    {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
    {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
    {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
    {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
  ];
ReactDOM.render(
    <FilterProductTable products={PRODUCTS}/>,
    document.getElementById('root')
);

在这里插入图片描述

添加交互功能

React的理念是UI=render(data),数据驱动视图。
交互功能就是通过改变数据实现的,而这个变动的数据就是state

state的最小表示

state最小表示,个人理解就是 没有冗余,必须有,一个都不能少的数据。

就拿本例来说,我们会关注四个数据:

  • 全部产品数据
  • 是否过滤
  • 过滤字符串
  • 过滤后的产品数据

全部产品数据是由后台给的,如果要过滤,只要知道过滤字符串,就能推导出过滤后的产品数据,所以 过滤后的产品数据不是 state

一个数据应不应该是statereact哲学给我们提供了以下判断思路:

  • 如果数据是通过props从父组件传递过来的,那么这个数据就不应该是state
  • 如果数据是静态的,不会变动的,那么这个数据也不应该是state
  • 如果数据可以根据其他propsstate推导计算出来,那么这个数据也不应该是state

再回到本例,之前我们确定了 过滤后的产品数据 不是 state
全部产品数据是后台给的,且通过props传递给了最高层组件FilterableProductTable,所以它不是state
这样一来,最后入选state就是 是否过滤isStockOnly 和 过滤字符串filterText

state应该放哪儿

提取组件与状态提升这篇有介绍过:通常组件维护着自己的state,但如果其他组件也需要这个state,就应该把这个state提升到它们的最近共同父组件里。
react哲学建议:如果实在找不到合适的位置,就新建一个组件来放state,且该组件的层级比共同父组件的层级高。这让我想到了redux的<Provider>
好吧,所以把isStockOnlyfilterText放到组件FilterableProductTable里。

完整实现

在这里插入图片描述

import React from "react";
import ReactDOM from "react-dom";
import './index.css';

class ProductRow extends React.Component{
    render(){
        let {name,price,stocked} = this.props.product;
        const style = {
            color:'red'
        }
        name = stocked?name:<span style={style}>{name}</span>;
        return (
            <tr>
                <td>{name}</td>
                <td>{price}</td>
            </tr>
        )
    }
}
class ProductCategoryRow extends React.Component{
    render(){
        const category = this.props.category;
        return (
            <tr>
                <th colSpan='2'>{category}</th>
            </tr>
        )
    }
}

class ProductTable extends React.Component{
    render(){
        const {filterText,isStockOnly} = this.props;
        const map = new Map();
        this.props.products.forEach(product => {
            const {category,name,stocked} = product;
            if(name.toLowerCase().indexOf(filterText.toLowerCase())===-1){
                return;
            }
            if(isStockOnly && !stocked){
                return;
            }
            let rows = map.get(category);
            if(!rows){
                rows = [<ProductCategoryRow key={category}
                                            category={category}/>];
                map.set(category,rows)
            }
            rows.push(
                <ProductRow key={name}
                            product={product} />
            );
            
        });
        const allRows = [...map.values()].reduce((accum,item)=>[...accum,...item],[]);
        return (
            <table>
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Price</th>
                    </tr>
                </thead>
                <tbody>
                    {allRows}
                </tbody>
            </table>
        )
    }
}

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


class FilterProductTable extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            filterText:'',
            isStockOnly:false
        }
        this.onFilterTextChange = this.onFilterTextChange.bind(this);
        this.onStockChange = this.onStockChange.bind(this);
    }
    onFilterTextChange(filterText){
        this.setState({
            filterText
        });
    }
    onStockChange(isStockOnly){
        this.setState({
            isStockOnly
        });
    }
    render(){
        const {filterText,isStockOnly} = this.state;
        return (
            <div>
                <SearchBar
                    filterText={filterText}
                    isStockOnly={isStockOnly}
                    onFilterTextChange={this.onFilterTextChange}
                    onStockChange={this.onStockChange}
                />
                <ProductTable 
                    filterText={filterText}
                    isStockOnly={isStockOnly}
                    products={this.props.products}/>
            </div>
        )
    }
}
const PRODUCTS = [
    {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
    {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
    {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
    {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
    {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
    {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
  ];
ReactDOM.render(
    <FilterProductTable products={PRODUCTS}/>,
    document.getElementById('root')
);
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值