美文网首页
React数据流

React数据流

作者: kim_jin | 来源:发表于2018-12-13 11:18 被阅读0次

    React数据流

    React中,数据是自顶向下单向流动的,即从父组件到子组件。这条原则让组件之间的关系变得简单且可预测。
    statepropsReact组件中最重要的概念,如果顶层组件初始化props,那么React会向下遍历整棵组件树,重新尝试渲染所有相关的子组件。而state只关心每个组件自己内部的状态,这些状态只能在组件内改变。把组件看成一个函数,那么它接受了props作为参数,内部由state作为函数的内部参数,返回一个virtual DOM实现。
    其中react有三个非常重要的概念:statepropscontextstate其实应该被称为内部状态或是局部状态。“内部”表示它很少"跑出"组件,状态意味着它经常发生改变。Propscontext用于在组件中传递数据,props仅仅支持逐层传递数据,但是context则支持跨级传递。Statepropscontext都是react中的数据载体,他们都是各司其职,让数据在组件中优雅的变化和流动。

    state

    在使用React之前,常见的MVC框架也非常容易实现交互界面的状态管理。在MVC框架将View中与界面交互的状态解耦,一般将状态放在Model中管理。但是React没有结合Flux或是Redux框架前,React也同样可以管理组件的内部状态。在React中,把这类状态统一称为state
    当组件内部使用库内置的setState方法时,最大的表现行为就是该组件会尝试重新渲染。这很好理解,因为我们改变了内部的状态,组件需要更新了。我们编写一个计数器组件:

    import React, {Copmonent} from 'react';
    
    class Counter extends Component{
      constructor(props){
        super(props);
    
        this.handleClick = this.handleClick.bind(this);
    
        this.state ={
          count:0,
        };
      }
      handleClick(e){
        e.preventDefault();
    
        this.setState({
          count:this.state.count + 1,
        });
      }
      render(){
        return(
          <div>
            <p>{this.state.count}</p>
            <a href='#' onClick = {this.handleClick}>更新</a>
          </div>
        );
      }
    }
    

    React中常常在事件处理方法中更新state,上面的例子中就是通过点击“更新”按钮不断地更新内部cout的值,这样就可以把组件内状态封装在实现中。
    值得注意的是,setState是一个异步的方法,一个生命周期内所有的setState方法会合并操作,有了这个特性,让React变得充满想象力,我们完全可以只用React来完成行为控制、数据的更新和界面的渲染。然而,随着内容的深入,我们发现官方并不推荐开发者滥用state。因为过多的内部状态会让数据流混乱,数据变得难以维护。
    我们再看一下Tabs组件的state,我们了解到应该有两个内部状态-- activeIndexpreIndex,这两个状态分别表示当前选中tab的索引和前一次选中的tab的索引,我们需要注意的是,当前选中的索引也是组件本身需要的参数之一。
    我们针对activeIndex做为state,就有两种不同的视角:

    • activeIndex在内部更新:当我们切换tab标签时,可以看做是组件内部的交互行为,被选择后通过回调函数返回具体选择的索引。
    • activeIndex 在外部更新:当我们切换tab标签时,可以看做是组件外部在传入具体的索引,而组件就像‘木偶’一样被操作。

    这两种情况在React组件的设计非常的常见,我们形象的把第一种和第二种视角写成的组件分别称为智能组件(smart component)和木偶组件(dumb component)
    实现组件的时候,可以同时考虑兼容这两种。我们来看一下Tabs组件初始化时实现部分:

    constructor (props){
      super(props);
      const currProps = this.props;
      let activeIndex = 0;
    
      if('activeIndex' in currProps){
        activeIndex = currProps.activeIndex;
      }else if('defaultActiveIndex' in currProps){
        activeIndex = currProps.defaultActiveIndex;
      }
    
      this.state ={
        activeIndex ,
        preIndex:activeIndex ;
      }
    }
    
    props

    propsReact中的另外的一个重要概念。propsReact用来让组件之间互相联系的一种机制,通俗的说就像方法传入参数一样。
    props的传统过程,对于React组件来说是非常直观的。React的单向数据流,主要的流动管道就是propsprops本身是不可变的,当我们试图改变props的原始值的时候,React会报出类型错误的警告,组件的props一定来自于 默认属性或通过父组件传递而来。如果说要渲染一个对props加工后的值,最简单的方法就是使用局部变量或直接在JSX中计算结果。
    我们之前了解到Tabs组件的数据都是通过data prop传入的,也就是<Tabs data = {data} />。那么Tabs组件的props还会有哪些,我们看一下下面的几项:

    • className:根节点的class,为了方便覆盖其原始样式,我们都会在根节点上定义class
    • classPrefixclass前缀,对于组件来说,定义一个统一的class前缀,对样式与交互分离起了很重要的作用。
    • defaultActiveIndexactiveIndex:默认的激活索引。
    • onChange:回调函数,当我们切换tab的时候,外组件需要知道组件的内部信息,尤其是当前tab的索引号的信息,onChange一般与activeIndex搭配使用。

    Reactprops同样提供了默认配置,通过defaultProp静态变量的方式来定义。当组件被调用的时候,默认值
    保证渲染后始终有值。在render方法中,可以直接使用props的值来渲染。这里,我们只需要默认设置classPrefixonChange即可。因为defaultActiveIndexactiveIndex,我们需要保持只去其中一个条件:

    static defaultProps = {
      classPrefix : 'tabs',
      onchange:()=>{},
    };
    

    但是Tabs组件的信息全由一个对象传进来的方式真的好吗?对于React组件来说,我们考虑设计组件一定要满足一大原则-- 直观。把基本设置与数据一起定义成一个组件或对象是初学者很容易犯的错误,对于React来说,如果组件是可以分解的,那么一定要将它进行分解,使用子组件的方式来进行处理。
    我们仔细观察一下Tabs组件在web界面的特征,一般来说,主要分成两个区域:切换区域和内容区域。那么我们根据上面说的,定义两个区域:切换区域和内容区域。TabNav组件对应切换区域,TabContent组件对应内容区域。这两个区域组件都存放一个有序的数组,都可以进行进一步的拆分, 具体的两种组织方式如下:

    • Tabs组件的内部把所有定义的子组件都显示的展示出来。这么做的好处在于非常的易于理解,可以自定义的能力强,但是在调用的过程就会显得笨重。React-BootstrapMaterial UI组件库中的Tabs组件采用的就是这样的方式,我们进行调用的方式如下:
    <Tabs classPrefix = {'tabs'} defaultActiveIndex ={0}>
      <TabNav>
        <TabHead>Tab 1</TabHead>
        <TabHead>Tab 2</TabHead>
        <TabHead>Tab 3</TabHead>
      </TabNav>
      <TabContent>第一个Tab里面的内容</TabContent>
      <TabContent>第二个Tab里面的内容</TabContent>
      <TabContent>第三个Tab里面的内容</TabContent>
    </Tabs>
    
    • Tabs组件内置显示定义内容区域的子组件集合,头部区域对应内部区域的每一个TabPane组件的props,让其在TabNav组件内拼装。这种方式的调用写法比较简单,把复杂的逻辑留给了组件去实现。Ant Design组件库中的Tabs组件采用的就是这种方式。调用方式如下形式:
    <Tabs classPrefix = {'tabs'} defaultActiveIndex ={0}>
      <TabPane key ={0} tab = {'Tab 1'}>第一个Tab里面的内容</TabPane>
      <TabPane key ={1} tab = {'Tab 2'}>第二个Tab里面的内容</TabPane>
      <TabPane key ={2} tab = {'Tab 3'}>第三个Tab里面的内容</TabPane>
    </Tabs>
    

    我们通过后面的一种方法进行具体的讲述,当基本结构确定后,我们需要看一下怎么渲染这个结构的内容。显然,不能让所以的参数都由Tabs组件来承载。只有两个props放在了Tabs组件上面,而其他的参数直接放在了TabPane组件上面,由它的父组件TabContent隐式对TabPane组件的拼装。

    子组件prop

    在React中有一个重要且内置的prop-children,它代表了组件的子组件结合。children可以根据传入子组件的数量来决定是否是数组类型。我们上面调用TabPane组件的过程,翻译过来就是:

    <Tabs classPrefix = {'tabs'} defaultActiveIndex ={0} className = "tabs-bar"
      children ={[
         <TabPane key ={0} tab = {'Tab 1'}>第一个Tab里面的内容</TabPane>
         <TabPane key ={1} tab = {'Tab 2'}>第二个Tab里面的内容</TabPane>
         <TabPane key ={2} tab = {'Tab 3'}>第三个Tab里面的内容</TabPane>
      ]}
    >
    </Tabs>
    

    实现的基本思路就是以TabContent组件渲染TabPane子组件集合为例来讲,其中渲染TabPane组件的方法如下:

    getTabPanes(){
      const {classPrefix, activeIndex, panels, isActive } = this.props;
      return React.Children.map(panels, (child) =>{
        if(!child){return;}
    
        const order = parseInt(child.props.order, 10);
        const isActive = activeIndex === order;
    
        return React.cloneElement(child,{
          classPrefix,
          isActive,
          children:child.props.children,
          key:'tabPane - ${order}',
        });
     });
    }
    

    上面的代码讲述了子组件组合是怎么渲染的,通过React.Children.map方法遍历子组件将order(渲染顺序)、isActive(是否激活tab)、childrenTabs组件中传下的children)和key利用ReactcloneElement方法克隆到TabPane组件中,最后返回这个TabPane组件集合。这也是Tabs组件拼装子组件的基本原理。
    其中,React.childrenReact官方提供的一系列操作children的方法。它提供诸如mapforEachcount等实用函数,可以为我们提供子组件提供便利。
    最后,TabContent组件的render方法只需要调用getTabPanes方法就可以完成渲染:

    render(){
      return (<div>{this.getTabPanes()}</div>)
    }
    

    假如我们把render方法中的this.getTabPanes方法中对子组件的遍历直接放进去,就会变成如下的形式

    render(){
      return (<div>{React.Children.map(this.props.children, (child) => {...})}</div>);
    }
    

    这种调用方式称为Dynamic Children(动态子组件)。它指的是组件内的子组件是通过动态计算得到的。就像上述对子组件的遍历一样,我们一样可以对任何数据、字符串、数组或对象作动态计算。
    用声明式编程的方式来渲染数据,这样的做法和关心所有的细节的命令式编程相比,会让我们轻松很多,当然,除了数据的map函数。还可以用其他使用的高阶函数,如reducefilter等函数。值得注意的是,与map函数相似但不返回调用结果的forEach函数不能这么使用。

    组件props
    <TabPane key ={0} tab = {'Tab 1'}>第一个Tab里面的内容</TabPane>
    

    现在tab prop中传入的是一个字符串。但是,如果我们传入的是节点呢,是不是就可以自定义tab头展示的形式了,这就是component props。对于子组件而言,我们不仅可以直接使用this.props.children定义,也可以将子组件以props的形式传递。一般我们会用这种方法来让开发者定义组件的某一个prop,让其具备多种类型,来做到简单配置和自定义配置组合在一起的效果。
    Tabs组件中,我们就用到了这样的功能,调用当时如下所示:

    <Tabs classPrefix = {'tabs'} defaultActiveIndex ={0} className = "tabs-bar">
      <TabPane 
        order ='0' 
        tab = {<span><i className = "fa fa-home">&nbsp;Home</i></span>}>
          第一个Tab里面的内容
      </TabPane>
      <TabPane 
        order ='1' 
        tab = {<span><i className = "fa fa-book">&nbsp;Library</i></span>}>
          第二个Tab里面的内容
      </TabPane>
      <TabPane 
        order ='3' 
        tab = {<span><i className = "fa fa-home">&nbsp;Application</i></span>}>
          第三个Tab里面的内容
      </TabPane>
    <Tabs>
    

    我们也可以加入更多的自定义元素,可以是多行的,甚至可以插入动态数据。这听上去有些复杂,但是实现的过程其实非常简单,下面写在TabNav组件中简化的渲染子组件集合的方法:

    getTabs(){
      const {classPrefix, activeIndex, panels} = this.props;
      return React.Children.map(panels, (child) =>{
         if(!child){return;}
    
         const order = parseInt(child.props.order, 10);
       
         let classes = classnames ({
            [`${classPrefix} - tab `] : true,
            [`${classPrefix} - active`] : activeIndex === order,
            [`${classPrefix} - disable`] : child.props.disable,
        });
    
      return (
        <li>{child.props.tab}</li>
      );
     });
    }
    

    上面的方法和getTabPanes方法非常像,关键在于通过遍历TabPane组件的tab prop来实现我们想要的功能,不论tab是以字符串的形式还是以虚拟元素的形式存在,都可以直接在<li>标签中渲染出来。

    相关文章

      网友评论

          本文标题:React数据流

          本文链接:https://www.haomeiwen.com/subject/npwzcqtx.html