灵活创建 React Component

React 通过创建组件实现功能,而创建组件的方法有很多。
今天分享一下几种方法及其优缺点。

1.React.createClass()

早期(0.x命名时代)React官网给出的Demo中用到的方法。首先createClass是React提供的工具,如:

var React = require("react");
var Greetings = React.createClass({

  propTypes: {
    name: React.PropTypes.string
  },

  getDefaultProps: function() {
    return {
      name: 'Babytree-inc.com'
    };
  },

  getInitialState: function() {
    return {count: this.props.initialCount};
  },

  handleClick: function() {
    //点击事件的处理函数
  },

  render: function() {
    return <h1>This is {this.props.name}</h1>;
  }

});
module.exports = Greetings;

这段代码主要遵循的是ES5语法。
其中组件的props、state等以对象属性的方式陈列,其中默认属props和初始state都是返回对象的函数,propTypes则是个对象。
特别地,在createClass中,React对属性中的所有函数都进行了this绑定,以上handleClick相当于handleClick.bind(this) 。

2.Class 方法

利用ES6类和继承相关的语法来创建组件比前者优雅许多。


import React from 'react'; class Greetings extends React.Component { constructor(props) { super(props); this.state = {count: props.initialCount}; this.handleClick = this.handleClick.bind(this); } //static defaultProps = { // name: 'Babytree-inc.com' //定义defaultprops的另一种方式 //} //static propTypes = { //name: React.PropTypes.string //} handleClick() { //点击事件的处理函数 } render() { return <h1>Hello, {this.props.name}</h1>; } } Greetings.propTypes = { name: React.PropTypes.string }; Greetings.defaultProps = { name: 'Babytree-inc.com' }; export default Greating;

这种方法中,Greetings 继承 React.component。
在构造函数中,通过super()来调用父类的构造函数,同时我们看到组件的state是通过在构造函数中对this.state进行赋值实现。而组件的props是在类Greetings上创建的属性(类属性与对象属性是有区别的,我找了相关参考链接进行说明)。
对于组件来说,组件的props是父组件通过调用子组件向子组件传递的,子组件内部不应该对props进行修改,它更像是所有子组件实例共享的状态,不会因为子组件内部操作而改变,因此将props定义为类Greetings的属性更为合理,而在面向对象的语法中类的属性通常被称作静态(static)属性,
对于Greetings类的一个实例对象的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>
    );
  }
}

shouldComponentUpdate通过判断props.color和state.count是否发生变化来决定需不需要重新渲染组件。除了以上方法,React还有更简单定义PureComponent的方式:

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>
    );
  }
}

PureComponent在某种场景下可以简化代码并提高性能,但使用时需要注意,PureComponent包含了shouldComponentUpate函数的功能,只对props和state进行浅比较(shadow comparison),当props或者state本身是嵌套对象或数组等时,浅比较并不能得到预期的结果,这会导致实际的props和state发生了变化,但组件却没有更新的问题。

    // 比如初始化state
    this.state = {
        data: ['Baby']
    }
    // 如果此时在handler中执行
    handleClick() {
        // push只能引发浅比较
        const data = this.state.data;
        words.push('Tree');
        this.setState({data: data});
    }

以上操作,PureComponent只会对this.props.data进行一次浅比较,虽然数组里面新增了元素,但是this.props.data与nextProps.data指向的仍是同一个数组,因此this.props.data !== nextProps.data 返回的便是flase,从而导致组件没有重新渲染。

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

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

4.无状态函数组件(Stateless Functional Component)

以上创建组件的方式,更适合用来创建包含状态及复杂用户交互的组件。
如果组件本身仅仅用来展示内容,所有数据都是通过props传入的时候,使用无状态函数创建组件。
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显得更简洁。

综上

使用React.createClass()来创建组件已经是out-of-date的做法。使用Class定义组件将其取而代之。
PureComponent内置shouldUpdateComponent的处理方式,在效能方面有所提高,但记得这是利用了浅比较机制。
Component包含内部状态state,而Stateless Functional Component所有数据都来自props,没有内部状态state;
Component 包含的一些生命周期函数,Stateless Functional Component都没有,因为Stateless Functional component没有shouldComponentUpdate,所以也无法控制组件的渲染,也即是说只要是收到新的props,Stateless Functional Component才会重新渲染。
Stateless Functional Component 不支持Refs(关于Refs参考)。

所以,定义组件的时候,根据对内部状态和生命周期的考量,选择对于的方法。
– 对于不需要内部状态,且用不到生命周期函数的组件,推荐使用Stateless Functional Component,比如展示性的列表、富文本组件。
– PureComponent/Component,都拥有内部state,PureComponent提供了更好的性能,同时强制使用不可变对象,这是编程方法所推崇的,关于React中的应用可以参加这个内容链接

对于具体的业务需求,涉及组件设计可以遵循以上原则,多体会,才能有更深入的认识。

发表评论

电子邮件地址不会被公开。 必填项已用*标注