React數(shù)據(jù)流

React數(shù)據(jù)流

React中,數(shù)據(jù)是自頂向下單向流動的,即從父組件到子組件。這條原則讓組件之間的關(guān)系變得簡單且可預(yù)測。
statepropsReact組件中最重要的概念,如果頂層組件初始化props,那么React會向下遍歷整棵組件樹,重新嘗試渲染所有相關(guān)的子組件。而state只關(guān)心每個組件自己內(nèi)部的狀態(tài),這些狀態(tài)只能在組件內(nèi)改變。把組件看成一個函數(shù),那么它接受了props作為參數(shù),內(nèi)部由state作為函數(shù)的內(nèi)部參數(shù),返回一個virtual DOM實現(xiàn)。
其中react有三個非常重要的概念:state、propscontextstate其實應(yīng)該被稱為內(nèi)部狀態(tài)或是局部狀態(tài)?!皟?nèi)部”表示它很少"跑出"組件,狀態(tài)意味著它經(jīng)常發(fā)生改變。Propscontext用于在組件中傳遞數(shù)據(jù),props僅僅支持逐層傳遞數(shù)據(jù),但是context則支持跨級傳遞。State、propscontext都是react中的數(shù)據(jù)載體,他們都是各司其職,讓數(shù)據(jù)在組件中優(yōu)雅的變化和流動。

state

在使用React之前,常見的MVC框架也非常容易實現(xiàn)交互界面的狀態(tài)管理。在MVC框架將View中與界面交互的狀態(tài)解耦,一般將狀態(tài)放在Model中管理。但是React沒有結(jié)合Flux或是Redux框架前,React也同樣可以管理組件的內(nèi)部狀態(tài)。在React中,把這類狀態(tài)統(tǒng)一稱為state
當組件內(nèi)部使用庫內(nèi)置的setState方法時,最大的表現(xiàn)行為就是該組件會嘗試重新渲染。這很好理解,因為我們改變了內(nèi)部的狀態(tài),組件需要更新了。我們編寫一個計數(shù)器組件:

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,上面的例子中就是通過點擊“更新”按鈕不斷地更新內(nèi)部cout的值,這樣就可以把組件內(nèi)狀態(tài)封裝在實現(xiàn)中。
值得注意的是,setState是一個異步的方法,一個生命周期內(nèi)所有的setState方法會合并操作,有了這個特性,讓React變得充滿想象力,我們完全可以只用React來完成行為控制、數(shù)據(jù)的更新和界面的渲染。然而,隨著內(nèi)容的深入,我們發(fā)現(xiàn)官方并不推薦開發(fā)者濫用state。因為過多的內(nèi)部狀態(tài)會讓數(shù)據(jù)流混亂,數(shù)據(jù)變得難以維護。
我們再看一下Tabs組件的state,我們了解到應(yīng)該有兩個內(nèi)部狀態(tài)-- activeIndexpreIndex,這兩個狀態(tài)分別表示當前選中tab的索引和前一次選中的tab的索引,我們需要注意的是,當前選中的索引也是組件本身需要的參數(shù)之一。
我們針對activeIndex做為state,就有兩種不同的視角:

  • activeIndex在內(nèi)部更新:當我們切換tab標簽時,可以看做是組件內(nèi)部的交互行為,被選擇后通過回調(diào)函數(shù)返回具體選擇的索引。
  • activeIndex 在外部更新:當我們切換tab標簽時,可以看做是組件外部在傳入具體的索引,而組件就像‘木偶’一樣被操作。

這兩種情況在React組件的設(shè)計非常的常見,我們形象的把第一種和第二種視角寫成的組件分別稱為智能組件(smart component)和木偶組件(dumb component)
實現(xiàn)組件的時候,可以同時考慮兼容這兩種。我們來看一下Tabs組件初始化時實現(xiàn)部分:

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用來讓組件之間互相聯(lián)系的一種機制,通俗的說就像方法傳入?yún)?shù)一樣。
props的傳統(tǒng)過程,對于React組件來說是非常直觀的。React的單向數(shù)據(jù)流,主要的流動管道就是props。props本身是不可變的,當我們試圖改變props的原始值的時候,React會報出類型錯誤的警告,組件的props一定來自于 默認屬性或通過父組件傳遞而來。如果說要渲染一個對props加工后的值,最簡單的方法就是使用局部變量或直接在JSX中計算結(jié)果。
我們之前了解到Tabs組件的數(shù)據(jù)都是通過data prop傳入的,也就是<Tabs data = {data} />。那么Tabs組件的props還會有哪些,我們看一下下面的幾項:

  • className:根節(jié)點的class,為了方便覆蓋其原始樣式,我們都會在根節(jié)點上定義class
  • classPrefixclass前綴,對于組件來說,定義一個統(tǒng)一的class前綴,對樣式與交互分離起了很重要的作用。
  • defaultActiveIndexactiveIndex:默認的激活索引。
  • onChange:回調(diào)函數(shù),當我們切換tab的時候,外組件需要知道組件的內(nèi)部信息,尤其是當前tab的索引號的信息,onChange一般與activeIndex搭配使用。

Reactprops同樣提供了默認配置,通過defaultProp靜態(tài)變量的方式來定義。當組件被調(diào)用的時候,默認值
保證渲染后始終有值。在render方法中,可以直接使用props的值來渲染。這里,我們只需要默認設(shè)置classPrefixonChange即可。因為defaultActiveIndexactiveIndex,我們需要保持只去其中一個條件:

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

但是Tabs組件的信息全由一個對象傳進來的方式真的好嗎?對于React組件來說,我們考慮設(shè)計組件一定要滿足一大原則-- 直觀。把基本設(shè)置與數(shù)據(jù)一起定義成一個組件或?qū)ο笫浅鯇W(xué)者很容易犯的錯誤,對于React來說,如果組件是可以分解的,那么一定要將它進行分解,使用子組件的方式來進行處理。
我們仔細觀察一下Tabs組件在web界面的特征,一般來說,主要分成兩個區(qū)域:切換區(qū)域和內(nèi)容區(qū)域。那么我們根據(jù)上面說的,定義兩個區(qū)域:切換區(qū)域和內(nèi)容區(qū)域。TabNav組件對應(yīng)切換區(qū)域,TabContent組件對應(yīng)內(nèi)容區(qū)域。這兩個區(qū)域組件都存放一個有序的數(shù)組,都可以進行進一步的拆分, 具體的兩種組織方式如下:

  • Tabs組件的內(nèi)部把所有定義的子組件都顯示的展示出來。這么做的好處在于非常的易于理解,可以自定義的能力強,但是在調(diào)用的過程就會顯得笨重。React-BootstrapMaterial UI組件庫中的Tabs組件采用的就是這樣的方式,我們進行調(diào)用的方式如下:
<Tabs classPrefix = {'tabs'} defaultActiveIndex ={0}>
  <TabNav>
    <TabHead>Tab 1</TabHead>
    <TabHead>Tab 2</TabHead>
    <TabHead>Tab 3</TabHead>
  </TabNav>
  <TabContent>第一個Tab里面的內(nèi)容</TabContent>
  <TabContent>第二個Tab里面的內(nèi)容</TabContent>
  <TabContent>第三個Tab里面的內(nèi)容</TabContent>
</Tabs>
  • Tabs組件內(nèi)置顯示定義內(nèi)容區(qū)域的子組件集合,頭部區(qū)域?qū)?yīng)內(nèi)部區(qū)域的每一個TabPane組件的props,讓其在TabNav組件內(nèi)拼裝。這種方式的調(diào)用寫法比較簡單,把復(fù)雜的邏輯留給了組件去實現(xiàn)。Ant Design組件庫中的Tabs組件采用的就是這種方式。調(diào)用方式如下形式:
<Tabs classPrefix = {'tabs'} defaultActiveIndex ={0}>
  <TabPane key ={0} tab = {'Tab 1'}>第一個Tab里面的內(nèi)容</TabPane>
  <TabPane key ={1} tab = {'Tab 2'}>第二個Tab里面的內(nèi)容</TabPane>
  <TabPane key ={2} tab = {'Tab 3'}>第三個Tab里面的內(nèi)容</TabPane>
</Tabs>

我們通過后面的一種方法進行具體的講述,當基本結(jié)構(gòu)確定后,我們需要看一下怎么渲染這個結(jié)構(gòu)的內(nèi)容。顯然,不能讓所以的參數(shù)都由Tabs組件來承載。只有兩個props放在了Tabs組件上面,而其他的參數(shù)直接放在了TabPane組件上面,由它的父組件TabContent隱式對TabPane組件的拼裝。

子組件prop

在React中有一個重要且內(nèi)置的prop-children,它代表了組件的子組件結(jié)合。children可以根據(jù)傳入子組件的數(shù)量來決定是否是數(shù)組類型。我們上面調(diào)用TabPane組件的過程,翻譯過來就是:

<Tabs classPrefix = {'tabs'} defaultActiveIndex ={0} className = "tabs-bar"
  children ={[
     <TabPane key ={0} tab = {'Tab 1'}>第一個Tab里面的內(nèi)容</TabPane>
     <TabPane key ={1} tab = {'Tab 2'}>第二個Tab里面的內(nèi)容</TabPane>
     <TabPane key ={2} tab = {'Tab 3'}>第三個Tab里面的內(nèi)容</TabPane>
  ]}
>
</Tabs>

實現(xiàn)的基本思路就是以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的方法。它提供諸如mapforEach、count等實用函數(shù),可以為我們提供子組件提供便利。
最后,TabContent組件的render方法只需要調(diào)用getTabPanes方法就可以完成渲染:

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

假如我們把render方法中的this.getTabPanes方法中對子組件的遍歷直接放進去,就會變成如下的形式

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

這種調(diào)用方式稱為Dynamic Children(動態(tài)子組件)。它指的是組件內(nèi)的子組件是通過動態(tài)計算得到的。就像上述對子組件的遍歷一樣,我們一樣可以對任何數(shù)據(jù)、字符串、數(shù)組或?qū)ο笞鲃討B(tài)計算。
用聲明式編程的方式來渲染數(shù)據(jù),這樣的做法和關(guān)心所有的細節(jié)的命令式編程相比,會讓我們輕松很多,當然,除了數(shù)據(jù)的map函數(shù)。還可以用其他使用的高階函數(shù),如reduce、filter等函數(shù)。值得注意的是,與map函數(shù)相似但不返回調(diào)用結(jié)果的forEach函數(shù)不能這么使用。

組件props
<TabPane key ={0} tab = {'Tab 1'}>第一個Tab里面的內(nèi)容</TabPane>

現(xiàn)在tab prop中傳入的是一個字符串。但是,如果我們傳入的是節(jié)點呢,是不是就可以自定義tab頭展示的形式了,這就是component props。對于子組件而言,我們不僅可以直接使用this.props.children定義,也可以將子組件以props的形式傳遞。一般我們會用這種方法來讓開發(fā)者定義組件的某一個prop,讓其具備多種類型,來做到簡單配置和自定義配置組合在一起的效果。
Tabs組件中,我們就用到了這樣的功能,調(diào)用當時如下所示:

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

我們也可以加入更多的自定義元素,可以是多行的,甚至可以插入動態(tài)數(shù)據(jù)。這聽上去有些復(fù)雜,但是實現(xiàn)的過程其實非常簡單,下面寫在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方法非常像,關(guān)鍵在于通過遍歷TabPane組件的tab prop來實現(xiàn)我們想要的功能,不論tab是以字符串的形式還是以虛擬元素的形式存在,都可以直接在<li>標簽中渲染出來。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本筆記基于React官方文檔,當前React版本號為15.4.0。 1. 安裝 1.1 嘗試 開始之前可以先去co...
    Awey閱讀 7,932評論 14 128
  • 學(xué)習(xí)如何在Flow中使用React 將Flow類型添加到React組件后,F(xiàn)low將靜態(tài)地確保你按照組件被設(shè)計的方...
    vincent_z閱讀 6,553評論 4 21
  • 作為一個合格的開發(fā)者,不要只滿足于編寫了可以運行的代碼。而要了解代碼背后的工作原理;不要只滿足于自己的程序...
    六個周閱讀 8,679評論 1 33
  • 近一年處在風口浪尖的樂視,最近真的到了生死存亡的緊要關(guān)口。借用一句流行語:留給樂視的時間真的不多了。 水哥認為他是...
  • 高考,是我們無可選擇,更不可缺席的一次成年禮。 高考,便是父母、老師口中所常言道的“前途光明的出口”。為了父母所謂...
    馬克華菲發(fā)閱讀 247評論 0 0

友情鏈接更多精彩內(nèi)容