React.js编程思想

JavaScript框架层出不穷,在很多程序员看来,React.js是创建大型、快速的Web应用的最好方式。这一款由Facebook出品的JS框架,无论是在Facebook还是在Instagram中,它的表现都非常出色。

使用React.js的好处多多,但是其中非常好的一部分是它能够让你重新思考如何创建一个web应用。在本文中,我们将通过一步一步的创建一个产品数据表,并以此过程带领你思考和领悟React.js的编程思想。

步骤零:创建一些伪数据

假设现在我们已经有了一个JSON API和一些伪数据。如下图所示。当然,现在的设计看起来非常糟糕,但是它可以体现出我们将要创建的应用结构:

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编程思想

在上面的示意图中,你可以看到在我们这个简单的应用中,包含着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


上一篇:HTML 5 <em> <strong> <dfn> <code>
下一篇:7个你可能不认识的CSS单位

相关推荐

  • 🙋Hanjst汉吉斯特改进+enSafeExpression安全表达式等

    Hanjst汉吉斯特模版语言及模版引擎,近期持续改进升级。 这次改进主要是增加了对安全输出表达式兼容,由于涉及到对软件开发过程中的效率和软件运行效率的平衡和取舍,所以多写了几句,以描述这个权衡利弊对...

    3 个月前
  • 🙋Hanjst汉吉斯特升级:+showImageAsync及性能改进等

    自2019年元旦🙋Hanjst汉吉斯特 模板语言及其编译引擎发布,已经过去一年多了。 这期间随着 🙋Hanjst汉吉斯特 的推广应用,我们也陆续发布了如下一些更新内容: 🛠️Hanjst/汉吉...

    4 个月前
  • 🙋Hanjst汉吉斯特优化+JsonDataFromScript等

    近日继续对 🙋Hanjst汉吉斯特优化改进。这次的改进思考是从服务器端返回的 HanjstJsonData的容器设计问题。目前的做法是服务器端的HanjstJsonData放入终端页面的一个Div元...

    2 个月前
  • 😉我用 Nuxt.js 仿了个掘金

    前言 首先肯定是要夸夸掘金啦,最开始从 CSDN 到 博客园 再到 掘金,个人感觉掘金的技术氛围非常的nice,真是个宝藏社区👏。技术文章大多以前端为主,对前端开发者非常友好,质量也是歪瑞古的。

    3 个月前
  • 😀一个原生js弹幕库

    danmujs 😀一个原生js弹幕库,基于 CSS3 Animation 地址、核心代码 本项目基于 rcbullets,项目约70%的代码基于rcbullets,首先要感谢这个项目的作者,如...

    6 个月前
  • 🕵️‍♀️由原型到JS中的“模拟类”

    讲述了有关 JavaScript 中原型相关知识,又引出了 JavaScript 中的“类“究竟是什么?,以及一系列相关问题。 一、前置知识 1、JavaScript 的面向对象(OOP) ​ 面向...

    4 个月前
  • 🔥《吊打面试官》系列 Node.js 必知必会必问!

    (/public/upload/f204a3b224d986128f1b4d9b8d06cd17) 前言 codeing 应当是一生的事业,而不仅仅是 30 岁的青春🍚 本文已收录 Git...

    5 个月前
  • 💖CSS + JS 送学妹满屏幕小爱心

    故事开始 午饭时间,暗恋已久的学妹拉着我的衣袖:“学长学长,你能不能让这些爱心变成五颜六色的吗~”。 我在旁边笑开了花~~~ image.png(/public/upload/04aaa24e...

    3 个月前
  • 🆘 一次理解清楚,为什么使用 React useEffect 中使用 setInterval 获取的值不是最新的

    Intro 这篇文章将通过一个使用 React Hook 常遇到的问题(stale state)入手,尝试理解 Hook 的内部运行逻辑。 废话不多说,直接看示例Sandbox。

    1 个月前
  • (干货👍)从详细操作js数组到浅析v8中array.js

    前言 最近在写面试编程题,经常用到数组,经常想偷个懒,用它提供的方法,奈何还是对数组方法使用不熟练,导致写了很多的垃圾代码,很多地方稍加修改的话肯定变得简洁高效优雅👊 所以✍这篇文章本着了解一下Ja...

    1 个月前

官方社区

扫码加入 JavaScript 社区