React组件详解

react的组件化

react的一个组件很明显的由dom视图和state数据组成,两个部分泾渭分明。state是数据中心,它的状态决定着视图的状态。这时候发现似乎和我们一直推崇的MVC开发模式有点区别,没了Controller控制器,那用户交互怎么处理,数据变化谁来管理?然而这并不是react所要关心的事情,它只负责ui的渲染。与其他框架监听数据动态改变dom不同,react采用setState来控制视图的更新。setState会自动调用render函数,触发视图的重新渲染,如果仅仅只是state数据的变化而没有调用setState,并不会触发更新。 组件就是拥有独立功能的视图模块,许多小的组件组成一个大的组件,整个页面就是由一个个组件组合而成。它的好处是利于重复利用和维护。

react的 Diff算法

react的diff算法用在什么地方呢?当组件更新的时候,react会创建一个新的虚拟dom树并且会和之前储存的dom树进行比较,这个比较多过程就用到了diff算法,所以组件初始化的时候是用不到的。react提出了一种假设,相同的组件具有类似的结构,而不同的组件具有不同的结构。在这种假设之上进行逐层的比较,如果发现对应的节点是不同的,那就直接删除旧的节点以及它所包含的所有子节点然后替换成新的节点。如果是相同的节点,则只进行属性的更改。
对于列表的diff算法稍有不同,因为列表通常具有相同的结构,在对列表节点进行删除,插入,排序的时候,单个节点的整体操作远比一个个对比一个个替换要好得多,所以在创建列表的时候需要设置key值,这样react才能分清谁是谁。当然不写key值也可以,但这样通常会报出警告,通知我们加上key值以提高react的性能。

组件作为React的核心内容,是View的重要组成部分,每一个View页面都由一个或多个组件构成

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

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

一、Props: props是实现组件组合的关键。props是React的一种从父组件向子组件传输数据的机制。他们在子组件中是不能被修改的;props由父组件传输出去,也被父组件缩“拥有”。

二、state: React的组件可以在this.state里保存可变的数据

 

一、组件的props


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

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

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()方法来设置。例如:

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作为父子组件沟通的桥梁,为组件的通信和传值提供了重要手段,下面是一个父子组件传值的实例。

//子组件
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属性即可。例如:

//父组件
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的控制台会给出警告提示。

二、组件的state


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

constructor(props) {
   super(props);
   this.state={
      key:value,
      ...
   }
}


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

this.setState({
     key:value
 }) ;


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

this.state = {
  title : 'React',
  content : 'React is an wonderful JS library!'
}


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

this.setState({title: 'React Native'});


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

{
  title : 'React Native ',
  content : 'React is an wonderful JS library!'
}


三、组件的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作为参数。

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}的方式来获取组件实例。例如:

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,因为它们不能获取组件的实例。例如,下面的实例是错误的:

function InputComponent() {
  …
  return <input />;
}

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


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

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节点外,还可以实现多个组件跨层级调用。
 

四、react-router路由

Router和Route就是React的一个组件,它并不会被渲染,只是一个创建内部路由规则的配置对象,根据匹配的路由地址展现相应的组件。Route则对路由地址和组件进行绑定,Route具有嵌套功能,表示路由地址的包涵关系,这和组件之间的嵌套并没有直接联系。Route可以向绑定的组件传递7个属性:children,history,location,params,route,routeParams,routes,每个属性都包涵路由的相关的信息。比较常用的有children(以路由的包涵关系为区分的组件),location(包括地址,参数,地址切换方式,key值,hash值)。react-router提供Link标签,这只是对a标签的封装,值得注意的是,点击链接进行的跳转并不是默认的方式,react-router阻止了a标签的默认行为并用pushState进行hash值的转变。切换页面的过程是在点击Link标签或者后退前进按钮时,会先发生url地址的转变,Router监听到地址的改变根据Route的path属性匹配到对应的组件,将state值改成对应的组件并调用setState触发render函数重新渲染dom。

当页面比较多时,项目就会变得越来越大,尤其对于单页面应用来说,初次渲染的速度就会很慢,这时候就需要按需加载,只有切换到页面的时候才去加载对应的js文件。react配合webpack进行按需加载的方法很简单,Route的component改为getComponent,组件用require.ensure的方式获取,并在webpack中配置chunkFilename。

 

 

 

 

 

 

 

 

 

 

 

 

 

参考博客:

React组件详解:https://blog.csdn.net/xiangzhihong8/article/details/81124363

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值