谈一谈创建React Component的几种方式(Talk about several ways to create react component)

当我们谈起React的时候,多半会将注意力集中在组件之上,思考如何将页面划分成一个个组件,以及如何编写可复用的组件。但对于接触React不久,还没有真正用它做一个完整项目的人来说,理解如何创建一个组件也并不那么简单。在最开始的时候我以为创建组件只需要调用这个api就可以了;但学习了ES6的语法后,又知道了可以利用继承,通过来创建组件;后来在阅读别人代码的时候又发现了以及完全没有继承,仅仅通过返回JSX语句的方式创建组件的方式。下面这篇文章,就将逐一介绍这几种创建组件的方法,分析其特点,以及如何选择使用哪一种方式创建组件。

createClass
extends React.component
PureComponent

几种方法

1.createClass

如果你还没有使用ES6语法,那么定义组件,只能使用这个helper来创建组件,下面是一段示例:

React.createClass
var React = require("react");
var Greeting = React.createClass({
  
  propTypes: {
    name: React.PropTypes.string //属性校验
  },

  getDefaultProps: function() {
    return {
      name: 'Mary' //默认属性值
    };
  },
  
  getInitialState: function() {
    return {count: this.props.initialCount}; //初始化state
  },
  
  handleClick: function() {
    //用户点击事件的处理函数
  },

  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
});
module.exports = Greeting;

这段代码,包含了组件的几个关键组成部分,这种方式下,组件的props、state等都是以对象属性的方式组合在一起,其中默认属props和初始state都是返回对象的函数,propTypes则是个对象。这里还有一个值得注意的事情是,在createClass中,React对属性中的所有函数都进行了绑定,也就是如上面的其实相当于 。

this
hanleClick
handleClick.bind(this)

2.component

因为ES6对类和继承有语法级别的支持,所以用ES6创建组件的方式更加优雅,下面是示例:

import React from 'react';
class Greeting extends React.Component {

  constructor(props) {
    super(props);
    this.state = {count: props.initialCount};
    this.handleClick = this.handleClick.bind(this);
  }
  
  //static defaultProps = {
  //  name: 'Mary'  //定义defaultprops的另一种方式
  //}
  
  //static propTypes = {
    //name: React.PropTypes.string
  //}
  
  handleClick() {
    //点击事件的处理函数
  }
  
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Greeting.propTypes = {
  name: React.PropTypes.string
};

Greeting.defaultProps = {
  name: 'Mary'
};
export default Greating;

可以看到Greeting继承自React.component,在构造函数中,通过super()来调用父类的构造函数,同时我们看到组件的state是通过在构造函数中对this.state进行赋值实现,而组件的props是在类Greeting上创建的属性,如果你对和的区别有所了解的话,大概能理解为什么会这么做。对于组件来说,组件的props是父组件通过调用子组件向子组件传递的,子组件内部不应该对props进行修改,它更像是所有子组件实例共享的状态,不会因为子组件内部操作而改变,因此将props定义为类Greeting的属性更为合理,而在面向对象的语法中类的属性通常被称作静态(static)属性,这也是为什么props还可以像上面注释掉的方式来定义。对于Greeting类的一个实例对象的state,它是组件对象内部维持的状态,通过用户操作会修改这些状态,每个实例的state也可能不同,彼此间不互相影响,因此通过this.state来设置。

类的属性
对象的属性

用这种方式创建组件时,React并没有对内部的函数,进行this绑定,所以如果你想让函数在回调中保持正确的this,就要手动对需要的函数进行this绑定,如上面的handleClick,在构造函数中对this 进行了绑定。

3.PureComponet

我们知道,当组件的props或者state发生变化的时候:React会对组件当前的Props和State分别与nextProps和nextState进行比较,当发现变化时,就会对进行重新渲染,否则就不渲染。有时候为了避免组件进行不必要的重新渲染,我们通过定义来优化性能。例如如下代码:

当前组件以及子组件
shouldComponentUpdate
class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

通过判断和是否发生变化来决定需不需要重新渲染组件,当然有时候这种简单的判断,显得有些多余和样板化,于是React就提供了来自动帮我们做这件事,这样就不需要手动来写了:

shouldComponentUpdate
props.color
state.count
PureComponent
shouldComponentUpdate
class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

大多数情况下, 我们使用能够简化我们的代码,并且提高性能,但是的自动为我们添加的函数,只是对props和state进行浅比较(shadow comparison),当props或者state本身是嵌套对象或数组等时,浅比较并不能得到预期的结果,这会导致实际的props和state发生了变化,但组件却没有更新的问题,例如下面代码有一个组件来将单词数组拼接成逗号分隔的句子,它有一个父组件让你点击按钮为单词数组添加单词,但他并不能正常工作:

PureComponent
PureComponent
shouldComponentUpate
ListOfWords
WordAdder
class ListOfWords extends React.PureComponent {
  render() {
    return <div>{this.props.words.join(',')}</div>;
  }
 }
 
class WordAdder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      words: ['marklar']
    };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    // 这个地方导致了bug
    const words = this.state.words;
    words.push('marklar');
    this.setState({words: words});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick} />
        <ListOfWords words={this.state.words} />
      </div>
    );
  }
}

这种情况下,只会对this.props.words进行一次浅比较,虽然数组里面新增了元素,但是this.props.words与nextProps.words指向的仍是同一个数组,因此this.props.words !== nextProps.words 返回的便是flase,从而导致ListOfWords组件没有重新渲染,笔者之前就因为对此不太了解,而随意使用PureComponent,导致state发生变化,而视图就是不更新,调了好久找不到原因~。

PureComponent

最简单避免上述情况的方式,就是避免使用可变对象作为props和state,取而代之的是每次返回一个全新的对象,如下通过来返回新的数组:

concat
handleClick() {
  this.setState(prevState => ({
    words: prevState.words.concat(['marklar'])
  }));
}

你可以考虑使用来创建不可变对象,通过它来简化对象比较,提高性能。这里还要提到的一点是虽然这里虽然使用了这个词,但是并不是纯的,因为对于纯的函数或组件应该是没有内部状态,对于更符合纯的定义,不了解纯函数的同学,可以参见这篇文章。

Immutable.js
Pure
PureComponent
stateless component

4.Stateless Functional Component

上面我们提到的创建组件的方式,都是用来创建包含状态和用户交互的复杂组件,当组件本身只是用来展示,所有数据都是通过props传入的时候,我们便可以使用来快速创建组件。例如下面代码所示:

Stateless Functional Component
import React from 'react';
const Button = ({
  day,
  increment
}) => {
  return (
    <div>
      <button onClick={increment}>Today is {day}</button>
    </div>
  )
}

Button.propTypes = {
  day: PropTypes.string.isRequired,
  increment: PropTypes.func.isRequired,
}

这种组件,没有自身的状态,相同的props输入,必然会获得完全相同的组件展示。因为不需要关心组件的一些生命周期函数和渲染的钩子,所以不用继承自Component显得更简洁。

对比

createClass vs Component

对于 和 本质上都是用来创建组件,他们之间并没有绝对的好坏之分,只不过一个是ES5的语法,一个是ES6的语法支持,只不过createClass支持定义PureRenderMixin,这种写法官方已经不再推荐,而是建议使用PureComponent。

React.createClass
extends React.Component

pureComponent vs Component

通过上面对PureComponent和Component的介绍,你应该已经了解了二者的区别:已经定义好了而需要显示定义。

PureComponent
shouldUpdateComponent
Component

Component vs Stateless Functional component

  • Component包含内部state,而Stateless Functional Component所有数据都来自props,没有内部state;
  • Component 包含的一些生命周期函数,Stateless Functional Component都没有,因为Stateless Functional component没有shouldComponentUpdate,所以也无法控制组件的渲染,也即是说只要是收到新的props,Stateless Functional Component就会重新渲染。
  • Stateless Functional Component 不支持Refs

选哪个?

这里仅列出一些参考:

  • createClass, 除非你确实对ES6的语法一窍不通,不然的话就不要再使用这种方式定义组件。
  • Stateless Functional Component, 对于不需要内部状态,且用不到生命周期函数的组件,我们可以使用这种方式定义组件,比如展示性的列表组件,可以将列表项定义为Stateless Functional Component。
  • PureComponent/Component,对于拥有内部state,使用生命周期的函数的组件,我们可以使用二者之一,但是大部分情况下,我更推荐使用PureComponent,因为它提供了更好的性能,同时强制你使用不可变的对象,保持良好的编程习惯。
————————

When we talk about react, we mostly focus on components, thinking about how to divide the page into components and how to write reusable components. However, it is not so easy for people who have not really used react to do a complete project yet, to understand how to create a component. At the beginning, I thought that to create a component, you only need to call this API; However, after learning the syntax of ES6, I know that inheritance can be used to create components; Later, when reading other people’s code, I found that there is no inheritance at all, and the component is created only by returning JSX statements. The following article will introduce these methods of creating components one by one, analyze their characteristics, and how to choose which method to use to create components.

createClass
extends React.component
PureComponent

Several methods

1.createClass

If you haven’t used ES6 syntax yet, you can only use this helper to create components when defining components. The following is an example:

React.createClass
var React = require("react");
var Greeting = React.createClass({
  
  propTypes: {
    name: React.PropTypes.string //属性校验
  },

  getDefaultProps: function() {
    return {
      name: 'Mary' //默认属性值
    };
  },
  
  getInitialState: function() {
    return {count: this.props.initialCount}; //初始化state
  },
  
  handleClick: function() {
    //用户点击事件的处理函数
  },

  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
});
module.exports = Greeting;

This code contains several key components of the component. In this way, the props and state of the component are combined in the form of object attributes. The default props and initial state are functions that return objects, and proptypes is an object. Another noteworthy thing here is that in createclass, react binds all functions in the attribute, that is, as above, it is actually equivalent to  。

this
hanleClick
handleClick.bind(this)

2.component

Because ES6 has syntax level support for classes and inheritance, the way of creating components with ES6 is more elegant. The following is an example:

import React from 'react';
class Greeting extends React.Component {

  constructor(props) {
    super(props);
    this.state = {count: props.initialCount};
    this.handleClick = this.handleClick.bind(this);
  }
  
  //static defaultProps = {
  //  name: 'Mary'  //定义defaultprops的另一种方式
  //}
  
  //static propTypes = {
    //name: React.PropTypes.string
  //}
  
  handleClick() {
    //点击事件的处理函数
  }
  
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Greeting.propTypes = {
  name: React.PropTypes.string
};

Greeting.defaultProps = {
  name: 'Mary'
};
export default Greating;

We can see that greeting inherits from react.component. In the constructor, we call the constructor of the parent class through super (). At the same time, we can see that the state of the component is realized by assigning a value to this.state in the constructor, and the props of the component is an attribute created on the class greeting. If you know the difference between and, you can probably understand why you do so. For a component, the props of the component is passed from the parent component to the child component by calling the child component. The child component should not modify the props. It is more like the shared state of all child component instances and will not change due to the internal operation of the child component. Therefore, it is more reasonable to define props as the attribute of greeting class, In object-oriented syntax, class attributes are usually called static attributes, which is why props can also be defined in the way noted above. For the state of an instance object of the greeting class, it is the state maintained inside the component object. These States will be modified through user operations. The states of each instance may be different and do not affect each other. Therefore, it is set through this.state.

类的属性
对象的属性

When creating a component in this way, react does not bind this to the internal function, so if you want the function to maintain the correct this in the callback, you need to manually bind this to the required function, such as handleclick above, and bind this in the constructor.

3.PureComponet

We know that when the props or state of a component changes: react will compare the current props and state of the component with nextprops and nextstate respectively. When changes are found, it will re render the component, otherwise it will not be rendered. Sometimes, in order to avoid unnecessary re rendering of components, we optimize performance by definition. For example, the following code:

当前组件以及子组件
shouldComponentUpdate
class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

The need to re render components is determined by judging and whether there are changes. Of course, sometimes this simple judgment seems redundant and templated, so react provides to automatically help us do this, so we don’t need to write manually:

shouldComponentUpdate
props.color
state.count
PureComponent
shouldComponentUpdate
class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

In most cases, we use functions that can simplify our code and improve performance. However, the functions automatically added for us only make a shadow comparison between props and state. When props or state itself is a nested object or array, the shallow comparison can not get the expected results, which will lead to changes in the actual props and state, However, the component does not have the problem of updating. For example, the following code has a component to splice the word array into comma separated sentences. It has a parent component that allows you to click the button to add words to the word array, but it does not work normally:

PureComponent
PureComponent
shouldComponentUpate
ListOfWords
WordAdder
class ListOfWords extends React.PureComponent {
  render() {
    return <div>{this.props.words.join(',')}</div>;
  }
 }
 
class WordAdder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      words: ['marklar']
    };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    // 这个地方导致了bug
    const words = this.state.words;
    words.push('marklar');
    this.setState({words: words});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick} />
        <ListOfWords words={this.state.words} />
      </div>
    );
  }
}

In this case, only a shallow comparison will be made for this.props.words. Although elements are added to the array, this.props.words and nextprops.words still point to the same array, so this.props.words== Nextprops.words returns false, which causes the listofwords component not to be re rendered. I used purecomponent at will because I didn’t know much about it before, resulting in changes in the state, but the view is not updated. I can’t find the reason after adjusting it for a long time ~.

PureComponent

The simplest way to avoid the above situation is to avoid using variable objects as props and state. Instead, a new object is returned each time, as follows:

concat
handleClick() {
  this.setState(prevState => ({
    words: prevState.words.concat(['marklar'])
  }));
}

You can consider using to create immutable objects, which can simplify object comparison and improve performance. It should also be mentioned here that although this word is used here, it is not pure, because there should be no internal state for pure functions or components. For students who are more in line with the definition of pure and do not understand pure functions, please refer to this article.

Immutable.js
Pure
PureComponent
stateless component

4.Stateless Functional Component

The methods of creating components mentioned above are used to create complex components including state and user interaction. When the component itself is only used for display and all data is passed in through props, we can use it to quickly create components. For example, the following code is shown:

Stateless Functional Component
import React from 'react';
const Button = ({
  day,
  increment
}) => {
  return (
    <div>
      <button onClick={increment}>Today is {day}</button>
    </div>
  )
}

Button.propTypes = {
  day: PropTypes.string.isRequired,
  increment: PropTypes.func.isRequired,
}

This component does not have its own state. The same props input will inevitably obtain the same component display. Because you don’t need to care about some life cycle functions and rendering hooks of components, it’s more concise not to inherit from components.

contrast

createClass vs Component

about   and   In essence, they are used to create components. There is no absolute difference between them. One is the syntax of Es5 and the other is the syntax support of ES6, but createclass supports the definition of purerendermixin. This writing method is officially no longer recommended, but purecomponent is recommended.

React.createClass
extends React.Component

pureComponent vs Component

Through the above introduction to purecomponent and component, you should have understood the difference between them: it has been defined and needs to be displayed.

PureComponent
shouldUpdateComponent
Component

Component vs Stateless Functional component

  • Component包含内部state,而Stateless Functional Component所有数据都来自props,没有内部state;
  • Component 包含的一些生命周期函数,Stateless Functional Component都没有,因为Stateless Functional component没有shouldComponentUpdate,所以也无法控制组件的渲染,也即是说只要是收到新的props,Stateless Functional Component就会重新渲染。
  • Stateless Functional Component 不支持Refs

Which one?

Only some references are listed here:

  • Createclass, unless you really know nothing about the syntax of ES6, otherwise don’t use this way to define components.
  • Stateless functional component. For components that do not need internal state and do not use life cycle function, we can define components in this way, such as illustrative list components. List items can be defined as stateless functional components.
  • Purecomponent / component. For components with internal state and using life cycle functions, we can use one of the two, but in most cases, I prefer to use purecomponent because it provides better performance, forces you to use immutable objects and maintains good programming habits.