Part 2, React Classes and ES7 Property Initializers
这是React和ECMAScript6结合使用系列文章的第二篇。
下面是所有系列文章章节的链接:
在文章中,我们开始介绍如何使用ES6来创建静态的组建并且输出Hello from ES6
. Not so exciting :)
在这篇文章中,我们将创建一个名字叫做CartItem
的更复杂的组建。它将输出购物车中的一些产品信息,包括图片、标题和价格。
此外,用户可以和CartItem
组建交互,通过点击增加
或者减少
改变items的数量。并且我们的组建将对交互后的总价格做出动态改变。
最后的项目效果图:
创建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
添加了框架服务.它可以让我们微应用看起来很漂亮。同时,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
创建一个空文件夹,切换到这个文件夹中,在终端输入
npm init
初始化你的package.json
。运行
npm install --save react react-dom
,这将安装react
和react-dom
到你的node_modules
文件夹并且作为依赖库保存到package.json
文件中。运行
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']})
你可以从直接拷贝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系列教程。
参考文档
扫描二维码
关注更多精彩
点击“阅读原文”