zoukankan      html  css  js  c++  java
  • Thinking in React

    本文翻译自React的官方博客,详情请阅读原文。

    React非常适合构建组件化的应用,它注重高性能,因此组建的重用,项目的扩展都十分灵活,Facebook和instagram的不少商业项目使用了此框架。

    本文主要通过“输入查询数据”这个简单的demo来说明或者学习如何用React来架构。

    数据模型

           我们需要根据JSON API来显示并且操作数据,最终的可视化操作是基于JSON数据的基础之上。最终的效果图如下:

    thinking-in-react-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"}
    ];

    step1 变UI为组件继承

               我们如何确定哪一部分应该为一个组件呢?我们可以遵循“单一职责”原则,也就是说,理想的组件只做一件事情。组件也应该根据数据(model)的结构灵活进行设计,这样最终的UI匹配提供的数据(model),利于维护和理解。

    thinking-in-react-components

    如上图所示,我们将这个应用分为5个组件。

    1. FilterableProductTable (orange): 包含所有的子组件,是个容器
    2. SearchBar (blue): 用于用户输入交互
    3. ProductTable (green): 呈现数据项并根据用户输入过滤数据
    4. ProductCategoryRow (turquoise): 显示条目信息
    5. ProductRow (red): 显示产品的具体信息

    我们可以看到,tHead部分(Name和Price)并不是一个单独的组件,在这个例子中,之所以tHead属于ProductTable组件是因为它并没有与数据(model)有关联,考虑这种情况,如果要单击tHead部分的表头实现表格内容的排列,我们最好为tHead单独设计一个组件,并在该组件上绑定事件处理函数。

    至此,我们将这五个组件的继承关系确定下来:

    • FilterableProductTable
      • SearchBar
      • ProductTable
        • ProductCategoryRow
        • ProductRow

    step2 创建静态版本

           有了组件的继承关系,我们首先创建一个静态版本的应用。我们可以通过组件复用以及父子组件之间的props通信来完成模型数据的渲染。props是父子组件通信的一种方式,如果你也了解state特性的话,那么一定不要使用state来构建静态版本,state用于创建交互版本,也就是说,state中的数据会随着时间而改变,下面的一节会讲解何时将数据放入state中。

          我们可以自顶向下或者自下而上来构建应用,在做测试时我们可以自下而上来进行每个模块的测试,而一般构建应用我们则是采用自顶向下的模式,结合数据的自上而下传递,利于开发。

          在这一步,由于我们构建的是静态版本,因此每个组件只实现了其render方法,用以基本的数据渲染。最顶层的组件(FilterableProductTable)的props中存入要渲染的数据模型,每当模型数据发生改变时,会对应的视图层的改变,这也正是React所提出的的单向数据流模型(one-way data flow)。

          在React中,组件有两种类型数据--props和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.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'}
    ];
     
    React.render(<FilterableProductTable products={PRODUCTS} />, document.body);

    step3 确定组件的state

           为了使应用具有动态交互性,必须将状态的改变(用户的输入或者单击操作等)反映到我们的UI上,通过React给组件提供的state完成上述需求。

           我们首先要确定应用的可变的状态集合,遵循DRY原则:don't repeat youself。我们需要考虑应用中的所有的数据,它包括:

    • 基本的产品列表
    • 用户输入的过滤条件
    • checkbox的值
    • 过滤后的产品列表

    根据下面条件选择哪些数据可以作为state:

    1. 是否通过父组件通过props传递,如果是,则不是state
    2. 是否随着时间而改变,如果不变,则不是state
    3. 可以通过其他state或者props计算得到,如果可以,则不是state

    产品数据列表是通过父组件的props传递,因此不是state,用户输入和checkbox满足上述三个条件,可以作为state,二对于过滤的列表,则可以根据产品数据和用户输入来获取到,因此不是state。

    故,input输入值和checkbox的值可以作为state。

    step4 确定state所属的组件

    目前确定了state集合,接下来需要确定究竟是哪个组件拥有这个state,或者随着state而变化。

    我们要明确React的单项数据流是沿着组件继承链流动的,这有时很难确定哪一个组件拥有这个state,不过我们可以根据以下原则来大体确定state所属的组件。

           在每一个状态期,

    • 确保每个组件都会根据当前状态来渲染
    • 寻找其共同的祖先组件
    • 在继承链中层级较高的组件拥有state

    回到我们的应用中,

    • ProductTable需要根据state来过滤数据,SearchBar需要显示输入的文字和选项.
    • 这两个组件的共同祖先是 FilterableProductTable.
    • 因此state的集合应该所属FilterableProductTable组件

    所以,我们确定了state所属的组件是FilterableProductTable。我们需要给该组件设置getInitialState方法设置组件的初始状态,并且通过props将状态传递给ProductTable和SearchBar,最后我们就可以在ProductTable和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({
        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'}
    ];
    
    React.render(<FilterableProductTable products={PRODUCTS} />, document.body);

    step5 添加反向数据流

           等等,目前构建的应用并不能通过表单来反向设置state,因此,我们无法再input标签输入任何值。这就需要我们手动进行反向数据设置。React默认的单项数据流是从model渲染到UI,而通过UI来设置model则需要手动编写,主要的操作就是通过获取组件对应的DOM对象,获取当前DOM的属性值并反向设置state来完成。

          当前版本的应用,React会忽略输入值和选定值,这是理所当然的,因为我们在FilterableProductTable中设置的state初始值为filterText=‘’,inStockOnly=false,所以对于ProductTable和SearchBar而言,也就是针对这两个值渲染,但是由于通过input和checkbox的输入并未改变这两个state的值,因此,这两个组件其实并没有被渲染。

          所以我们通过在ProductTable和SearchBar设置事件监听函数,并且每当函数触发时setState当前的状态,促使组件渲染重绘,完成数据的动态呈现。在具体实现中,可以通过refs锚点来获取具体的具名组件,并通过调用组件的getDOMNode方法,获取对于DOM对象并据此设置新的state。

    /** @jsx React.DOM */
    
    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() {
            console.log(this.props);
            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.getDOMNode().value,
                this.refs.inStockOnlyInput.getDOMNode().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'}
    ];
    
    React.render(<FilterableProductTable products={PRODUCTS} />, document.body);

    对,就是这样

           例子虽然非常简单,但是里面蕴含的思想确实值得玩味。组件的设计,数据的传递,状态集的确定,双向数据的传递以及事件处理和获取具名组件等等技术都包含在内,如果真的吃透了这个例子,那么我想在今后的可重用敏捷开发之路上必定又有新的收获,具体到我们的实现上就是组件设计的更为优美,代码量更为精少。

  • 相关阅读:
    Android Jetpack之WorkManager: 观察结果
    解决'androidx.arch.core:core-runtime' has different version for the compile (2.0.0) and runtime (2.0.1)
    我要研究一下minio,管理大量的照片
    分发消息的写法
    百度地图坐标转换
    HighChart 实现从后台取数据来实时更新柱状和折线组图
    导出Excel
    Java 8新特性之集合
    java中的Switch case语句
    提问:"~"运算符
  • 原文地址:https://www.cnblogs.com/accordion/p/4438475.html
Copyright © 2011-2022 走看看