React类、ES7属性初始化(第二部分)

这是React和ECMAScript6结合使用系列文章的第二篇。

下面是所有系列文章章节的链接:

本篇文章Github源码

ReactJS

第一篇文章中,我们开始介绍如何使用ES6来创建静态的组建并且输出Hello from ES6. Not so exciting :)

在这篇文章中,我们将创建一个名字叫做CartItem的更复杂的组建。它将输出购物车中的一些产品信息,包括图片、标题和价格。

此外,用户可以和CartItem组建交互,通过点击增加或者减少改变items的数量。并且我们的组建将对交互后的总价格做出动态改变。

最后的项目效果图:

cover

创建index.html文件

让我们来创建一个简单的html模版文件。

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>React and ES6 Part 2</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/5.5.2/css/foundation.min.css">
</head>
<body>
<div class="root"></div>
<script src="dist/bundle.js"></script>
</body>
</html>

注意我们已经通过cdn添加了Foundation CSS框架服务.它可以让我们微应用看起来很漂亮。同时,class为root的div将是我们应用加载的地方。

Gulpfile.js

创建gulpfile.js文件,拷贝下面的代码到gulpfile.js文件中。

var gulp = require('gulp');
var browserify = require('browserify');
var babelify = require('babelify');
var source = require('vinyl-source-stream');

gulp.task('build', function () {
    return browserify({entries: './app.jsx', extensions: ['.jsx'], debug: true})
        .transform('babelify', {presets: ['es2015', 'react']})
        .bundle()
        .on('error', function(err) { console.error(err); this.emit('end'); })
        .pipe(source('bundle.js'))
        .pipe(gulp.dest('dist'));
});

gulp.task('watch', ['build'], function () {
    gulp.watch('*.jsx', ['build']);
});

gulp.task('default', ['watch']);

package.json

  1. 创建一个空文件夹,切换到这个文件夹中,在终端输入npm init初始化你的package.json

  2. 运行npm install --save react react-dom,这将安装reactreact-dom到你的node_modules文件夹并且作为依赖库保存到package.json文件中。

  3. 运行npm install --save-dev gulp browserify babelify vinyl-source-stream babel-preset-es2015 babel-preset-react,这将安装更多的依赖到你的node_modules文件夹。

Main application React Component

创建app.jsx:

import React from 'react';
import ReactDOM from 'react-dom';
import CartItem from './cartItem';

const order = {
    title: 'Fresh fruits package',
    image: 'http://images.all-free-download.com/images/graphiclarge/citrus_fruit_184416.jpg',
    initialQty: 3,
    price: 8
};

ReactDOM.render(
    <CartItem title={order.title}
              image={order.image}
              initialQty={order.initialQty}
              price={order.price}/>,
    document.querySelector('.root')
);

上面的代码做了什么:

  • Lines 1-2. 我们导入 React / ReactDOM 库。

  • Line 3. 导入我们马上要创建的CartItem组建。

  • Lines 5-10. 给CartItem组建设置相关属性(属性包括 item title, image, initial quantity and price).

  • Lines 12-18. 加载CartItem组建到一个CSS类为root的DOM元素上。

CartItem React Component 架构

现在是创建负责显示项目的数据以及与用户的交互组件的时候了。

添加下面的代码到cartItem.jsx文件中:

import React from 'react';

export default class CartItem extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            qty: props.initialQty,
            total: 0
        };
    }
    componentWillMount() {
        this.recalculateTotal();
    }
    increaseQty() {
        this.setState({qty: this.state.qty + 1}, this.recalculateTotal);
    }
    decreaseQty() {
        let newQty = this.state.qty > 0 ? this.state.qty - 1 : 0;
        this.setState({qty: newQty}, this.recalculateTotal);
    }
    recalculateTotal() {
        this.setState({total: this.state.qty * this.props.price});
    }
}

代码解释:

  • Lines 4-10. 这是ES6中React类的构造函数。super(props)这句代码是先处理父类的props,这句代码必不可少。下面的状态机变量的设置相当于ES5中getInitialState()方法状态机变量的初始化,我们通过this.state来给状态机变量设置初始值。个人意见,我比较喜欢ES6中构造函数的写法。

  • Lines 11-13. componentWillMount()是生命周期中的方法,在这个方法里面我们通过recalculateTotal()方法对总价格做了计算。

  • Lines 14-20. 给用户提供增加和减少的交互方法。当用户点击相应的按钮(如前面的效果图所示)时,这两个方法会被调用。

CartItem render method

CartItem类中添加新的方法:

export default class CartItem extends React.Component {

    // previous code we wrote here

    render() {
        return (
          <article className="row large-4">
              <figure className="text-center">
                  <p>
                      <img src={this.props.image}/>
                  </p>
                  <figcaption>
                      <h2>{this.props.title}</h2>
                  </figcaption>
              </figure>
              <p className="large-4 column"><strong>Quantity: {this.state.qty}</strong></p>

              <p className="large-4 column">
                  <button onClick={this.increaseQty.bind(this)} className="button success">+</button>
                  <button onClick={this.decreaseQty.bind(this)} className="button alert">-</button>
              </p>

              <p className="large-4 column"><strong>Price per item:</strong> ${this.props.price}</p>

              <h3 className="large-12 column text-center">
                  Total: ${this.state.total}
              </h3>

          </article>
        )
    }
}

这里我们只是使用JSX+组建,再加上一些基础的CSS输出漂亮的标签。

不要担心{this.increaseQty.bind(this)}这句代码的使用,下一小结中我们将会详细讲解,现在你需要相信我,这句代码会调用CartItem类中的increaseQty()方法即可。

因此,到现在我们已经有了和用户交互的漂亮的应用了,它向我们展示了如何使用ECMAScript6来编写React 组建。就我个人而言,我很喜欢ES6带来的新的语法。

到现在我们还没有完成。在完成这篇文章之前,我们将再看看ES6中其它的一些新的特性。

Default Props and Prop Types for ES6 React classes

想象我们想要为CartItem组建添加一些验证和默认值。

幸运的是,你只需要在CartItem类中添加如下代码即可。

static get defaultProps() {
    return {
      title: 'Undefined Product',
      price: 100,
      initialQty: 0
    }
}


static propTypes = {
  title: React.PropTypes.string.isRequired,
  price: React.PropTypes.number.isRequired,
  initialQty: React.PropTypes.number
}

PS: 也可以将上面的代码删除,在cartItem里面非CartItem类的内部添加如下代码:

CartItem.defaultProps = {
    title: 'Undefined Product',
    price: 100,
    initialQty: 0
}

CartItem.propTypes = {
    title: React.PropTypes.string.isRequired,
    price: React.PropTypes.number.isRequired,
    initialQty: React.PropTypes.number
}

就我个人而言,比较喜欢第一种写法,它不会破坏类的封装性。

添加上面的代码后,如果你给title属性添加numeric类型的值,将在控制台出现警告,这就是React属性验证的功能。

Bringing ES7 into the project

你可能会问一个合理的问题,为什么ES6还没有定稿,在你的标题中为什么出现ES7呢?

我会告诉你,让我们展望未来。我们开始使用non-breaking、property initializers和decorators的新特性。

即使ES7还处于非常早期的阶段,在Babel中已经实现了部分的建议性的新特性。这些实验性的新特性是从ES5到ES7令人惊叹的新的特性的转变。

首先,通过npm install --save-dev babel-preset-stage-0命令安装缺失的npm module

其次,为了在我们项目中能够使用新的语法,我们需要在gulpfile.js文件的第8行做一些改变,代码如下:

.transform('babelify', {presets: ['react', 'es2015', 'stage-0']})

你可以从GitHub repository直接拷贝gulpfile.js完整的代码。

ES7 React 组建属性初始化/默认值/类型

Inside class add this right above :

CartItem中将下面的代码:

static get defaultProps() {
    return {
      title: 'Undefined Product',
      price: 100,
      initialQty: 0
    }
}


static propTypes = {
  title: React.PropTypes.string.isRequired,
  price: React.PropTypes.number.isRequired,
  initialQty: React.PropTypes.number
}

替换成下面的代码:

  static propTypes = {
        title: React.PropTypes.string.isRequired,
        price: React.PropTypes.number.isRequired,
        initialQty: React.PropTypes.number
    };    
    static defaultProps = {
        title: 'Undefined Product',
        price: 100,
        initialQty: 0
    };

ES7 Property Initialiazers for initial state of React component

最后一步将初始状态从构造函数中转变成属性初始化。

CartItem构造函数的后天添加正确的代码:

export default class CartItem extends React.Component {

    // .. constructor starts here
    state = {
        qty: this.props.initialQty,
        total: 0
    };
    // .. some code here

你需要把状态初始化代码从构造函数中删除。

最后你需要检查一下cartItem.jsx文件里面完整的代码:

import React from 'react';

export default class CartItem extends React.Component {
    constructor(props) {
        super(props);
    }

    state = {
        qty: this.props.initialQty,
        total: 0
    };

    static propTypes = {
        title: React.PropTypes.string.isRequired,
        price: React.PropTypes.number.isRequired,
        initialQty: React.PropTypes.number
    };
    static defaultProps = {
        title: 'Undefined Product',
        price: 100,
        initialQty: 0
    };

    componentWillMount() {
        this.recalculateTotal();
    }
    increaseQty() {
        this.setState({qty: this.state.qty + 1}, this.recalculateTotal);
    }
    decreaseQty() {
        let newQty = this.state.qty > 0 ? this.state.qty - 1 : 0;
        this.setState({qty: newQty}, this.recalculateTotal);
    }
    recalculateTotal() {
        this.setState({total: this.state.qty * this.props.price});
    }

    render() {
        return (
          <article className="row large-4">
              <figure className="text-center">
                  <p>
                      <img src={this.props.image}/>
                  </p>
                  <figcaption>
                      <h2>{this.props.title}</h2>
                  </figcaption>
              </figure>
              <p className="large-4 column"><strong>Quantity: {this.state.qty}</strong></p>

              <p className="large-4 column">
                  <button onClick={this.increaseQty.bind(this)} className="button success">+</button>
                  <button onClick={this.decreaseQty.bind(this)} className="button alert">-</button>
              </p>

              <p className="large-4 column"><strong>Price per item:</strong> ${this.props.price}</p>

              <h3 className="large-12 column text-center">
                  Total: ${this.state.total}
              </h3>

          </article>
        )
    }
}

结论

在这一节中,我们熟练掌握了ES6和ES7 React组建属性的初始化,类型绑定。

下一小结,我们继续研究React+ES6系列教程。

参考文档

扫码申请加入全栈部落
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值