React.js编程思想

admin 发布于:2015-11-12 00:01 栏目: 浏览:1228 评论:0
JavaScript框架层出不穷,在很多程序员看来,React.js是创建大型、快速的Web应用的最好方式。这一款由Facebook出品的JS框架,无论是在Facebook还是在Instagram中,它的表现都非常出色。
使用React.js的好处多多,但是其中非常好的一部分是它能够让你重新思考如何创建一个web应用。在本文中,我们将通过一步一步的创建一个产品数据表,并以此过程带领你思考和领悟React.js的编程思想。
步骤零:创建一些伪数据
假设现在我们已经有了一个JSON API和一些伪数据。如下图所示。当然,现在的设计看起来非常糟糕,但是它可以体现出我们将要创建的应用结构:

描述:React.js编程思想

图片:0.png

React.js编程思想


同时,我们可以通过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打散为层级组件
你需要做的第一件事情是在每一个组件(以及子组件)周围画上一个框,然后给每个组件起一个名字。如果设计图是由一个设计师给你的,他通常已经帮你完成了这件事情,因此你可以跑去问他设计图中各个层的名字,并以这些名字为你的React组件命名。
但是我们怎么知道哪些部分应该是一个组件呢?想象一下,加入你现在不是在创建一个应用,而是在创建一个新的函数或者对象,你会怎么做。在这里,我们 最好遵循”单一任务原则”,也就是说,一个组件最好只是做一件事情。如果一个组件的任务有所增加,那么你应该考虑是不是要把这个组件拆成几个更小的组件。
由于你经常需要讲一个JSON数据模型展示给用户,因此你需要检查这个模型结构是否正确以便你的UI(在这里指的就是你的组件结构)是否能够正确的 映射到这个模型上。这是因为用户界面和数据模型在信息构造方面都要一直,这意味着将你可以省下很多将UI分割成组件的麻烦事。你需要做的仅仅只是将数据模 型分隔成一小块一小块的组件以便它们都能够表示成组件。

描述:React.js编程思想

图片:0.png

React.js编程思想


在上面的示意图中,你可以看到在我们这个简单的应用中,包含着5个组件:
FilterableProductTable(橙色): 包含着整个例子
SearchBar(蓝色): 接收用户的输入
ProductTable(绿色): 基于用户输入展示和过滤数据集合
ProductCategoryRow(青色): 为每一种类型展示一个头部数据
ProductRow(红色): 为每种产品展示一行
如果你注意看ProductTable,你将会看到表头(就是包含着”Name”和”Price”标签的部分)并不是单独一个组件。这并不是什么硬性规定,无论你是否将这部分单独隔离出来都没有错。但是在这个例子中,我们将它作为ProductTable的一个部分,因为它是渲染数据集合的一个部分,而这是ProductTable的任务。然而,如果表头变得很复杂(例如,如果我们在其中添加了排序功能),那么你应该毫不犹豫的将它作为一个单独的ProductTableHeader组件。
现在我们已经理清楚了我们的应用中的组件。我们可以简单的将它们做一个层级排序,如下所示:
 - FilterableProductTable
   - SearchBar
   - ProductTable
     - ProductCategoryRow
     - ProductRow
步骤二:使用React创建一个静态版本的应用
/** @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=pw_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.renderComponent(<FilterableProductTable products={PRODUCTS} />, document.body);


既然现在我们的组件已经都创建完毕了,是时候开始实现我们的应用了。最简单的方式是使用数据模型来渲染一个有UI但是暂时没有交互的应用。在这里之所以把交互单独拿出来,是因为如果只是构建UI的话,非常直观而且几乎不需要什么思考,但是一旦涉及到交互,则会复杂的多。
在创建一个静态版本的应用时,你需要创建一个可以使用给其他组件的组件,同时你需要通过props来传递数据。在React中,props是一种从 父组件将数据传递给子组件的方式。当然,在React中还有一个叫做state的概念,但是在创建一个静态版本的应用时,千万不要使用state。 state仅仅只在有交互时使用,这意味着,数据会随时发生变化。因此,在这里不应该使用state。
你可以从上到下或者从下到上开始。这就是说,你可以从最外面的组件开始,或者从最里面的组件开始。一般来说,在简单应用中,一半是从上到下,而在大型的项目中,一半是从下到上开始。
在这一步的最后,你会拥有一个可重用的组件库,这个组件库将会渲染你的数据模型。由于这个版本是一个静态的版本,因此所有的组件中仅仅只有render()方法。最外层的组件(FilterableProductTable)将会接收你的数据模型作为prop。如果你对底层数据模型做了一些修改,并且调用renderComponent()方法,相应的UI将会发生变化。由于React是单向数据流(也可以说是单向数据绑定),同时具有很强的模块化,这些变化查看起来将会非常容易,这也是React运行这么快的原因之一。
在React中存在两种”模型”数据:props和state。理解二者之间的区别很重要。
步骤三:识别应用UI的状态(state)
为了让UI变得可交互,你需要能够在数据模型发生改变时触发一些UI的变化。React使用state使得这项工作变得简单。
为了能够正确的创建应用,首先要思考的第一件事情是应用状态的集合。标准很简单:不要重复你自己。例如如果你创建了一个TODO列表,你只需要创建 一个TODO项目列表,而不需要专门分出一个状态参数来记录TODO项目的数量。当你想要渲染这个TODO数量时,你只需要直接获取TODO数组的长度即 可。
现在思考一下例子中的数据。我们拥有:
原始的产品列表
用户输入的搜索关键字
复选框的值
过滤之后的产品列表
现在我们来看看上面这些数据,看看哪一个是state。问自己三个问题:
这个数据是否来自父组件中的props?如果是,那么它不是state。
它会随时间改变吗?如果不是,那么它不是state。
它能够基于组件中的任何state或者props计算出来吗?如果是,那么它不是state。
原始的产品列表作为props传递到组件中,因此它不是state。搜索字段和复选框因为会随时间变化并且不能够由其他state或者props计算出,因此是state。最后,产品的过滤列表不是state,因为它可以根据原始数据、搜索字段以及复选框一起计算出。
因此,我们的state包括:
用户输入的搜索字段
复选框的值
步骤四:弄清楚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=pw_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" value={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.renderComponent(<FilterableProductTable products={PRODUCTS} />, document.body);

现在我们已经弄清楚了state集合。下面我们要做的事情就是弄清楚这些state应该存在什么地方。
记住:React的数据是通过层级组件单向流动。弄清楚哪个组件应该拥有state并不是一件容易的事情。这往往是React新手最容易弄错的地方。
首先,对于应用中的每一个state,你需要:
找出那些需要基于state渲染的组件
找出一个公共的组件(也就是能够包含所有可能涉及state的组件的组件)
公共组件以及之外的组件应该拥有这个state
如果你找不出一个拥有state的组件,创建一个简单的可以包含这个state的组件,然后将它添加到公共组件之上
具体到例子中的应用:
ProductTable需要基于state过滤产品列表,而SearchBar需要展示搜索字段和复选框的状态
公共组件是FilterableProductTable
很明过滤字段和复选框值都存在于FilterableProductTable之中
到目前为止,我们已经弄清楚了我们的state位于FilterableProductTable中。首先,我们需要在FilterableProductTable中添加一个getInitialState()方法,这个方法将会返回{filterText: ", inStockOnly: false}来反映应用的初始状态。其次,将filterText和inStockOnly作为一个prop传递给ProductTable和SearchBar。最后,在ProductTable中使用这些props去过滤,并在SearchBar中设置相应的值。
步骤五:添加反向数据流
/** @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=pw_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"
                        value={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.renderComponent(<FilterableProductTable products={PRODUCTS} />, document.body);


到目前为止,我们的应用已经基本完成了。现在我们需要使用另一种方法来支持数据流:层级深处的组件需要更新FilterableProductTable的state。在React中完成这件事情非常直观,但是这和传统的双向数据绑定有一些不同。虽然React提供了一个叫做ReactLink的插件来帮助我们做类似于双向绑定的事情,但是为了能够明确其中的具体过程,我们在这里不使用这个插件。
到目前为止,如果你进行了一些输入或者选中了一些复选框,你会发现React并没有做出反应。这很正常,因为我们将value这个prop的值总是设置为等于来自FilterableProductTable中的state。现在来明确一下我们想要做的事情。我们想要确保无论用户在何时做了修改,应用都能正确的反应state。因为组件只可以改变自己的state,FilterableProductTable将会给SearchBar传递一个回调函数,这个毁掉函数将会在state发生变化时被触发。我们可以在输入框上使用onChange事件来进行触发。接着,由FilterableProductTable传递的回调函数将会调用setState(),应用将会被更新。
尽管听起来很复杂,但是其实需要写的代码非常少。同时,这也清晰的向我们展示了数据流是怎样穿过整个应用的。总结
到这里,我们的应用就算是创建完成了。本文可能并不会教你很多关于React的用法,但是你可以从中学习到React应用和模块创建的基本思想。React的代码非常具有模块化,而且易于阅读。现在就开学习使用React来创建不可思议的web应用吧。本文参考自thinking in react,原文地址http://facebook.github.io/react/docs/thinking-in-react.html
游客

返回顶部