一,原始的createClass写法

对于写react组件,很多人第一印象往往是createClass,这是因为createClass是react组件最原始的写法,基本每个学react的人都是接触这种写法过来的。

createClass写法是基于es5,它实际上是React对象的一个顶层API,它只接受一个配置对象作为参数,如下:

var React = require('react');
var ReactDOM = require('react-dom');

var AppComponent = React.createClass( {这是一个react组件配置对象} );

1,下面来说说组件配置对象。

首先,这个配置对象必须有一个render()方法,并且这个render()方法必须返回由闭合标签包裹的html片段。

render()方法的作用在于渲染DOM。

如下:

var AppComponent = React.createClass({

   render:function(){
      return (
       <div> 返回值最外层必须是闭合标签  </div>
     )
   }
})

一个最为简单react组件就这样完成了。这个组件只配置了一个render函数,你还可以有更多的配置。

react组件的调用也很简单:

<div>
  <AppComponent />   // 调用react组件
</div>

最终结果如下:

<div>
  <div> 返回值最外层必须是闭合标签  </div>
</div>

2,添加组件生命周期方法

react组件本身除了render()方法以外,还有诸多生命周期方法。这些生命周期方法如下:

# 初始化阶段
getDefaultProps()  // 组件类创建的时候调用

getInitialState()   // 组件挂载之前调用, 定义state初始值

componentWillMount()   // 组件即将被装载、渲染到页面上

componentDidMount()   // 组件挂载后调用

# 组件运行阶段

componentWillReceiveProps()   // 组件将要接收到新属性的时候调用

shouldComponentUpdate()      // 组件接受到新属性或者新状态的时候调用

componentWillUpdate()   // 新属性或state 更新前调用

componentDidUpdate()    // 更新完成后调用

# 组件销毁阶段

 componentWillUnmount()  // 组件销毁之前调用

那么,这些生命周期方法是不是必须的呢?

没有一个是必须的,也就是说是否使用,使用哪些生命周期方法,全凭你自身需求来决定。

但通常你会用到下面这几个:

getInitialState()    // 定义你的初始state

componentDidMount()    // 在render()方法之后调用,也就是执行这个方法前DOM已经渲染完成,
                                         // 此时可以使用这个方法来载入数据,也就是发送ajax 请求数据

那么,这些方法是如何应用的呢?

很简单,使用方法和render()方法一样,在组件配置对象中定义即可。组件配置对象实际上就是一个组件属性和方法的集合。

接着上面的例子:

var AppComponent = React.createClass({
   getDefaultProps: function(){
      // 一些逻辑
   },
   getInitialState: function(){
      // 一些逻辑
   },
   componentWillMount: function(){
      // 一些逻辑
   },
   componentDidMount: function(){
       // 一些逻辑
   },
  componentWillReceiveProps: function(){
        // 一些逻辑
   },
  shouldComponentUpdate: function(){
      // 一些逻辑
   },

  ……

  render:function(){
      return (
       <div> 返回值最外层必须是闭合标签  </div>
     )
   }
})

至此,完成了一个略完整的组件。

为什么说是略完整?因为还没有添加组件验证器,也就是propTypes。

3,添加组件验证器propTypes

propTypes是一个对象,主要用于验证传入组件的props属性是否符合你在propTypes对象中设定的类型 ,如果不符合,组件会报错。通常在开发环境中使用。

下面我们来给组件添加验证器,在组件内配置propTypes属性即可。以上面的例子为例。

var AppComponent = React.createClass({
   propTypes: {
       data: React.PropTypes.array,     // 验证传入的data是不是数组,如果不是则会报错
    loadding: React.PropTypes.bool,  // 验证loadding是否为true
    loadData: React.PropTypes.func, // 验证loadData是否为函数类型
   },
   getDefaultProps: function(){
      // 一些逻辑
   },
   getInitialState: function(){
      // 一些逻辑
   },

  ……

  render:function(){
      return (
       <div> 返回值最外层必须是闭合标签  </div>
     )
   }
})

于是,组件验证器添加完成。

props属性是如何传入呢?如下:

<AppComponent
     data = []
     loadding = { true }
     loadData = { 函数 }
 />

所有传入的属性都可以在组件内通过this.props对象访问到。

4, 自定义方法

上面讲解了react组件自带的一些方法和属性。那么除了组件自带的,我们可不可以给组件自定义我们需要的方法呢?

答案必然是肯定的。光自带的方法是无法满足需求的,往往我们需要自定义一些方法来处理很多的逻辑。

自定义方法和组件自带方法一样,都是在组件配置对象内定义。以上面的粟子,再来举个粟子:

var AppComponent = React.createClass({
   propTypes: {
       data: React.PropTypes.array,     // 验证传入的data是不是数组,如果不是则会报错
    loadding: React.PropTypes.bool,  // 验证loadding是否为true
    loadData: React.PropTypes.func, // 验证loadData是否为函数类型
   },

   getInitialState:  function(){
      // 一些逻辑
   },
   componentDidMount: function(){
       // ajax请求
   },
   renderSubmit: function(e){    // 自定义的方法,用于处理表单提交
      e. stopPropagation();
      // 一些表单提交处理逻辑
   }
  ……

  render:function(){
      return (
       <div>
          <form method="post",action="/api/query" onSubmit={this.renderSubmit.bind(this)}>   // 调用自定义方法
  </form>
      </div>
    )
  }
})

至此,一个完整的组件基本完成。

createClass方法的劣势

现阶段已不推荐使用createClass方法来创建组件,准确地说它已经过时了。

基于es5,终究是要被淘汰的

过于臃肿,组件性能略差

二,class组件写法,也就是使用es6的写法

1,创建class组件

class写法是目前比较推荐的写法,另一种最为推荐的写法是stateless组件,后面会讲到。

这里所说的class是es6的class特性。

react巧妙地运用了这一特性。因为它有一个非常棒的API,那就是extends(继承)。

我们先来回顾下如何使用class定义一个类,如下:

class  AppComponent { }

这样就定义了一个AppComponent类。

再来回顾下,如何继承另一个类,如下:

class  AppComponent extends APP { }

这样AppComponent类就继承了APP类,AppComponent就可以访问到APP类的属性和方法。

总所周知,react有一个顶层组件,也就是React.Component,它本身封装了诸多属性和方法,那么,我们可以使用class来继承它,从而使它的属性和方法能够共享给我们自定义的一个类,从而形成一个新的组件。如下:

import React  from 'react';

 class AppComponent extends  React.Component {   // 定义一个继承于react顶层Component的新组件AppComponent

     constructor(props){
        super(props)            // 调用super父类构造函数改变this指向
     }

     //这是一个基于es6的react组件 

   render(){
       return  <div>
            返回值最外层必须是闭合标签
      </div>
   }
}

或者

 import React,{ Component }  from 'react';   // 解构

 class AppComponent extends  Component {   // 定义一个继承于react顶层Component的新组件AppComponent

    constructor(props){
        super(props)            // 调用super父类构造函数改变this指向
     }

     //这是一个基于es6的react组件 

    render(){
       return  <div>
            返回值最外层必须是闭合标签
      </div>
   }
}

# 上面两种写法同理,只是第二种使用了es6的另一个特性,对象的解构,将Component从React对象中解构出来。

至此,一个基于es6的react组件诞生了。

2,class组件与createClass组件的比较

class组件的用法和createClass组件的用法基本一致,它同样有生命周期,有验证器,同样可以自定义方法等等。

createClass组件到class组件的变迁,实际上是es5到es6的变迁,基本上是语法上的变迁和功能上的拓展。组件的本质是没有改变的,只是写法稍有不同。

下面来说说这些不同点。

创建组件的方式不同

一个是使用es5封装的createClass方法来创建,一个是使用es6的class特性来创建。

上面已经详细阐述过,这点就不过多阐述了。

定义初始state的方式不同

createClass组件是使用getInitialState方法来定义初始state的,如下:

var AppComponent = React.createClass({

     getInitialState: function(){
        return {
            loadding: false,
            isshow: false,
            data: null
            ……
      }
    } 

 })

class组件定义初始state则大不相同,可分为两种,如下:

# 第一种,构造函数内定义,这是es6的实现
class AppComponent extends  React.Component {

   constructor(props){
      super(props);     // 调用父类的构造函数,改变this指向
       this.state = {
         loadding: false,
         isshow: false,
         data: null,
         ……
      }
   }

   ……
}
# 第二种,直接定义静态属性,这是es7的实现,更为简单,实用
class AppComponent extends  React.Component {

   state = {
        loadding: false,
        isshow: false,
        data: null
       ……
   }

    ……

}

# es6规定是只有静态方法,没有静态属性的。但是es7可以定义静态属性,可通过babel转换实现。
# 要使用es7也很简单,通常安装和配置babel-preset-stage-0即可使用所有的es6/7 新特性

react组件内es5和es6的不同写法

直接举个粟子说明,以class组件为例:

# es5写法

class AppComponent extends  React.Component {

   componentDidMount: function(){

   }
   renderSubmit: function(){

  }

  ……

  render: function(){

    return <div> hello </div>
  }

}

# es6写法,有2种

// 写法一
class AppComponent extends  React.Component {

    ……

    componentDidMount(){

    }
    renderSubmit(){

   }

   ……

  render(){

   return <div> hello </div>

  }

}

/*
  提示:
  componentDidMount(){

   }
 这种写法相当于es5的:
 componentDidMount: function(){

 }

*/

# 写法二,使用箭头函数,似乎高逼格一些
class AppComponent extends  React.Component {

     …… 

    componentDidMount=()=>{

    }
    renderSubmit=()=>{

   }

   ……

  render=()=>{

     return <div> hello </div>

  }

}

# 提示: class严格来讲不是一个对象,class内定义的属性和方法并不需要用逗号',' 隔开。

三, stateless组件写法

前言

所谓stateless组件,也就是无状态组件。 这种react组件有一个特点,它没有生命周期方法,没有render方法,连state也没有,this也没有,也不需要实例化。

为什么需要这样的组件?

很多时候,从业务上考虑,我们的某些组件只用于纯UI展示,并没有涉及到生命周期,也不需要setState, 但是react组件本身依然存在生命周期方法等一大堆组件本身的设定,然而这些设定我们根本不需要用到的,它们的存在造成了资源浪费,多余,和臃肿。 另外组件实例化是需要占用内存,消耗性能的。

因此,考虑到不同业务需求,后来react增加了stateless组件的支持。

stateless组件本质是一个函数,它没有生命周期,也不需要实例化,没有this指向, 更轻盈,性能更加好。

这种组件是所有react组件中性能最好的组件类型。官方也推荐多用这种组件。

stateless组件的定义

stateless组件本质是一个带有返回值的函数,而且必须是使用闭合标签包裹的返回值。 下面来举个粟子:

const AppComponent = (props) =>{

     // 一些逻辑

     return <div>
      这是一个干净纯洁的stateless组件
     </div>
  }

这样就定义了一个常见的stateless组件。发现没,这就是一个函数。 有没有觉得非常干净,整洁,高逼格了许多?

stateless组件的使用和前面两种是一样的,直接来个粟子:

<div>
  <AppComponent />
 </div>

同样,你还可以给它加验证器:

AppComponent.propTypes = {
    data: React.PropTypes.array,     // 验证传入的data是不是数组,如果不符合则会报错
    loadding: React.PropTypes.bool,  // 验证传入的loadding是否为true
    loadData: React.PropTypes.func, // 验证传入的loadData是否为函数类型
 }

如何获取props属性?

首先来看看如何在调用组件时传入props属性:

<div>
  <AppComponent
   data = {[]}
   loadding = { true }
   loadData = { 函数 }
  />
 </div>

上面组件调用时传入了data, loadding, loadData三个props属性。这些传入的属性最终都会被收集到组件的props对象里面。

需要注意的是,stateless组件的props是通过传参传进去的,因为它本身是一个函数,没有this

而获取props属性的方式也五花八门,下面直接看个粟子:

# 第一种,在内部展开获取
 const AppComponent = (props) =>{

     const { data, loadding, loadData } = props; // 通过es6的对象解构赋值的写法,直接从props获取到
    /*
     上面等同于:
     const data = props.data;
     const loadding = props.loadding;
     const loadData = props.loadData;
    */

     ……

     return <div>
      这是一个干净纯洁的stateless组件
     </div>
  }

  # 第二种,高逼格一些,直接从参数里解构出来,如下:
  const AppComponent = ({ data, loadding, loadData }) =>{

     ……

     return <div>
      这是一个干净纯洁的stateless组件
     </div>
  }

  或者,如果参数较多,可以写得优雅一些:

  const AppComponent = ({
    data,
    loadding,
    loadData
  }) =>{

     ……

     return <div>
      这是一个干净纯洁的stateless组件
     </div>
  }

使用反向数据流来实现setState功能

stateless组件本身没有生命周期,也无法设置state,那是否意味着stateless组件不能使用setState功能呢? 答案是可以实现,不过是间接使用。实现的方式是——反向数据流。 有两个前提条件,一个是必须依赖一个父组件,二是这个父组件不是stateless组件,它有生命周期。

在实践之前,我们先来解决一个疑问,react明明是单向绑定,何来反向数据流? 的确react本身是单向绑定,是自上而下传递信息的,也就是从父组件传递给子组件,逐级向下传递。 如果要实现反向数据流,那么就意味着要实现自下而上传递信息,也就是要实现子组件向父组件传递信息。 那么,如何做到子组件向父组件传递信息?

下面是实现原理: 在父组件中定义一个方法,把这个方法传递给子组件,由子组件去触发这个方法,并且以函数传参的方式,把需要的信息传递给父组件。 这样就实现了自下而上传递的反向数据流。

那么,我们可以使用这个原理来实现stateless组件的setState功能。 下面来个例子:

# 父组件
  import ChildComponent from './ChildComponent'; // 引入子组件
  class AppComponent extends React.Component {
   state = {
     msg:null,
     content: null   // 初始状态
   }
   changeLoad(content){  // 这个方法是传递给子组件调用的,并且子组件会传递content过来
     this.setState({
      msg: '反向传递成功',
      content           // { content } 等同于 { content: content }
     })
   }
   render(){
     return <div>
      { this.state.content }
      <div>
       <ChildComponent,
        msg = { msg }
        changeLoad = { this.changeLoad }   // 调用子组件并传递msg以及changeLoad方法
       />
      </div>
     </div>
   }
 }

# 子组件
const ChildComponent =({ msg, changeLoad })=>{

  const str = "我是一段文字,子组件把我这段文字传给了它的父组件,并在父组件中展示我";
  return <div>
   { msg?msg:null }
   <button onClick = { changeLoad(str) }> 点我传递str给父组件 </button>  // 调用父组件的changeLoad方法并传递str给父组件
  </div>
}

这个例子很简单。父组件定义了msg和content两个初始state, 并且定义了一个changeLoad函数,这个函数是传递给子组件,由子组件去触发的。 子组件触发的时候传递新的content给父组件, changeLoad函数接受到新的content就会通过setState功能去更新旧的content,以及msg提示。 此时父组件就会展示子组件传递过来的content,并传递msg通知子组件。msg就是被改变的props属性。 这样不仅实现了正向传递,也实现了反向传递。

四,不同组件的最佳应用方式

写react组件,推荐使用class组件和stateless组件。createClass组件尽量少用或者不用。 下面来说说这三种组件的应用场景。

createClass

已不推荐使用,这里不再多讲。但你仍需要了解它,因为你可能会接触到一些旧项目,或者一些旧的开源项目,这些项目大都采用createClass写法。 再加上es6受限于兼容性问题而尚未普及,所以你可能接触到较多的createClass组件,你有必要去了解它。

class组件和stateless组件互为替补

这两种组件通常要搭配使用,互为替补,这是react组件最好的应用方式。

在实践前,先来讲讲两个概念,分别是——容器组件,展示组件。 也就是Container Component和 Presentational Component

所谓Container Component(容器组件),简单来讲就是它通常是作为一个父组件,它底下领着一群子组件。容器组件(父组件)的作用在于,给子组件传递数据,并且定义逻辑方法传递给子组件。 子组件不参与或少参与业务逻辑处理,它主要负责接收和展示容器组件传递过来的数据,以及调用传递过来的逻辑方法。 而这里所说的子组件,通常就是展示型组件,也就是Presentational Component。

这里为什么说class组件和stateless组件搭配使用,互为替补,是react组件的最好应用方式呢?

首先,stateless组件没有生命周期,无实例化,性能最好。而展示组件通常只需要做数据展示,和逻辑方法调用,它并不需要使用到生命周期方法。 这不正和stateless组件最为契合吗? 于是,stateless组件,通常用作于展示组件。

再来说说class组件,它有生命周期,再搭配es6/7语法,它可以处理众多复杂的业务逻辑。 而容器组件通常只专注于处理业务逻辑,需要使用生命周期,对于数据的展示则交给展示组件。这正和class组件最为契合。于是,通常class组件作为容器组件。

这两种组件,各自分工,互为替补,是react组件最好的应用方式。

下面是一个例子:

# 容器组件
// /containers/index.js

import ListTableComponent from '/components/ListTable';
import ItemTableComponent from '/components/ItemTable';

class AppComponent extends React.Component {

   state = {
        loadding: false
   }

render =()=>{

   const { list, item } = this.props;  

   const handdleLoad = ()=>{

     // ToDo
  }

   const listProps = { list, handdleLoad }; 

   const  itemProps = { item, handdleLoad }; 

   return <div className="box">

      <ListTableComponent       // 子组件
       { ...listProps }                // ‘...’ 是es7的展开属性运算符,
      />

      <ItemTableComponent    // 子组件
       { ...itemProps }
     />

   </div>
 }
}

# 展示组件一
//  /components/ListTable.js

const ListTable = ({ list,handdleLoad })=>{

   return <div>

           ……
           <td> {list.coutry} </td>
           <td> {list.address} </td>
          ……

      <button onClick={ handdleLoad() }> 做一些事情 </button>
    </div>
}

# 展示组件二
//  /components/ItemTable.js

const ItemTable = ({ item,handdleLoad })=>{

   return <div>

           ……
           <td> {item.name} </td>
           <td> {item.age} </td>
          ……

      <button onClick={ handdleLoad() }> 做一些事情 </button>
    </div>
}

这个例子中,容器组件index.js载入了两个子组件,这两个是展示组件,容器组件定义了handdleLoad方法,并从props拿到数据list和item两个数据源,再组织成listProps和itemProps两个props属性对象,并把他们分别传给ListTable和ItemTable两个展示组件。 这两个展示组件从props中拿到传过来的数据,并在render方法中展示出来,并不需要处理过多业务逻辑。

总结

react组件的演进,依赖于js语法的演进,随着es6/7的到来,react组件的写法变得多样,且更为高效便捷。 因此,多结合es6/7语法,多使用stateless组件,可使你的react应用更上一层楼。

react - 解刨组件的多种写法的更多相关文章

  1. React之智能组件和木偶组件

    智能组件 VS 木偶组件 在 React + Redux 结合作为前端框架的时候,提出了一个将组件分为“智能”和“木偶”两种 智能组件:它是数据的所有者,它拥有数据.且拥有操作数据的action,但是 ...

  2. React jQuery公用组件开发模式及实现

    目前较为流行的react确实有很多优点,例如虚拟dom,单向数据流状态机的思想.还有可复用组件化的思想等等.加上搭配jsx语法和es6,适应之后开发确实快捷很多,值得大家去一试.其实组件化的思想一直在 ...

  3. React笔记:组件(3)

    1. 组件定义 组件是React的核心概念,组件将应用的UI拆分成独立的.可复用的模块. 定义组件的两种方式: (1)类组件:使用ES6 class (2)函数组件:使用函数 使用class定义组件的 ...

  4. 当初要是看了这篇,React高阶组件早会了

    当初要是看了这篇,React高阶组件早会了. 概况: 什么是高阶组件? 高阶部件是一种用于复用组件逻辑的高级技术,它并不是 React API的一部分,而是从React 演化而来的一种模式. 具体地说 ...

  5. react高阶组件

    高阶组件 为了提高组件复用性,在react中就有了HOC(Higher-Order Component)的概念.所谓的高阶组件,其本质依旧是组件,只是它返回另外一个组件,产生新的组件可以对属性进行包装 ...

  6. React 高阶组件浅析

    高阶组件的这种写法的诞生来自于社区的实践,目的是解决一些交叉问题(Cross-Cutting Concerns).而最早时候 React 官方给出的解决方案是使用 mixin .而 React 也在官 ...

  7. React高阶组件 和 Render Props

    高阶组件 本质 本质是函数,将组件作为接收参数,返回一个新的组件.HOC本身不是React API,是一种基于React组合的特而形成的设计模式. 解决的问题(作用) 一句话概括:功能的复用,减少代码 ...

  8. 封装 React Native 原生组件(iOS / Android)

    封装 React Native 原生组件(iOS / Android) 在 React Native中,有很多种丰富的组件了,例如 ScrollView.FlatList.SectionList.Bu ...

  9. React Native 之 组件化开发

    前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...

随机推荐

  1. Git操作简介

    一 概述 1.什么是Git? Git是分布式版本控制系统. 2.集中式与分布式对比 在集中式版本控制系统中,版本库集中在中央服务器上,每次工作时都需要先从中央服务器获取最新版本,修改后,再推送到中央服 ...

  2. [leetcode-494-Target Sum]

    You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symb ...

  3. USACO hamming

    考试周终于过去了一半,可以继续写USACO了. 先来看一下题目吧. Hamming CodesRob Kolstad Given N, B, and D: Find a set of N codewo ...

  4. android studio gradle 两种更新方法更新

    android studio gradle 两种更新方法更新 第一种.Android studio更新 第一步:在你所在项目文件夹下:你项目根目录gradlewrappergradle-wrapper ...

  5. 微信小程序知识总结及案例集锦

    微信小程序知识总结及案例集锦 微信小程序的发展会和微信公众号一样,在某个时间点爆发 学习路径 微信小程序最好的教程肯定是官方的文档啦,点击这里直达 微信官方文档 认真跟着文档看一遍,相信有vue前端经 ...

  6. Win7使用USB口连接H3C交换机的Console口

    使用Console线的一端连接交换机的Console口,另一端连接电脑的USB口. 使用驱动精灵安装USB转串口驱动,我电脑上面提示安装的是: Prolific PL2303 USB转串口驱动1.16 ...

  7. Mybatis(七) mybatis的逆向工程的配置详解

    还是觉得看书学习有意思~嘿嘿.今天把mybatis给结束掉. --WH 一.什么是逆向工程? 简单点说,就是通过数据库中的单表,自动生成java代码. Mybatis官方提供了逆向工程,可以针对单表自 ...

  8. Vuejs——Vue生命周期,数据,手动挂载,指令,过滤器

    版权声明:出处http://blog.csdn.net/qq20004604   目录(?)[+]   原教程: http://cn.vuejs.org/guide/instance.html htt ...

  9. Vsftpd3.0--FTP服务器搭建之本地用户篇

    Vsftpd3.0--FTP服务器搭建之本地用户篇 年4月10日 19:23 FTP服务在工作中是经用到的一种工具,可以实现上传下载等功能.那么今天我们来聊一聊FTP服务器使用本地用户登录的实现模式. ...

  10. 防止js全局变量污染方法总结

    javaScript 可以随意定义保存所有应用资源的全局变量.但全局变量可以削弱程序灵活性,增大了模块之间的耦合性.在多人协作时,如果定义过多的全局变量 有可能造成全局变量冲突,也就是全局变量污染问题 ...