我的 React Native 技能树点亮计划 の React Native 从 ES5 到 ES6 的语法升级

907 查看

@author ASCE1885的 Github 简书 微博 CSDN 知乎
本文由于潜在的商业目的,不开放全文转载许可,谢谢!


nami.png-250.3kB

广而告之时间:我的新书《Android 高级进阶》(https://item.jd.com/10821975932.html在京东开始预售了,欢迎订购!


TB2MnqlXH1J.eBjSszcXXbFzVXa_!!1020536390.png-39kB

ECMAScript 2015(为了行文简短起见,后文统称为 ES6)已经于 2015 年 6 月份正式发布,React 框架从 v0.13[1] 版本开始增加对 ES6 语法的支持,React Native 框架从 v0.18.0 版本开始,例子工程 AwesomeProject 的 index.android.jsindex.ios.js 中的语法也由之前版本的 ES5 语法切换到 ES6 语法。虽然官方没有明确说不鼓励 ES5 语法,但使用 ES6 代替 ES5 是大势所趋,为何不就现在开始做呢?在新开发的代码中,我们建议尽量使用 ES6 语法。下面就来具体说说 React Native 开发中哪些方面涉及到 ES5 语法到 ES6 语法的升级。

模块的导入

在 ES5 语法中,模块的导入使用 require 语句,到了 ES6 则改用 import 语句,先来看下 ES5 语法:

let React = require('react-native');
let {
    AppRegistry,
    StyleSheet,
    Text,
    View
} = React;

根据 ES6 语法修改如下:

import React, {
    AppRegistry,
    StyleSheet,
    Component,
    Text,
    View
} from 'react-native';

可以看到,使用 ES6 语法时,我们多导入了一个名为 Component 的组件,从类的定义一节可以看出,这个 Component 是 React 组件的基类。上面代码中是从 react-native 包中导入 ReactComponent 的,但这一做法从 React Native 的 v0.25.1[2] 版本开始被标记为 deprecated,取而代之的是下面这种写法:

import React, { Component } from 'react';
import {     
    AppRegistry,
    StyleSheet,
    Text,
    View
} from 'react-native';

也就是说,ReactComponent 等 React 相关的组件已经从 react-native 包中独立出来并放到了名为 react 的包中。这一变化其实从 2015 年 10 月份发布的 React v0.14[3] 可以看出端倪来,React 的这次版本发布一个主要的变化是将原本的 React 包拆分成 React 和 React DOM 两个包,将与浏览器渲染相关的代码独立成 React DOM,而多个平台可以共用的代码独立成 React,这样一来就为 React 在不同平台(浏览器和移动端原生系统)的通用性铺平了道路。


image_1ao1ep4mo1keh1m2p1j341b7b1hpk9.png-155.1kB

如果你在 React Native v0.25.1 之前就已经使用 RN 进行开发,那么不可避免的会需要将 import 语法进行升级,手动修改当代码量较多时肯定是不现实的,可以使用开发者社区给出的自动转换方案 codemod-RN24-to-RN25[4]

模块的导出

在 ES6 语法中,模块的导出使用 export default 代替 module.exports,如下所示:

// ES5 语法
var SwipeLayout = React.createClass({
    ...
});

module.exports = SwipeLayout;

// ES6 语法
export default SwipeLayout extends React.Component {
    ...
}

类的定义

从 ES6 开始引入了类的概念,我们通过关键字 class 可以实现类的定义,本质上,ES6 中的 class 概念可以看作是一个语法糖,它使得开发者可以使用更面向对象的语法进行代码的编写。

ES5 中其实也是有类的概念的,只不过更多的是以模块作为称呼,ES5 中定义一个模块语法如下:

var F8App = React.createClass({
    componentDidMount: function() {//...},
    componentWillUnmount: function() {//...},
    render: function() {//...},
});

使用 ES6 语法进行改写,如下所示:

class F8App extends React.Component {
    constructor(props) {//...}
    componentDidMount() {//...}
    componentWillUnmount() {//...}
    render() {//...}
}

可以看到,最明显的区别是 ES6 使用 React.Component 来代替 ES5 的 React.createClass,同时,类中方法的定义不再需要尾随 function,而且方法之间不需要以 , 分隔;同时增加了构造函数 constructor

成员变量的声明

使用 ES5 语法时,React Native 组件的成员变量声明方式如下所示:

let SwipeLayout = React.createClass({
    _panResponder:{},
    _isExpanded:true,
    _isSwiping:false,
    ...
});

当切换到 ES6 语法时,由于 class 引入了构造函数的概念,因此,成员变量的初始化都应该放在构造函数中,如下所示:

class SwipeLayout extends React.Component {
    constructor(props) {
        super(props);
        this._panResponder = {};
        this._isExpanded = true;
        this._isSwiping = false;
    }
}

类的静态成员变量和静态成员函数的声明

ES5 语法中,在 React Native 组件中声明静态成员变量和静态成员函数的方式如下:

let F8HeaderIOS = React.createClass({
    statics: {
        height : 100
        verify : function() {
            ...
        }
    },
});

ES6 语法中定义如下所示:

class F8HeaderIOS extends React.Component {
  static height: 100;
  static verify() {
    ...
  }
}

属性的声明

在 ES5 语法中,React Native 组件中属性的声明和默认值的指定如下所示,是在组件内部声明的:

var MyComponent = React.createClass({
    propTypes: {
        aStringProp: React.PropTypes.string
    },
    getDefaultProps: function() {
        return { aStringProp: '' };
    },
});

到了 ES6 的 class 中,要求属性的类型声明和默认值声明移动到组件定义的外部实现,同时在 class 类定义中将 getDefaultProps 函数重构为一个属性 defaultProps,转换为 ES6 语法之后的代码如下所示:

class MyComponent extends React.Component {...}
MyComponent.propTypes = {
    aStringProp: React.PropTypes.string
};
MyComponent.defaultProps = {
    aStringProp: ''
};

状态的初始化

使用 ES5 语法时,React Native 组件的状态变量是在 getInitialState 函数中初始化的,如下所示:

let MyComponent = React.createClass({
    getInitialState: function() {
        return {
            scrollTop: new Animated.Value(0),
        };
  },
});

到了 ES6 语法中,React Native 团队修改了状态变量的初始化方式,取消了单独的 getInitialState 函数,将初始化放在构造函数中,并提供 this.state 实例变量用来存储状态变量,如下所示:

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            scrollTop: new Animated.Value(0),
        };
    }
}

实例函数/回调函数的绑定

ES5 语法中,React Native 的 createClass 函数提供的一个便利的功能是自动绑定类作用域中的函数到组件实例,例如 createClass 里面定义的函数可以通过 this 直接指向本组件的某个成员函数,如下所示:

let MyComponent = React.createClass({
    _handleClick : function() { // 这个方法将作为回调函数使用
        console.log('onClick');
    },

    render : function() {
        return (
            <View style={styles.container}>
                <Text style={styles.normal}
                    onPress={this._handleClick}>
                    确定
                </Text>
            </View>
        );
    },
});

到了 ES6 的 class 定义中,开发者必须手动进行回调函数的绑定操作,React Native 团队推荐在组件的构造函数中进行,如下所示:

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        // 手动执行绑定操作
        this._handleClick = this._handleClick.bind(this);
    }

    _handleClick() {
        console.log('onClick');
    }

    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.normal}
                    onPress={this._handleClick}>
                    确定
                </Text>
            </View>
        );
    }
}

可以看到,如果 React Native 组件中存在多个回调函数需要进行 bind 操作,那么会存在较多的样板代码,如下所示:

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        // 手动执行绑定操作
        this._handleClick = this._handleClick.bind(this);
        this._handleTouch = this._handleTouch.bind(this);
        ...
    }
}

为了减少样板代码的存在,提高开发效率,我们可以将 bind 操作进行一次封装,提供一个 BaseComponent 的基类来简化绑定操作,如下所示:

class BaseComponent extends React.Component {
    _bind(...methods) {
        methods.forEach( (method) => this[method].bind(this) );
    }
}

class MyComponent extends BaseComponent {
        constructor(props) {
        super(props);
        // 手动执行绑定操作
        this._bind('_handleClick', '_handleTouch');
    }
}

Mixins

我们知道 ES5 语法中使用 React.createClass 是支持 Mixins 的,我们可以通过给 mixins 属性赋值从而给对应的组件添加 Mixins 能力,可以添加多个 Mixins,如下所示:

var SomeMixin = {
    doSomething() {

    }
};

var F8SessionDetails = React.createClass({
    mixins: [SomeMixin],

    getInitialState: function() {
        return {
            scrollTop: new Animated.Value(0),
        };
    },
    ...
 });

当你切换到 ES6 中的 class 时 Mixins 功能不可用。

拓展阅读

《Refactoring React Components to ES6 Classes》[5]
《React Native 的 ES6 类写法与未定义错误》[6]
《React and ES6 - Part 1, Introduction》[7]
《How To Use ES6 Arrow Functions With React Native》[8]
《react native 中es6语法解析》[9]
《React.createClass versus extends React.Component》[10]
《React on ES6+》[11]

欢迎关注我的微信公众号 ASCE1885,专注与原创或者分享 Android,iOS,ReactNative,Web 前端移动开发领域高质量文章,主要包括业界最新动态,前沿技术趋势,开源函数库与工具等。



[1]:https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html

[2]:https://github.com/facebook/react-native/releases/tag/v0.25.1

[3]:https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html

[4]:https://github.com/sibelius/codemod-RN24-to-RN25

[5]:http://www.newmediacampaigns.com/blog/refactoring-react-components-to-es6-classes

[6]:http://www.wangchenlong.org/2016/04/26/1604/261-rn-es6-class/

[7]:http://egorsmirnov.me/2015/05/22/react-and-es6-part1.html

[8]:http://moduscreate.com/how-to-use-es6-arrow-functions-with-react-native/

[9]:http://www.ghugo.com/react-native-es6/

[10]:https://toddmotto.com/react-create-class-versus-component/

[11]:https://babeljs.io/blog/2015/06/07/react-on-es6-plus