React 组件构造方法: ES5 (createClass) 还是 ES6 (class)?

写 React 组件的时候,应该使用 React.createClass 语法还是 ES6 的 class 语法?或两者都不?这篇文章解释了两者之间的一些差异,希望能帮你做决定。

用 ES5 或 ES6 都可以完美地写 React 组件。

使用 JSX 意味着你已经需要一个「构建」步骤,也就是 Babel 将 JSX 转译(transpile)为 React.createElement 调用。很多人充分利用这点, 仅仅向 Babel 的转译列表中添加一项 es2015,就可以自由使用 ES6 的全部特性。

如果你在使用类似 QuikReact Heatpack 的工具,ES6 就已经为你配置好了。(如果你还没配置过环境可以读一下quick start React(英文))

比较:createClass vs class

下面是同一个组件分别使用 React.createClass 和 ES6 class 的例子:

var InputControlES5 = React.createClass({
    propTypes: {
        initialValue: React.PropTypes.string
    },
    defaultProps: {
        initialValue: ''
    },
    // 设置 initial state
    getInitialState: function() {
        return {
            text: this.props.initialValue || 'placeholder'
        };
    },
    handleChange: function(event) {
        this.setState({
            text: event.target.value
        });
    },
    render: function() {
        return (
            <div>
                Type something:
                <input onChange={this.handleChange} value={this.state.text} />
            </div>
        );
    }
});
class InputControlES6 extends React.Component {
    constructor(props) {
        super(props);

        // 设置 initial state
        this.state = {
            text: props.initialValue || 'placeholder'
        };

        // ES6 类中函数必须手动绑定
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        this.setState({
            text: event.target.value
        });
    }

    render() {
        return (
            <div>
                Type something:
                <input onChange={this.handleChange}
               value={this.state.text} />
            </div>
        );
    }
}
InputControlES6.propTypes = {
    initialValue: React.PropTypes.string
};
InputControlES6.defaultProps = {
    initialValue: ''
};

以下是几点关键区别:

函数绑定

这可能是最容易犯错的点(tripping point)。

createClass 很简单:每一个成员函数都由 React 自动绑定。任何时候需要调用,直接使用 this.whateverFn 即可,函数中的 this 变量在函数调用时会被正确设置。

用 ES6 class 需要当心:函数不是自动绑定的。你必须手动绑定。最好是在构造函数中做这事,就像上面的例子那样。

如果你不想总是手动敲这些函数绑定的代码,可以看看 react-autobindautobing-decorator

另一个方法是行内绑定,像下面这样:

// 使用 `.bind`:
render() {
    return (
        <input onChange={this.handleChange.bind(this)}
           value={this.state.text} />
    );
}

// --- 或 ---

// 使用胖箭头函数:
render() {
    return (
        <input onChange={() => this.handleChange()}
           value={this.state.text} />
    );
}

以上任何一种方法都行的通,但都不如前文的方法有效率。因为每次render 方法被调用的时候(这个调用会相当频繁)就会有一个新的函数被创建。相比于在构造函数中做仅仅一次函数绑定,这个方法会慢一些。

还有一个最终选项是将函数自身替换为胖箭头函数,像这样:

// 通常的做法
// 需要在别处绑定
handleChange(event) {
    this.setState({
        text: event.target.value
    });
}

// ES7 做法
// 搞定,不需要另外绑定
handleChange = (event) => {
    this.setState({
        text: event.target.value
    });
}

使用这个方法,你不需要做任何绑定。胖箭头函数的魔法会帮你全部搞定。函数内部的 this 变量会像预期的那样指向组件实例。

唯一的警告是,胖箭头函数是一个「实验性」的特性,也就是说并不在官方的 ES6 标准中。但是它受 Babel 支持,只要启用“stage-0” 预置(preset)即可。如果你喜欢胖箭头语法(读作「令 handleChange 为一个以事件为参数的胖箭头函数),试试看。

构造函数是否调用 super 方法

ES6 类构造器需要接受 props 作为参数并调用 super(call)createClass 并不需要这步,相比之下多了点例行公事(boilerplate)。

class vs createClass

这一点显而易见。后者以一个对象为参数调用 React.createClass 方法,前者使用 class 扩展 React.Component 类。

专家提示: 如果你在一个文件中有多个组件,可以直接引入 Component 少打些字:import React, {Component} form 'react'

Initial State 配置

createClass 方法接受一个 getInitialState (译者注:原文误作 initialState)函数作为参数一部分,这个函数会在组件挂载(mount)时被调用一次。

ES6 class 使用构造函数。在调用 super 之后,直接设置 state 即可。

propTypes 和 defaultProps 的位置

使用 createClass 的时候,将 propTypesdefaultProps 作为你传入的对象的属性。

使用 ES6 class 的时候,这些变成了类本身的属性,所以他们需要在类定义完之后被加到类上。

如果你开启了 ES7 的属性初始化器(property initializer),可以使用下面的简写法:

class Person extends React.Component {
    static propTypes = {
        name: React.PropTypes.string,
        age: React.PropTypes.string
    };

    static defaultProps = {
        name: '',
        age: -1
    };

    ...
}

忍者第三选项

除了 createClassclass 外, React 还支持所谓的「无状态(stateless)函数组件」。本质上它就是一个函数,没有 state,并且不能使用任何诸如 componentWillMountshouldComponentUpdate 的生命周期方法。对于接受某些 props 并直接基于这些 props 渲染的简单组件来说,无状态函数组件非常合适。下面是一个例子:

function Person({firstName, lastName}) {
    return (
        <span>{lastName}, {firstName}</span>
    );
}

这里用到了 ES6 的解构赋值特性来分离传入的 props,也可以写成下面这样:

function Person(props) {
    var firstName = props.firstName;
    var lastName = props.lastName;
    return (
        <span>{lastName}, {firstName}</span>
    );
}

该用哪一个才对?

Facebook 已经声明 React.createClass 最终会被 ES6 class 取代,不过他们也说「我们不会废弃 React.createClass,直到我们找到目前 mixin 用例的替代方案,并且在语言中支持类属性初始化器」。

只要有可能,尽量使用无状态函数组件。他们很简单,也会迫使你保持 UI 组件简单。

对于需要 state、生命周期方法、或(通过 refs)访问底层 DOM 节点的复杂组件,使用 class。

不过了解全部三种风格总是有好处的。当你在 StackOverflow 或别的地方查问题的时候,你可能会看到 ES5 和 ES6 两种风格的答案。ES6 风格正在积聚人气,但并不是唯一的风格。

总结

可以使用多种方式写 React 组件,我希望这篇概述能帮助你理清这方面的一些困惑。

本文根据@DAVE CEDDIA的《React: ES5 (createClass) or ES6 (class)?》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://daveceddia.com/react-es5-createclass-vs-es6-classes/

u9lyfish

常用昵称 u9lyfish,现就职于支付宝口碑前端团队,关注 ES6 和 React。

如需转载,烦请注明出处:http://www.w3cplus.com/react/react-es5-createclass-vs-es6-classes.html

返回顶部