1. 函数式组件和类组件--初识

1.1 函数式定义的无状态组件

定义组件的最简单方法是编写JavaScript函数

function Welcome(props) {  return <h1>Hello, {props.name}</h1>;}

此函数是一个有效的React组件,因为它接受单个“props”(代表属性)对象参数与数据并返回一个React元素。我们称这些组件为“函数组件”,因为它们实际上是JavaScript函数。

1.2 es6形式的extends React.Component定义的组件

class Welcome extends React.Component {  render() {    return <h1>Hello, {this.props.name}</h1>;  }}

从React的角度来看,上述两个组件是等效的。

以上内容来自于react官方文档

1.3 es5原生方式React.createClass定义的组件

class Welcome extends React.CreatClass { render() {   return <h1>Hello, {this.props.name}</h1>; }}

2. React创建组件的三种方式及其区别

2.1 React创建组件的三种方式

  • 纯函数式定义的无状态组件
  • React.createClass 定义的组件
  • Extends React.Component 定义的组件

2.2 纯函数式(无状态)组件的特点

无状态组件式从React 0.14 版本开始的。为了创建纯展示组件,这种组件只负责根据传入的props来展示,不涉及到要state状态的操作。

其官方指出:在大部分React代码中,大多数组件被写成无状态的组件,通过简单组合可以构建成其他的组件等;这种通过多个简单然后合并成一个大应用的设计模式被提倡。

目前Reart已发展到16.9,引入了Hook,推荐使用无状态组件。

无状态函数式组件形式上表现为一个只带有一个render方法的组件类,通过函数形式或者ES6 arrow function的形式在创建,并且该组件是无state状态的。具体的创建形式如下:
第一个参数是 props,第二个是 context

function Welcome(props, context) {  return <h1>Hello, {props.name}</h1>;}ReactDOM.render(<Welcome name="whongliang" />, mountNode) 

无状态组件的创建形式使代码的可读性更好,并且减少了大量冗余的代码,精简至只有一个render方法,大大的增强了编写一个组件的便利,除此之外无状态组件还有以下几个显著的特点:

  • 组件不会被实例化,整体渲染性能得到提升

因为组件被精简成一个render方法的函数来实现的,由于是无状态组件,所以无状态组件就不会在有组件实例化的过程,无实例化过程也就不需要分配多余的内存,从而性能得到一定的提升。

  • 组件不能访问this对象

无状态组件由于没有实例化过程,所以无法访问组件this中的对象,例如:this.refthis.state等均不能访问。若想访问就不能使用这种形式来创建组件

  • 组件无法访问生命周期的方法

因为无状态组件是不需要组件生命周期管理和状态管理,所以底层实现这种形式的组件时是不会实现组件的生命周期方法。所以无状态组件是不能参与组件的各个生命周期管理的。

  • 无状态组件只能访问输入的props,同样的props会得到同样的渲染结果,不会有副作用

无状态组件被鼓励在大型项目中尽可能以简单的写法来分割原本庞大的组件,未来React也会这种面向无状态组件在譬如无意义的检查和内存分配领域进行一系列优化,所以只要有可能,尽量使用无状态组件。

补充

无状态组件内部其实是可以使用ref功能的,虽然不能通过this.refs访问到,但是可以通过将ref内容保存到无状态组件内部的一个本地变量中获取到。

例如下面这段代码可以使用ref来获取组件挂载到dom中后所指向的dom元素:

function TestComp(props){    let ref;    return (<div>        <div ref={(node) => ref = node}>            ...        </div>    </div>)}

2.3 React.createClassd的特点

React.createClass是React刚开始推荐的创建组件的方式,这是ES5的原生的JavaScript来实现的React组件,其形式如下:

var InputControlES5 = React.createClass({    propTypes: {//定义传入props中的属性各种类型        initialValue: React.PropTypes.string    },    defaultProps: { //组件默认的props对象        initialValue: ''    },    // 设置 initial state    getInitialState: function() {//组件相关的状态对象        return {            text: this.props.initialValue || 'placeholder'        };    },    handleChange: function(event) {        this.setState({ //this represents react component instance            text: event.target.value        });    },    render: function() {        return (            <div>                Type something:                <input onChange={this.handleChange} value={this.state.text} />            </div>        );    }});InputControlES6.propTypes = {    initialValue: React.PropTypes.string};InputControlES6.defaultProps = {    initialValue: ''};

与无状态组件相比,React.createClass和后面要描述的React.Component都是创建有状态的组件,这些组件是要被实例化的,并且可以访问组件的生命周期方法。但是随着React的发展,React.createClass形式自身的问题暴露出来:与无状态组件相比,React.createClass和后面要描述的React.Component都是创建有状态的组件,这些组件是要被实例化的,并且可以访问组件的生命周期方法。但是随着React的发展,React.createClass形式自身的问题暴露出来:

  • React.createClass会自绑定函数方法(不像React.Component只绑定需要关心的函数)导致不必要的性能开销,增加代码过时的可能性。
  • React.createClassmixins不够自然、直观;React.Component形式非常适合高阶组件(Higher Order Components--HOC),它以更直观的形式展示了比mixins更强大的功能,并且HOC是纯净的JavaScript,不用担心他们会被废弃。HOC可以参考无状态组件(Stateless Component) 与高阶组件。

2.3 React.createClassd的特点

React.Component是以ES6的形式来创建react的组件的,最终会取代React.createClass形式;相对于 React.createClass可以更好实现代码复用。将上面React.createClass的形式改为React.Component形式如下:

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: ''};

2.3.1 React.createClass与React.Component区别

根据上面展示代码中二者定义组件的语法格式不同之外,二者还有很多重要的区别,下面就描述一下二者的主要区别。

函数this自绑定

React.createClass创建的组件,其每一个成员函数的this都有React自动绑定,任何时候使用,直接使用this.method即可,函数中的this会被正确设置。

const Contacts = React.createClass({    handleClick() {    console.log(this); // React Component instance  },  render() {    return (      <div onClick={this.handleClick}></div>    );  }});

React.Component创建的组件,其成员函数不会自动绑定this,需要开发者手动绑定,否则this不能获取当前组件实例对象。

class Contacts extends React.Component {    constructor(props) {    super(props);  }  handleClick() {    console.log(this); // null  }  render() {    return (      <div onClick={this.handleClick}></div>    );  }

当然,React.Component有三种手动绑定方法:可以在构造函数中完成绑定,也可以在调用时使用method.bind(this)来完成绑定,还可以使用arrow function来绑定。拿上例的handleClick函数来说,其绑定可以有:

//构造函数中绑定constructor(props) {   super(props);   this.handleClick = this.handleClick.bind(this);}// 使用bind来绑定<div onClick={this.handleClick.bind(this)}></div>// 使用arrow function来绑定<div onClick={()=>this.handleClick()}></div> 

组件属性类型propTypes及其默认props属性defaultProps配置不同

eact.createClass在创建组件时,有关组件props的属性类型及组件默认的属性会作为组件实例的属性来配置,其中defaultProps是使用getDefaultProps的方法来获取默认组件属性的

const TodoItem = React.createClass({    propTypes: { // as an object        name: React.PropTypes.string    },    getDefaultProps(){   // return a object        return {            name: ''            }    }    render(){        return <div></div>    }})

React.Component在创建组件时配置这两个对应信息时,他们是作为组件类的属性,不是组件实例的属性,也就是所谓的类的静态属性来配置的。对应上面配置如下:

class TodoItem extends React.Component {    static propTypes = {//类的静态属性        name: React.PropTypes.string    };    static defaultProps = {//类的静态属性        name: ''    };    ...}

组件初始状态state的配置不同

React.createClass创建的组件,其状态state是通过getInitialState方法来配置组件相关的状态;
React.Component创建的组件,其状态state是在constructor中像初始化组件属性一样声明的。

const TodoItem = React.createClass({    // return an object    getInitialState(){         return {            isEditing: false        }    }    render(){        return <div></div>    }})
class TodoItem extends React.Component{    constructor(props){        super(props);        this.state = { // define this.state in constructor            isEditing: false        }     }    render(){        return <div></div>    }}

Mixins的支持不同

Mixins(混入)是面向对象编程OOP的一种实现,其作用是为了复用共有的代码,将共有的代码通过抽取为一个对象,然后通过Mixins进该对象来达到代码复用。具体可以参考React Mixin的前世今生。

React.createClass在创建组件时可以使用mixins属性,以数组的形式来混合类的集合。

var SomeMixin = {    doSomething() {  }};const Contacts = React.createClass({    mixins: [SomeMixin],  handleClick() {    this.doSomething(); // use mixin  },  render() {    return (      <div onClick={this.handleClick}></div>    );  }});

但是遗憾的是React.Component这种形式并不支持Mixins,至今React团队还没有给出一个该形式下的官方解决方案;但是React开发者社区提供一个全新的方式来取代Mixins,那就是Higher-Order Components,具体细节可以参考这篇文章

3 总结

  • react中有三种创建组件的形式
  • 函数式组件可以引入类组件,类组件中可以引入函数式组件
  • 纯函数不会被实例化,不能访问this,没有生命周期
  • 尽可能的使用纯函数拆分复杂型组件

4 参考文献

  • React 组件构造方法: ES5 (createClass) 还是 ES6 (class)?
  • React.createClass 对比 extends React.Component
  • 应该如何选择:React.createClass, ES6 Classes, 无状态函数式组件
  • React中函数式声明组件
  • React Mixin 的前世今生