React Components之間的通訊方式瞭解下

來源:互聯網
上載者:User

先來幾個術語:

官方 我的說法 對應代碼
React element React元素 let element=<span>A爆了</span>
Component 組件 class App extends React.Component {}
App為父元素,App1為子項目 <App><App1></App1></App>

本文重點:

  • 組件有兩個特性
    • 1、傳入了一個“props”
    • 2、返回了一個React元素
  • 組件的建構函式
    • 如果需要重新定義constructor,必須super一下,才能啟用this,也就是可以用來自React.component方法
  • 組件的props
    • 是可讀的,也就是不能在組件中修改prop的屬性
    • JSX中傳入對象的props,可以通過{...object}的方式
  • 父子項目之間的通訊(初級版本)
    • 父=>子,通過父元素的render既可改變子項目的內容。
    • 子=>夫,通過父元素傳入子項目中的props上掛載的方法,讓子項目觸發父元素中的方法,從而進行通訊。
Component

上回說到JSX的用法,這回要開講react組件之間的一個溝通。那麼什麼是組件?我知道英文是Component,但這對我而言就是一個單詞,毫無意義。要瞭解Component之間是如何進行友好交流的,那就要先瞭解Component是個什麼鬼。

上回說到的JSX,我們可以這麼建立對象:

let element=<h1 className="aaa">A爆了</h1>//等同於let element=React.createElement(  "h1",  {className:"aaa"},  "A爆了")

還是老老實實地用h1div這種標準的HTML標籤元素去產生React元素。但是這樣的話,我們的JS就會變得巨大無比,全部都是建立的React元素,有可能到時候我們連對象名都不曉得怎麼起了,也許就變成let div1;let div2這樣的。哈哈哈開個玩笑。但是分離是肯定要分離的。這個時候就有了名為Component的概念。他可以做些什麼呢?簡單的說就是建立一個個獨立的可複用的小工具。話不多說,我們來瞅瞅來自官方的寫法:

寫法一:函數型建立組件,大家可以看到我就直接定義一個名為App的方法,每次執行App()的時候就會返回一個新的React元素。而這個方法我們可以稱之為組件Component。有些已經上手React的朋友,可能傻了了,這是什麼操作,我的高大上class呢?extend呢?很遺憾地告訴你,這也是組件,因為他符合官方定義:1、傳入了一個“props” ,2、返回了一個React元素。滿足上述兩個條件就是Component!

function App(props) {  return <span>{props.name}!A爆了</span>     }

這個是最簡易的Component了,在我看來Component本身是對React.createElement的一種封裝,他的render方法就相當於React.createElement的功能。高大上的組件功能來啦:

import React, { Component } from 'react';class App extends Component {  render() {    return <span>{this.props.name}!A爆了</span>       }}export default App;

這個class版本的組件和上方純方法的組件,從React的角度上來說,並無不同,但是!畢竟我class的方式還繼承了React.Component,不多點小功能都說不過去對吧?所以說我們這麼想繼承了React.Component的組件的初始功能要比純方法return的要多。所以每個React的Component我們都可以當作React元素直接使用。

好了,我們來研究研究Component這個類的方法吧。

首先是一個神奇的constructor函數,這個函數在類中,可以說是用於初始化的函數。如果省去不寫,也不會出錯,因為我們的組件都是React.Component的子類,所以都繼承了React.Componentconstructor方法。如果我們在子類Component中定義了constructor相當於是覆蓋了父類的方法,這樣React.Component的建構函式就失效了。簡單地來說就是很多預設的賦值都失效了。你是擷取不到props的。因此官方為了提醒大家不要忘記super一下,也就是繼承父類的constructor,因此會報"this hasn't been initialised - super() hasn't been called"這個錯誤。意思就是你先繼承一下。也就是說super是執行了父類的constructor的方法。所以!!!重點來了——我們寫super的時候不能忘記傳入props。不傳入props,程式就無法擷取定義的組件屬性了。

constructor(props) {    super(props);//相當於React.Component.call(this,props)}

官方也給大家劃重點了:

Class components should always call the base constructor with props.(類組建在執行基本constructor的時候,必須和props一起。)

對於我們沒有寫constructor,但在其他內建方法中,比如render,也可以直接擷取到props,這個詭異的操作就可以解釋了。因為我們省略了重定義,但是constructor本身不僅是存在的而且也執行了,只不過沒有在我們寫的子類中體現出來而已。

props的坑

分析了Component之後,大家有沒有發現Component的一個局限?沒錯!就是傳參!關於Component的一個定義就是,只能傳入props的參數。也就是說所有的溝通都要在這個props中進行。有種探監的既視感,只能在規定的視窗,拿著對講機聊天,其他的方式無法溝通。React對於props有著苛刻的規定。

All React components must act like pure functions with respect to their props.

簡單地來說就是props是不能被改變的,是唯讀。(大家如果不信邪,要試試,可以直接改props的值,最終等待你的一定是報錯頁面。)

這裡需要科普下純函數pure function的概念,之後Redux也會遇到的。意思就是純函數只是一個過程,期間不改變任何對象的值。因為JS的對象有個很奇怪的現象。如果你傳入一個對象到這個方法中,並且改變了他某屬性的值,那麼傳入的這個對象在函數外也會改變。pure function就是你的改動不能對函數範圍外的對象產生影響。所以每次我們在Component裡面會遇到一個新的對象state,一般這個組件的資料我們會通過state在當前組件中進行變化處理。

劃重點:因為JS的特性,所以props設定為唯讀,是為了不汙染全域的範圍。這樣很大程度上保證了Component的獨立性。相當於一個Component就是一個小世界。

我發現定義props的值也是一門學問,也挺容易踩坑的。

比如下方代碼,我認為列印出來應該是props:{firstName:"Nana",lastName:"Sun"...},結果是props:{globalData:true}.

let globalData={    firstName:"Nana",    lastName:"Sun",    greeting:["Good moring","Good afternoon","Good night"]}ReactDOM.render(<App globalData/>, document.getElementById('root'));

所以對於props是如何傳入組件的,我覺得有必要研究一下下。

props其實就是一個參數直接傳入組件之中的,並未做什麼特殊處理。所以對props進行處理的是在React.createElement這一個步驟之中。我們來回顧下React.createElement是怎麼操作的。

React.createElement(  "yourTagName",  {className:"aaa",color:"red:},  "文字/子節點")//對應的JSX寫法是:<yourTagName className="aaa" color="red>文字/子節點</yourTagName>

也就是他的文法是一個屬性名稱=屬性值,如果我們直接放一個<App globalData/>,那麼就會被解析成<App globalData=true/>},所以props當然得不到我們想要的結果。這個是他的一個文法,我們無法扭轉,但是我們可以換一種寫法,讓他無法解析成屬性名稱=屬性值,這個寫法就是{...globalData},解構然後重構,這樣就可以啦。

Components之間的訊息傳遞單個組件的更新->setState

Components之間的訊息傳遞是一個互動的過程,也就是說Component是“動態”的而不是“靜態”的。所以首先我們得讓靜態Component“動起來”,也就是更新群組件的的值,前面不是提過props不能改嘛,那怎麼改?前文提過Component就是一個小世界,所以這個世界有一個狀態叫做state

先考慮如何外力改變Component的狀態,就比如點擊啦,划過啦。

class App extends Component {  state={      num:0  }  addNum=()=>{    this.setState({      num:this.state.num+1    })  }  render() {    return( [        <p>{this.state.num}</p>,        <button onClick={this.addNum}>點我+1</button>      ]    )       }}

這裡我用了onClick的使用者主動操作的方式,迫使組件更新了。其實component這個小世界主要就是靠state來更新,但是不會直接this.state.XXX=xxx直接改變值,而是通過this.setState({...})來改變。

這裡有一個小tips,我感覺大家很容易犯錯的地方,有關箭頭函數的this指向問題,大家看。箭頭函數轉化成ES5的話,我們就可以很清晰得看到,箭頭函數指向他上一層的函數對象。這裡也就指向App這個對象。

如果不想用箭頭函數,那麼就要注意了,我們可以在onClick中加一個bind(this)來綁定this的指向,就像這樣onClick={this.addNum.bind(this)}

  render() {    return( [        <p>{this.state.num}</p>,        <button onClick={this.addNum.bind(this)}>點我+1</button>      ]    )       }
組件之間的通訊

那麼Component通過this.setState可以自high了,那麼組件之間的呢?Component不可能封閉自己,不和其他的Component合作啊?那我們可以嘗試一種方式。

在App中我把<p>{this.state.num}</p>提取出來,放到App1中,然後App1直接用props來顯示,因為props是來自父元素的。相當於我直接在App(父元素)中傳遞num給了App1(子項目)。每次App中state發生變化,那麼App1就接收到召喚從而一起更新。那麼這個召喚是基於一個什麼樣的理論呢?這個時候我就要引入React的生命週期life cycle的問題了。

//Apprender() {  return( [      <App1 num={this.state.num}/>,      <button onClick={this.addNum}>點我+1</button>    ]  )     }//App1render() {  return( [      <p>{this.props.num}</p>,    ]  )     }
react的生命週期

看到生命週期life cycle,我就感覺到了生生不息的迴圈cycle啊!我是要交代在這個圈圈裡了嗎?react中的生命週期是幹嘛的呢?如果只是單純的渲染就沒有生命週期一說了吧,畢竟只要把內容渲染出來,任務就完成了。所以這裡的生命週期一定和變化有關,有變化才需要重新渲染,然後再變化,再渲染,這才是一個圈嘛,這才是life cycle。那麼React中的元素變化是怎麼變的呢?

先來一個官方的生命週期(我看著就頭暈):

點我看live版本

官方的全周期:

官方的簡約版周期:

有沒有看著頭疼,反正我是跪了,真令人頭大的生命週期啊。我還是通過實戰來確認這個更新是怎麼產生的吧。實戰出真理!(一些不安全的方法,或者一些我們不太用得到的,這裡就不討論了。)

Mounting裝備階段:

  • constructor()
  • render()
  • componentDidMount()

Updating更新階段:

  • render()
  • componentDidUpdate()
  • 具有爭議的componentWillReceiveProps()

Unmounting卸載階段:

  • componentWillUnmount()

Error Handling錯誤捕獲極端

  • componentDidCatch()

這裡我們通過運行代碼來確認生命週期,這裡是一個父元素嵌套子項目的部分代碼,就是告訴大家,我在每個階段列印了啥。這部分的例子我用的還是上方的App和App1的例子。

//fatherconstructor(props){  console.log("father-constructor");}componentDidMount() {  console.log("father-componentDidMount");}componentWillUnmount() {  console.log("father-componentWillUnmount");}componentDidUpdate() {  console.log("father-componentDidUpdate");}render() {  console.log("father-render");}
//childconstructor(props){  console.log("child-constructor");  super(props)}componentDidMount() {  console.log("child-componentDidMount");}componentWillUnmount() {  console.log("child-componentWillUnmount");}componentDidUpdate() {  console.log("child-componentDidUpdate");}componentWillReceiveProps(){  console.log("child-componentWillReceiveProps");}render() {  console.log("child-render");}

好了~開始看圖推理~

初始化運行狀態:

父元素先運行建立這沒有什麼問題,但是問題是父元素還沒有運行結束,殺出了一個子項目。也就是說父元素在render的時候裡面碰到了子項目,就先裝載子項目,等子項目裝載完成後,再告訴父元素我裝載完畢,父元素再繼續裝載直至結束。

我點擊了一下,父元素setState,然後更新了子項目的props

同樣的先父元素render,遇到子項目就先暫時掛起。子項目這個時候出現了componentWillReceiveProps,也就是說他是Crowdsourced Security Testing道了父元素傳props過來了,然後再render。因為有時候我們需要在擷取到父元素改變的props之後再執行某種操作,所以componentWillReceiveProps很有用,不然子項目就直接render了。突想皮一下,那麼我子項目裡面沒有props那是不是就不會執行componentWillReceiveProps了??就是<App1 num={this.state.num}/>變成<App1/>。我還是太天真了。這個componentWillReceiveProps依然會執行也就是說:

componentWillReceiveProps並不是父元素傳入的props發生了改變,而是父元素render了,就會出發子項目的這個方法。

關於卸載,我們來玩一下,把App的方法改成如下方所示,當num等於2的時候,不顯示App1。

render() {  return(     <div>      {this.state.num===2?"":<App1 num={this.state.num}/>}      <button onClick={this.addNum}>點我+1</button>    </div>  )     }

App先render,然後卸載了App1之後,完成了更新componentDidUpdate

那麼大家看懂了生命週期了嗎??我總結了下:

  • 父元素裝載時render了子項目,就先裝載子項目,再繼續裝載父元素。
  • 父元素render的時候,子項目就會觸發componentWillReceiveProps,並且跟著render
  • 父元素卸載子項目時,先render,然後卸載了子項目,最後componentDidUpdate
如何子傳父親呢??

通過生命週期,子項目可以很容易的擷取到父元素的內容,但是父元素如何獲得來自子項目的內容呢?我們不要忘記了他們為一個溝通橋樑props!我們可以在父元素中建立一個方法用於擷取子項目的資訊,然後綁定到子項目上,然後不就可以擷取到了!操作如下所示:

receiveFormChild=(value)=>{  console.log(value)}render() {  return(     <div>      {this.state.num===2?"":<App1 num={this.state.num} popToFather={this.receiveFormChild}/>}      <button onClick={this.addNum}>點我+1</button>    </div>  )     }

當子項目運行popToFather的時候,訊息就可以傳給父親啦!

子項目:

render() {  return( [      <p>{this.props.num}</p>,      <button onClick={()=>this.props.receiveState("來自子項目的慰問")}>子傳父</button>    ]  )     }

父元素成功擷取來自子項目的慰問!

這次就科普到這裡吧。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.