React中类组件和函数式组件

12次阅读

共计 6136 个字符,预计需要花费 16 分钟才能阅读完成。

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 的前世今生
正文完
 0