附录一、React ES5、ES6+ 常见用法对照表

一看就懂的 React ES5、ES6+ 常见用法对照表

前言

React 是 Facebook 推出的开源 JavaScript Library。自从 React 正式开源后,React 生态系开始蓬勃发展。事实上,透过学习 React 生态系(ecosystem)的过程中,可以让我们顺便学习现代化 Web 开发的重要观念(例如:ES6、WebpackBabel、模组化等),成为更好的开发者。虽然 ES6(ECMAScript2015)、ES7 是未来趋势(本文将 ES6、ES7 称为 ES6+),然而目前在网路上有许多的学习资源仍是以 ES5 为主,导致读者在学习上遇到一些坑洞和迷惑(本文假设读者对于 React 已经有些基本认识,若你对于 React 尚不熟悉,建议先行阅读官方文件本篇入门教学)。因此本文希望透过整理在 React 中 ES5、ES6+ 常见用法对照表,让读者们可以在实现功能时(尤其在 React Native)可以更清楚两者的差异,无痛转移到 ES6+。

大纲

  1. Modules
  2. Classes
  3. Method definition
  4. Property initializers
  5. State
  6. Arrow functions
  7. Dynamic property names & template strings
  8. Destructuring & spread attributes
  9. Mixins
  10. Default Parameters

1. Modules

随着 Web 技术的进展,模组化开发已经成为一个重要课题。关于 JavaScript 模组化我们这边不详述,建议读者参考 这份投影片这篇文章

ES5 若使用 CommonJS 标准,一般使用 require() 用法引入模组:

  1. var React = require('react');
  2. var MyComponent = require('./MyComponent');

输出则是使用 module.exports

  1. module.exports = MyComponent;

ES6+ import 用法:

  1. import React from 'react';
  2. import MyComponent from './MyComponent';

输出则是使用 export default

  1. export default class MyComponent extends React.Component {
  2. }

2. Classes

在 React 中组件(Component)是组成视觉页面的基础。在 ES5 中我们使用 React.createClass() 来建立 Component,而在 ES6+ 则是用 Classes 继承 React.Component 来建立 Component。若是有写过 Java 等物件导向语言(OOP)的读者应该对于这种写法比较不陌生,不过要注意的是 JavaScript 仍是原型继承类型的物件导向程式语言,只是使用 Classes 让物件导向使用上更加直观。对于选择 class 使用上还有疑惑的读者建议可以阅读 React.createClass versus extends React.Component 这篇文章。

ES5 React.createClass() 用法:

  1. var Photo = React.createClass({
  2. render: function() {
  3. return (
  4. <div>
  5. <images alt={this.props.description} src={this.props.src} />
  6. </div>
  7. );
  8. }
  9. });
  10. ReactDOM.render(<Photo />, document.getElementById('main'));

ES6+ class 用法:

  1. class Photo extends React.Component {
  2. render() {
  3. return <images alt={this.props.description} src={this.props.src} />;
  4. }
  5. }
  6. ReactDOM.render(<Photo />, document.getElementById('main'));

在 ES5 我们会在 componentWillMount 生命周期定义希望在 render 前执行,且只会执行一次的任务:

  1. var Photo = React.createClass({
  2. componentWillMount: function() {}
  3. });

在 ES6+ 则是定义在 constructor 建构子中:

  1. class Photo extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. // 原本在 componentWillMount 操作的动作可以放在这
  5. }
  6. }

3. Method definition

在 ES6 中我们使用 Method 可以忽略 function,,使用上更为简洁!ES5 React.createClass() 用法:

  1. var Photo = React.createClass({
  2. handleClick: function(e) {},
  3. render: function() {}
  4. });

ES6+ class 用法:

  1. class Photo extends React.Component {
  2. handleClick(e) {}
  3. render() {}
  4. }

4. Property initializers

Component 属性值是资料传递重要的元素,在 ES5 中我们使用 propTypesgetDefaultProps 来定义属性(props)的预设值和型别:

  1. var Todo = React.createClass({
  2. getDefaultProps: function() {
  3. return {
  4. checked: false,
  5. maxLength: 10,
  6. };
  7. },
  8. propTypes: {
  9. checked: React.PropTypes.bool.isRequired,
  10. maxLength: React.PropTypes.number.isRequired
  11. },
  12. render: function() {
  13. return();
  14. }
  15. });

在 ES6+ 中我们则是参考 ES7 property initializers 使用 class 中的静态属性(static properties)来定义:

  1. class Todo extends React.Component {
  2. static defaultProps = {
  3. checked: false,
  4. maxLength: 10,
  5. }; // 注意有分号
  6. static propTypes = {
  7. checked: React.PropTypes.bool.isRequired,
  8. maxLength: React.PropTypes.number.isRequired
  9. };
  10. render() {
  11. return();
  12. }
  13. }

ES6+ 另外一种写法,可以留意一下,主要是看各团队喜好和规范,选择合适的方式:

  1. class Todo extends React.Component {
  2. render() {
  3. return (
  4. <View />
  5. );
  6. }
  7. }
  8. Todo.defaultProps = {
  9. checked: false,
  10. maxLength: 10,
  11. };
  12. Todo.propTypes = {
  13. checked: React.PropTypes.bool.isRequired,
  14. maxLength: React.PropTypes.number.isRequired,
  15. };

5. State

在 React 中 PropsState 是资料流传递的重要元素,不同的是 state 可更动,可以去执行一些运算。在 ES5 中我们使用 getInitialState 去初始化 state

  1. var Todo = React.createClass({
  2. getInitialState: function() {
  3. return {
  4. maxLength: this.props.maxLength,
  5. };
  6. },
  7. });

在 ES6+ 中我们初始化 state 有两种写法:

  1. class Todo extends React.Component {
  2. state = {
  3. maxLength: this.props.maxLength,
  4. }
  5. }

另外一种写法,使用在建构式初始化。比较推荐使用这种方式,方便做一些运算:

  1. class Todo extends React.Component {
  2. constructor(props){
  3. super(props);
  4. this.state = {
  5. maxLength: this.props.maxLength,
  6. };
  7. }
  8. }

6. Arrow functions

在讲 Arrow functions 之前,我们先聊聊在 React 中 this 和它所代表的 context。在 ES5 中,我们使用 React.createClass() 来建立 Component,而在 React.createClass() 下,预设帮你绑定好 methodthis,你毋须自行绑定。所以你可以看到像是下面的例子,callback function handleButtonClick 中的 this 是指到 component 的实例(instance),而非触发事件的物件:

  1. var TodoBtn = React.createClass({
  2. handleButtonClick: function(e) {
  3. // 此 this 指到 component 的实例(instance),而非 button
  4. this.setState({showOptionsModal: true});
  5. },
  6. render: function(){
  7. return (
  8. <div>
  9. <Button onClick={this.handleButtonClick}>{this.props.label}</Button>
  10. </div>
  11. )
  12. },
  13. });

然而自动绑定这种方式反而会让人容易误解,所以在 ES6+ 推荐使用 bind 绑定 this 或使用 Arrow functions(它会绑定当前 scopethis context)两种方式,你可以参考下面例子:

  1. class TodoBtn extends React.Component
  2. {
  3. handleButtonClick(e){
  4. // 确认绑定 this 指到 component instance
  5. this.setState({toggle: true});
  6. }
  7. render(){
  8. // 这边可以用 this.handleButtonClick.bind(this) 手动绑定或是 Arrow functions () => {} 用法
  9. return (
  10. <div>
  11. <Button onClick={this.handleButtonClick.bind(this)} onClick={(e)=> {this.handleButtonClick(e)} }>{this.props.label}</Button>
  12. </div>
  13. )
  14. },
  15. }

Arrow functions 虽然一开始看起来有点怪异,但其实观念很简单:一个简化的函数。函数基本上就是参数(不一定要有参数)、表达式、回传值(也可能是回传 undefined):

  1. // Arrow functions 的一些例子
  2. ()=>7
  3. e=>e+2
  4. ()=>{
  5. alert('XD');
  6. }
  7. (a,b)=>a+b
  8. e=>{
  9. if (e == 2){
  10. return 2;
  11. }
  12. return 100/e;
  13. }

不过要注意的是无论是 bind 或是 Arrow functions,每次执行回传都是指到一个新的函数,若需要再调用到这个函数,请记得先把它存起来:

错误用法:

  1. class TodoBtn extends React.Component{
  2. componentWillMount(){
  3. Btn.addEventListener('click', this.handleButtonClick.bind(this));
  4. }
  5. componentDidmount(){
  6. Btn.removeEventListener('click', this.handleButtonClick.bind(this));
  7. }
  8. onAppPaused(event){
  9. }
  10. }

正确用法:

  1. class TodoBtn extends React.Component{
  2. constructor(props){
  3. super(props);
  4. this.handleButtonClick = this.handleButtonClick.bind(this);
  5. }
  6. componentWillMount(){
  7. Btn.addEventListener('click', this.handleButtonClick);
  8. }
  9. componentDidMount(){
  10. Btn.removeEventListener('click', this.handleButtonClick);
  11. }
  12. }

更多 Arrows and Lexical This 特性可以参考这个文件

7. Dynamic property names & template strings

以前在 ES5 我们要动态设定属性名称时,往往需要多写几行程式码才能达到目标:

  1. var Todo = React.createClass({
  2. onChange: function(inputName, e) {
  3. var stateToSet = {};
  4. stateToSet[inputName + 'Value'] = e.target.value;
  5. this.setState(stateToSet);
  6. },
  7. });

但在 ES6+中,透过 enhancements to object literalstemplate strings 可以轻松完成动态设定属性名称的任务:

  1. class Todo extends React.Component {
  2. onChange(inputName, e) {
  3. this.setState({
  4. [`${inputName}Value`]: e.target.value,
  5. });
  6. }
  7. }

Template Strings 是一种语法糖(syntactic sugar),方便我们组织字串(这边也用上 letconst 变数和常数宣告的方式,和 varfunction scope 不同的是它们是属于 block scope,亦即生存域存在于 {} 间):

  1. // Interpolate variable bindings
  2. const name = "Bob", let = "today";
  3. `Hello ${name}, how are you ${time}?` \\ Hello Bob, how are you today?

8. Destructuring & spread attributes

在 React 的 Component 中,父组件利用 props 来传递资料到子组件是常见作法,然而我们有时会希望只传递部分资料,此时 ES6+ 中的 DestructuringJSX 的 Spread Attributes ... Spread Attributes 主要是用来迭代物件:

  1. class Todo extends React.Component {
  2. render() {
  3. var {
  4. className,
  5. ...others, // ...others 包含 this.props 除了 className 外所有值。this.props = {value: 'true', title: 'header', className: 'content'}
  6. } = this.props;
  7. return (
  8. <div className={className}>
  9. <TodoList {...others} />
  10. <button onClick={this.handleLoadMoreClick}>Load more</button>
  11. </div>
  12. );
  13. }
  14. }

但使用上要注意的是若是有重复的属性值则以后来覆盖,下面的例子中若 ...this.props,有 className,则被后来的 main 所覆盖:

  1. <div {...this.props} className="main">
  2. </div>

Destructuring 也可以用在简化 Module 的引入上,这边我们先用 ES5 中引入方式来看:

  1. var React = require('react-native');
  2. var Component = React.component;
  3. class HelloWorld extends Component {
  4. render() {
  5. return (
  6. <View>
  7. <Text>Hello, world!</Text>
  8. </View>
  9. );
  10. }
  11. }
  12. export default HelloWorld;

以下 ES5 写法:

  1. var React = require('react-native');
  2. var View = React.View;

在 ES6+ 则可以直接使用 Destructuring 这种简化方式来引入模组中的组件:

  1. // 这边等于上面的写法
  2. var { View } = require('react-native');

更进一步可以使用 import 语法:

  1. import React, {
  2. View,
  3. Component,
  4. Text,
  5. } from 'react-native';
  6. class HelloWorld extends Component {
  7. render() {
  8. return (
  9. <View>
  10. <Text>Hello, world!</Text>
  11. </View>
  12. );
  13. }
  14. }
  15. export default HelloWorld;

9. Mixins

在 ES5 中,我们可以使用 Mixins 的方式去让不同的 Component 共用相似的功能,重用我们的程式码:

  1. var PureRenderMixin = require('react-addons-pure-render-mixin');
  2. React.createClass({
  3. mixins: [PureRenderMixin],
  4. render: function() {
  5. return <div className={this.props.className}>foo</div>;
  6. }
  7. });

但由于官方不打算在 ES6+ 中继续推行 Mixins,若还是希望使用,可以参考看看第三方套件或是这个文件的用法

10. Default Parameters

以前 ES5 我们函数要使用预设值需要这样使用:

  1. var link = function (height, color) {
  2. var height = height || 50;
  3. var color = color || 'red';
  4. }

现在 ES6+ 的函数可以支援预设值,让程式码更为简洁:

  1. var link = function(height = 50, color = 'red') {
  2. ...
  3. }

总结

以上就是 React ES5、ES6+常见用法对照表,能看到这边的你应该已经对于 React ES5、ES6 使用上有些认识,先给自己一些掌声吧!确实从 ES6 开始,JavaScript 和以前我们看到的 JavaScript 有些不同,增加了许多新的特性,有些读者甚至会很怀疑说这真的是 JavaScript 吗?ES6 的用法对于初学者来说可能会需要写一点时间吸收,下一章我们将进到同样也是有革新性设计和有趣的 React Native,用 JavaScript 和 React 写 Native App!

延伸阅读

  1. React/React Native 的ES5 ES6写法对照表
  2. React on ES6+
  3. react native 中es6语法解析
  4. Learn ES2015
  5. ECMAScript 6入门
  6. React官方网站
  7. React INTRO TO REACT.JS
  8. React.createClass versus extends React.Component
  9. react-native-coding-style
  10. Airbnb React/JSX Style Guide
  11. ECMAScript 6入门

| 回首页 | 上一章:用 React + Redux + Node(Isomorphic JavaScript)开发食谱分享网站 | 下一章:用 React Native + Firebase 开发跨平台行动应用程式 |

| 勘误、提问或许愿 |