前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React组件详解

React组件详解

作者头像
xiangzhihong
发布2022-11-30 11:39:40
1.5K0
发布2022-11-30 11:39:40
举报
文章被收录于专栏:向治洪向治洪

3.6.1 React组件简介

众所周知,组件作为React的核心内容,是View的重要组成部分,每一个View页面都由一个或多个组件构成,可以说组件是React应用程序的基石。在React的组件构成中,按照状态来分可以分为有状态组件和无状态组件。 所谓无状态组件,就是没有状态控制的组件,只做纯静态展示的作用,无状态组件是最基本的组件形式,它由属性props和渲染函数render构成。由于不涉及到状态的更新,所以这种组件的复用性也最强。 有状态组件是在无状态组件的基础上增加了组件内部状态管理,有状态组件通常会带有生命周期lifecycle,用以在不同的时刻触发状态的更新,有状态组件被大量用在业务逻辑开发中。

目前,React支持三种方式来定义一个组件,分别是: - ES5的React.createClass方式; - ES6的React.Component方式; - 无状态的函数组件方式。

在ES6出现之前,React使用React.createClass方式来创建一个组件类,它接受一个对象作为参数,对象中必须声明一个render方法,render函数返回一个组件实例。例如:

代码语言:javascript
复制
import React from 'react';

const TextView = React.createClass({  
//初始化组件状态
getInitialState () {
    return {
    };
  },

  render() {
    return (
      <div>我是一个Text</div>
    );
  }
});

export default TextView; 

不过,随着React版本的持续升级,ES5的React.createClass方式暴露的问题也越来越多。例如,使用React.createClass创建的组件,事件函数会自动绑定相关的函数,这样会导致不必要的性能开销,而React.Component则是有选择性的绑定有需要函数。 随着ES6语法的普及,React.createClass正逐渐被React.Component方式所替代。并且,使用React.Component方式创建的组件更符合面向函数编程的思想,可读性也更高。例如,下面是使用React.Component方式创建TextView的实例。

代码语言:javascript
复制
import React,{Component} from 'react'

class TextView extends Component {
//初始化组件状态
constructor(props) {
    super(props);    //传递props给component
    this.state = {
    };
  }

    render() {
        return (
            <div>我是一个Text</div>
        );
    }
}

export default TextView;

通过React.createClass和React.Component方式创建的组件都是有状态的组件,而无状态组件则是通过无状态的函数创建的。无状态组件是React在0.14版本推出的一种新的组件形式,它是一种只负责展示的纯组件。 在React开发中,随着应用复杂度的不断提升和组件数量的增加,组件的管理和维护成为不得不面对的问题,于是一种只负责展示的纯组件出现了。无状态组件的特点是不需要管理组件状态state,数据直接通过props传入即可,这也符合React单向数据流的思想。 对于无状态组件的函数式声明方式,不仅可以提高代码的可阅读性,还能大大的减少代码量,提高代码的复用率,箭头函数则是函数式编程的最佳搭档。

代码语言:javascript
复制
const Todo = (props) => (
  <li
    onClick={props.onClick}
    style={{textDecoration: props.complete ? "line-through" : "none"}}>
    {props.text}
  </li>
)

对于上面定义的Todo组件,输入输出数据完全由props决定,如果props为Object类型时,还可以使用ES6提供的解构赋值。例如:

代码语言:javascript
复制
const Todo = ({ onClick, complete, text, ...props }) => (
  <li
    onClick={onClick}
    style={{textDecoration: complete ? "line-through" : "none"}}
    {...props}
  >
    {props.text}
  </li>
)

无状态组件一般会搭配高阶组件(简称OHC)一起使用,高阶组件主要用来托管State,Redux框架就是通过store来管理数据源和组件的所有状态,其中所有负责展示的组件都使用无状态函数式的写法,无状态组件也被大规模的使用在大型应用程序中。 虽然,无状态组件具有诸多的优势,但也不是万能的。比如,无状态组件在被React调用之前,组件是不会被实例化的,所以它不支持ref特性。

3.6.2 ES5与ES6组件对比

相比React.createClass方式,React.Component带来了诸多语法上的改进: 1. import ES6使用import方式替代ES5的require方式来导入模块,其中import { }可以直接从模块中导入变量名,此种写法更加简洁直观。 2. 初始化state 在ES6的语法规则中,React的组件使用的类继承的方式来实现,去掉了ES5的getInitialState的hook函数,state的初始化则放在constructor构造函数中声明。 3. this绑定 使用React.createClass方式创建的组件,事件函数会自动绑定this函数,但是此种方式会带来不必要的性能开销,增加了代码过时的可能性。而使用React.Component方式创建组件时,事件函数并不会自动绑定this函数,需要开发者手动绑定,从而减少了不必要的性能开销。 4. 默认属性和状态 使用React.createClass方式创建组件时,有关组件props的属性类型及组件默认属性会作为组件实例的属性进行配置,其中defaultProps可以使用组件的getDefaultProps方法来获取。例如:

代码语言:javascript
复制
const Demo = React.createClass({
    propTypes: { 
        name: React.PropTypes.string
    },
    getDefaultProps(){   
        return {
            name: ' '    
        }
    }
    …
})

而使用React.Component方式创建组件时,配置组件的属性时,是通过类的静态属性来配置的。例如:

代码语言:javascript
复制
class Demo extends React.Component {
//类的静态属性
    static propTypes = {   
        name: React.PropTypes.string
    };
    //类的静态属性
    static defaultProps = {
        name: ' '
    };
    ...
}

同时,React.createClass方式创建的组件,组件的状态通过getInitialState方法来进行配置。而React.Component方式创建的组件,其状态state则是在constructor函数中像初始化组件属性一样进行声明的。

3.6.3 组件的props

React组件化的开发思路一直为人所称道,而组件最核心的两个概念莫过于props与state,组件的最终呈现效果正是props和state作用的结果。其中,props是组件对外的接口,而state则是组件对内的接口。一般情况下,props是不变的,其基本的使用方法如下。

代码语言:javascript
复制
{this.props.key}

在典型的React数据流模型中,props是父子组件交互的唯一方式,下面的例子演示了如何在组件中使用props。

代码语言:javascript
复制
class HelloMessage extends Component{
    constructor(props){
        super(props);
        this.state = {
            name: 'jack'
        }
    }

    render(){
        return (
            <h1> Hello {this.props.name}</h1>
        )
    }
}
export default Message;

在上面的例子中,通过构造函数为属性设置初始值,当然也可以不设置初始值,当需要使用name属性的时候可以通过{this.props.name}方式获取。在ES5语法中,如果想要为组件的属性设置默认值,需要通过getDefaultProps()方法来设置。例如:

代码语言:javascript
复制
var HelloMessage = React.createClass({
  //设置初始值
  getDefaultProps: function() {
    return {
      name: 'jack'
    };
  },
  render: function() {
    return <h1>Hello {this.props.name}</h1>;
  }
});

ReactDOM.render(
  <HelloMessage />,
  document.getElementById('example')
);

props作为父子组件沟通的桥梁,为组件的通信和传值提供了重要手段,下面是一个父子组件传值的实例。

代码语言:javascript
复制
//子组件
export default class Child extends Component {

    constructor(props){
        super(props);
        this.state={
            counter:props.age||0
        }
    }

    render() {
        return (
            <h1>Hello {this.props.name}</h1>
        )
    }
}

Child.propTypes={
    name:PropTypes.string.isRequired,
    age:PropTypes.number
}

Child.defaultProps={
    age:0
}

当父组件需要向子组件传递值时,只需要引入子组件,然后使用组件提供的props属性即可。例如:

代码语言:javascript
复制
//父组件
export default class Father extends Component {

    render() {
        return (
            <div>
                <Child name="jack" age={30}/>
                <Child name="tom" age={20}/>
            </div>
        )
    }
}

在上面的实例中,子组件props接受的数据格式由PropTypes进行检测,并且使用isRequired关键字来标识该属性是否是必须的。props使用PropTypes来保证传递数据的类型和格式,当向props传入无效数据时,JavaScript的控制台会给出警告提示。

3.6.4 组件的state

如果说props是组件对外的接口,那么state则是组件对内的接口,state作为组件的私有属性,只能被本组件去访问和修改。而props对于使用它的组件来说是只读的,如果想要修改props,只能通过组件的父组件修改。 React把组件看成是一个特殊的状态机,通过与用户的交互实现不同状态,进而渲染界面,让用户界面和数据保持一致。在React中,如果需要使用state,就需要在组件的constructor初始化相关的state。例如:

代码语言:javascript
复制
constructor(props) {
   super(props);
   this.state={
      key:value,
      ...
   }
}

如果要更新组件的state,则需要调用setState方法。

代码语言:javascript
复制
this.setState({
     key:value
 }) ;

需要注意的是,在调用setState函数执行更新操作时,组件的state并不会立即改变,因为setState()是异步的。setState操作只是把要修改的状态放入一个队列中,出于性能原因,React可能会对多次的setState状态修改进行合并修正,所以当我们使用{this.state}获取状态state时,可能并不是我们需要的那个state。同理,也不能依赖当前的props来计算组件的下一个状态,因为props一般也是从父组件的State中获取,依然无法确定组件在状态更新时的值。 同时,在调用setState修改组件状态时,只需要传入需要改变的状态变量即可,而不必传入组件完整的state,因为组件state的更新是一个浅合并的过程。例如,一个组件的state由title和content构成。

代码语言:javascript
复制
this.state = {
  title : 'React',
  content : 'React is an wonderful JS library!'
}

当需要修改title的状态时,只需要调用setState()修改title的内容即可。例如:

代码语言:javascript
复制
this.setState({title: 'React Native'});

由于state的更新是一个浅合并的过程,所以合并后的state只会修改新的title到state中,同时保留content的原有状态。合并后的内容如下:

代码语言:javascript
复制
{
  title : 'React Native ',
  content : 'React is an wonderful JS library!'
}

3.6.5 组件的ref

在React典型的数据流模型中,props作为父子组件交互的最基本也是最重要的方式,主要通过传递props值来使子组件重新render,从而达到父子组件通信的目的。当然,在某些特殊的情况下修改子组件的时候就需要是要另一种方式(例如和第三方库的DOM整合或者某个DOM元素focus问题上),即是ref方式。 React提供的ref属性,其本质就是调用ReactDOM.render()返回的组件实例,用来表示为对组件真正实例的引用。具体使用时,可以将它绑定到组件的render()上,然后就可以用它输出组件的实例。 ref不仅可以挂载到组件上,还可以作用于DOM元素上。具体来说,挂载组件使用class定义,表示对组件实例的引用,此时不能在函数式组件上使用ref属性,因为它们不能获取组件的实例。而挂载到DOM元素时则表示具体的DOM元素节点。 ref支持两种调用方式:一种是设置回调函数,另一种是字符串的方式。其中,设置回调函数是官方的推荐方式,使用它可以更细致的控制refs,使用此种方式,ref属性接受一个回调函数,它在组件被加载或者卸载时被立即执行。具体来说,当给HTML元素添加ref属性时,Refs回调接受底层的Dom元素作为参数,当组件卸载时Refs回调会接受null作为参数。

代码语言:javascript
复制
class Demo extends React.Component{
  constructor(props) {
    super(props);
    this.state = {           
      isInputshow:false    //控制input是否渲染
    }
  }

  inputRefcb(instance){
    if(instance) {                            
      instance.focus();                     
    }
  }

  render() {
   {
      this.state.isInputshow ? 
      <div>
        <input ref={this.inputRefcb} type="text" />
      </div>
      : null           
    }
  }
}

对于上面的例子,触发回调的时机主要有以下三种情况:

  • 组件被渲染后,回调参数instance作为input的组件实例的引用,回调参数可以立即使用该组件;
  • 组件被卸载后,回调参数instance此时为null,这样做可以确保内存不被泄露;
  • ref属性本身发生改变,原有的ref会再次被调用,此时回调参数instance变成具体的组件实例。

如果在使用String方式,则可以通过{this.refs.inputRef}的方式来获取组件实例。例如:

代码语言:javascript
复制
class Demo extends React.Component{
  constructor(props) {
    super(props);

    onFocus(){
     this.refs.inputRef.focus()
    }
  }
  render() {
    <div>
      <input ref="inputRef" type="text" />
    </div>
    <input type="button" value="Focus" onClick={this.onFocus} />
  }
}

同时,官方明确申明不能在函数式声明组件中使用ref,因为它们不能获取组件的实例。例如,下面的实例是错误的:

代码语言:javascript
复制
function InputComponent() {
  …
  return <input />;
}

class Demo extends React.Component {
  render() {
    // 编译不通过
    return (
      < InputComponent
        ref={(input) => { this.textInput = input; }} />
    );
  }
}

在某些情况下,可能需要从父组件中访问子组件的DOM节点,那么可以在子组件中暴露一个特殊的属性给父组件调用,子组件接收一个函数作为prop属性,同时将这个函数赋予到DOM节点作为ref属性,那么父组件就可以将它的ref回调传递给子级组件的DOM。这种方式对于class声明的组件和函数式声明的组件都是适用的。例如:

代码语言:javascript
复制
function TextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Father extends React.Component {
  render() {
    return (
//子组件传入inputRef函数
      < TextInput  inputRef={e => this.inputElement = e}  />  
    );
  }
}

在上面的例子中,父组件Father将他的ref回调函数通过inputRef属性传递给TextInput,而TextInput将这个回调函数作为input元素的ref属性,此时父组件Father中通过{this.inputElement}得到子组件的input对应的DOM元素。暴露DOM的ref属性除了可以方便在父组件中访问子组件的DOM节点外,还可以实现多个组件跨层级调用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-07-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 3.6.1 React组件简介
  • 3.6.2 ES5与ES6组件对比
  • 3.6.3 组件的props
  • 3.6.4 组件的state
  • 3.6.5 组件的ref
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档