您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

React 数据流,React Data Flow

React 数据流(React Data Flow)

Data Flow 只是一种应用架构的方式,比如数据如何存放,如何更改数据,如何数据更改等等,所以它不是 React 提供的额外的什么新,可以看成是使用 React 构建大型应用的一种最佳实践。

正因为它是这样一种概念,所以涌现了,这里主要关注两种实现:

官方的 

更优雅的 

在React中,数据是自顶向下单向流动的,即从父组件到子组件。这条原则让组件之关系变得简单且可预测。

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

state

在使用React之前,常见的MVC框架也非常容易实现交互界面的状态管理。在MVC框架将View中与界面交互的状态解耦,一般将状态放在Model中管理。但是React没有结合Flux或是R框架前,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,我们了解到应该有两个内部状态-- activeIndex和preIndex,这两个状态分别表示当前选中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

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

className:根节点的class,为了方便覆盖其原始样式,我们都会在根节点上定义class。

classPrefix:class前缀,对于组件来说,定义统一的class前缀,对样式与交互分离起了很重要的作用。

defaultActiveIndex和activeIndex:认的激活索引。

onChange:回调,当我们切换tab的时候,外组件需要知道组件的内部信息,尤其是当前tab的索引号的信息,onChange一般与activeIndex搭配使用。

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

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

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

在Tabs组件的内部把所有定义的子组件都的展示出来。这么做的好处在于非常的易于理解,可以的能力强,但是在的过程就会显得笨重。React-Bootstrap和Material 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)、children(Tabs组件中传下的children)和key利用React的cloneElement克隆到TabPane组件中,最后返回这个TabPane组件集合。这也是Tabs组件拼装子组件的基本原理。
其中,React.children是React官方提供的一系列操作children的。它提供诸如map、forEach、count等实用,可以为我们提供子组件提供便利。
最后,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。还可以用其他使用的高阶,如reduce、filter等。值得注意的是,与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>中渲染出来。


联系我
置顶