學習React階段性總結

自己學習React也有一段時間了,應該做一個階段性總結了,看了一下慕課網上的一些課程的介紹(自己捨不得花錢學-。-),然後自己截取了裏面知識點介紹,對比一下自己的學習內容,試着去解釋一下下面的知識點。

----------------------------------------------------------------------------------------------------------------------------------------------

React.js組件基礎
虛擬 DOM 概念
React 組件
React 多組件嵌套
JSX 內置表達式
生命週期

 

React.js屬性與事件
State 屬性、Props 屬性
事件和數據的雙向綁定(組件通信)
可複用組件
組件的 Refs
獨立組件間共享 Mixins(ES6下是用高級組件替代了Mixins)

 

React.js樣式

內聯樣式及其表達式
CSS 模塊化
JSX 樣式與 CSS 的互轉
Ant Design 樣式框架

 

React.js Router
Router 概念
Router 參數傳遞

(下面就按照我的一個思路和目前學習的進度進行總結)

----------------------------------------------------------------------------------------------------------------------------------------------

一.虛擬DOM的概念:

首先我用幾個問題來引出這個話題

如果我們想創建一個DOM節點我們有哪些辦法?
1.直接在HTML裏面寫標籤<div>,<p>,<span>.....等。
2.利用document.creatEelement,document.createTextNode創建我們的節點,然後appendchild進行相關嵌套在js中實現。


如果我們想給創建有屬性的DOM節點呢?
1.在HTML中我們直接就是<div class="xxx" id="xxx"> 這樣創建的屬性只能是這個標籤能擁有的,不能進行擴展(HTML5中可以進行擴展自定義屬性data-xxx這樣的形式)。


2.在js中我們就直接setAttribute( ) 方法添加指定的屬性(我們可以自定義屬性)。


(根據上面兩個問題會引發一個思考)
如果我們有多個標籤能不能批量去創建呢(不想一個個再去調用document.creatEelement,document.createTextNode,appendchild)?


如果我們想操作多個標籤,有沒有什麼簡單的方法,非要一個個先獲取到標籤然後一個個再去調用API去操作,去修改,如果遇到了標籤之間的互換位置,刪除等操作,我們是不是又要重複去創建,或者去思考一個算法來優化呢?

=================================================================================

 

首先我們可以利用面向對象的思想去解決,配合”工廠模式“批量建造我們的“DOM樹”


創建一個形如下面這樣的一個DOM節點

 

<ul class="list">
   <li class="item">1</li>
   <li class="item">2</li>
</ul>

 

 

 

我們可以把它想象成一個對象

 

var ul={
	tagName:"ul",
	props:{
		class:"list"
		},
	childern:[
		{
			tagName:"li",
			props:{
			    class:"item"
			},
			children:["1"]
		 },
		
		{
			tagName:"li",
			props:{
			    class:"item"
			},
			children:["2"]
		 },
	]
}

 

 

 

 

 

創建我們的構造器函數進行批量處理:

 

function Element(tagName,props,children){
   this.tagName=tagName;
   this.props=props;
   this.children=children;
}

var ul=new Element("ul",{class:"list"},[
                   new Element("li" , { class: " item" }, [ 1 ] ),
                   new Element("li" , { class: " item" }, [ 2 ] ),
                    ]
                  )

 

/*更新於2017/8/24   我發現這樣傳數據太麻煩了,而且我們如果從後臺接收數據的應該是json對象的格式,用上面的方法一個個去new,還要自己轉換一下,很麻煩,也不直觀,修改如下,可以直接傳入我們的json對象進行構建*/

 

function Element(obj) {
  this.tagName = obj.tagName;
  this.props = obj.props;
  var children = obj.children.map(function(item) {
    if (typeof item == "object") //如果包裹的是一個對象的話,繼續new Element
    {
      item = new Element(item)
    }
    return item
  })
  this.children = children;
}

var jsonData = {
  tagName: "ul",
  props: {
    class: "wrap"
  },
  children: [{
    tagName: "li",
    props: {
      class: "item"
    },
    children: ["111"]
  }, {
    tagName: "li",
    props: {
      class: "item"
    },
    children: ["222"]
  }, {
    tagName: "li",
    props: {
      class: "item"
    },
    children: ["333"]
  }]
}

Element.prototype.render = function() {
  var el = document.createElement(this.tagName) // 根據tagName構建  
  var props = this.props


  for (var propName in props) { // 設置節點的DOM屬性  
    var propValue = props[propName]
    el.setAttribute(propName, propValue)
  }

  var children = this.children || []

  children.forEach(function(child) {
    var childEl = (child instanceof Element) ?
      child.render() // 如果子節點也是虛擬DOM,遞歸構建DOM節點  
      :
      document.createTextNode(child) // 如果字符串,只構建文本節點  
    el.appendChild(childEl)
  })

  return el
}

var virtualDom = new Element(jsonData);
var realDom = virtualDom.render();
document.body.appendChild(realDom);


/*end*/

 

 

 


然後我們通過上面構造器函數得到的只是一個JS對象表示的DOM結構,然後我們再利用render方法去生成真正的DOM節點

 

Element.prototype.render = function () {
  var el = document.createElement(this.tagName) // 根據tagName構建
  var props = this.props


  for (var propName in props) { // 設置節點的DOM屬性
    var propValue = props[propName]
    el.setAttribute(propName, propValue)
  }

  var children = this.children || []

  children.forEach(function (child) {
    var childEl = (child instanceof Element)
      ? child.render() // 如果子節點也是虛擬DOM,遞歸構建DOM節點
      : document.createTextNode(child) // 如果字符串,只構建文本節點
    el.appendChild(childEl)
  })

  return el
}

 


ul.render();這個方法返回的對象纔是我們真正的DOM節點,然後我們再把它appendchild到我們頁面的真實DOM中即可


最後就是我們的DOM節點操作了,它會有一套自己的對比算法,就像我剛起提到的一樣它內部已經做好了優化的算法(Diff算法),我們不用再去造輪子,我們要做的操作無非就是:
 1.替換掉原來的節點
 2.移動、刪除、新增子節點
 3.修改了節點的屬性
 4.對於文本節點,文本內容可能會改變。


大概思路:會在代碼中定義好上面幾種類型操作的名稱,然後根據具體是哪個虛擬DOM的哪種操作,利用最優的算法,找出用這種方法實現後的DOM與之前的DOM的差異(由上面知道:初始化的時候它會創建一個“DOM樹”,當我們想去修改的時候我們會創建一個新的"DOM樹"),形成新的“DOM樹”,最後把這個差異渲染到之前的DOM結構中去。

(這裏尋找“DOM樹上”具體的DOM節點是利用key這個屬性,一般來說不用我們自己去設置,但是如果我們自己利用數組去動態渲染虛擬DOM的話就需要自己手動添加這個key值,我前面的博客中有提到過:http://blog.csdn.net/liuzijiang1123/article/details/66974630



綜上所述:我前面提到的那些問題,我們都可以更加"智能"地去用虛擬DOM(其實就是我們用JS對象模擬的一種DOM結構)去批量處理。
而且React幫我們封裝好了接口,再結合ES6我們開發起來就更加容易了,我們可以把每一個虛擬的DOM理解爲一個class,即React中的一個組件,
我們做的只需要爲這個組件定義屬性和方法。


React會比手動更新DOM要快麼?


網上查閱瞭解到:
其實React不會比手動更新DOM要快,因爲其實它也是需要調用DOM API去操作DOM的,而且操作之前還有構建虛擬DOM和diff的過程。但是它高效維護狀態和減少手動視圖更新的同時達到了儘量少的DOM操作,權衡了代碼可維護性和DOM性能。

 

有個貼切的比喻,把DOM和JavaScript各自想象爲一個島嶼,它們之間用收費橋樑連接,js每次訪問DOM,都要途徑這座橋,並交納“過橋費”,訪問DOM的次數越多,費用也就越高。 因此,推薦的做法是儘量減少過橋的次數,努力待在ECMAScript島上。因爲這個原因react的虛擬dom就顯得難能可貴了,它創造了虛擬dom並且將它們儲存起來,每當狀態發生變化的時候就會創造新的虛擬節點和以前的進行對比,讓變化的部分進行渲染。

----------------------------------------------------------------------------------------------------------------------------------------------

二.React 組件,多組件嵌套,jsx內置表達式

對於這些概念我就不再詳細說了,就直接看實例應該就能明白這些概念了。

(只給出關鍵性代碼)

 

class HelloMessage extends React.Component { 
        constructor(props) {
		super(props);
		this.msg="Hello World"
        	}
			
        render() {
	      return (
                  <div>
                     <h1>{this.msg}</h1>
                  </div>
		     );
                 }
           };
		
ReactDOM.render(
		<HelloMessage/>, 
		document.getElementById('app')
		);

 

 

 

 

 

 

 

 

我這邊就把這個程序梳理一下:

 1.創建一個HelloMessage類(首字母需要大寫),繼承React.Component(這個是React封裝好的一個組件類,我們只需要繼承它就可以用它提供的方法了,而且ES6提供了class和extends,這樣我們創建類和繼承就特別方便)。

 

 2.在es6裏面我們創建類后里面的方法就不用再用fn:function 這種形式,直接fn( )即可,我們創建的屬性都放在constructor( )這個方法(構造函數)裏面。

 

 3.在constructor中我們需要在最開頭使用super( ),super其實就是父類的構造函數,這個可以理解,因爲我們使用了繼承,所以當我們使用構造函數的時候,要先調用一下父類的構造函數,而且要把自己的參數全傳給父類,這個props參數其實就是包含了組件上傳進來的屬性。

 

 4.rener方法中return出來的對象就是我們產生的真實DOM;這裏面也用到了jsx語法,html標籤可以嵌入到我們的js語言中,只不過在html標籤中寫js的時候需要用{ } 括起來,在寫樣式的時候需要兩個{ } 例如<div style={ { "color":"red"} }>,還有就是裏面的寫法需要用駝峯式,例如onClick ,marginLeft等,還有就是有些和ES6衝突的寫法需要換一種方式,比如calss屬性要寫成calssName,for需要寫成htmlFor。

 

 5.render中return中只能有一個父級元素,不能有並列的。最後靠ReactDOM.render將我們生成的DOM節點真正掛載到我們的頁面的DOM結構中去。

 

 6.render中renturn中返回的元素不完全是我們的html標籤元素,還可以是我們自己定義例如,組件之間各級嵌套,就如我們的html元素一樣。

 

return (
 <div>
   <h1>{this.msg}</h1>
   <Component2>
     <Component3/>
   </Component2>
 </div>
 );

 

7.我們可以把頁面細分成多個組件,例如我們<h1>,<p>都可以看成一個組件,也可以進行嵌套形成一個複雜的組件;其實我們可以看見React組件之間嵌套一層又一層,形如一個金字塔形的結構。每個組件分工不一樣,有的只是做展示,有的則是有具體的功能和交互,組件之間互相配合,重複使用。

 

----------------------------------------------------------------------------------------------------------------------------------------------

三.State 屬性、Props 屬性、組件通信

 

React中最重要的兩個概念,一個是組件,一個就是狀態。我們是通過控制組件的狀態從而控制程序的view層(都說React框架其實只是view層的表現,側重點在於DOM節點的渲染).說到狀態就不得不提state和props這2個屬性。

這兩個屬性的最大不同點在於,props是隻讀的屬性,state不是。由於這個特性我們大概就能猜出來這兩個屬性的作用來了:props是用來定義組件一些不變的屬性,用來進行組件初始化的參數傳遞來達到渲染組件的目的,而state是定義一些可變屬性,用來修改組件屬性,從而達到再次渲染組件的目的。

這裏既然說到了修改組件屬性,那麼我們就不得不提一下組件之間的通信:

可以參考我這篇博客的例子:http://blog.csdn.net/liuzijiang1123/article/details/64131547

這裏還需要注意的是對於state屬性的修改我們不能想當然的this.state.xxx=abc這樣進行修改,react給了我們專門的方法this.setState( )這個方法供我們使用。

 

----------------------------------------------------------------------------------------------------------------------------------------------

四.生命週期

 

個人認爲組件的生命週期同樣是個很重要的概念。

首先說一下生命週期分爲以下幾個:(這些函數都是以回調函數的形式給我們,這樣我們就只需要在這裏面寫我們的邏輯即可,不用去調用,去擔心什麼時候該調用這些函數)

 

  • componentWillMount( )
  • componentDidMount( ) (僅在客戶端有效)
  • componentWillUpdate(object nextProps, object nextState)
  • componentDidUpdate(object prevProps, object prevState)
  • componentWillUnmount( )

 

此外,React 還提供兩種特殊狀態的處理函數。

  • componentWillReceiveProps(object nextProps):已加載組件收到新的參數時調用
  • shouldComponentUpdate(object nextProps, object nextState):組件判斷是否重新渲染時調用<return true渲染組件,反之不渲染>

      當我們有這些生命週期函數後,首先在書寫代碼的邏輯上給了我們很大的一個幫助,好比告訴我們一個人每個階段該做什麼事情,這樣就不至於出現"誰的青春不迷茫"這樣的感慨了,對於我們代碼的結構上有很大的幫助,方便我們以後去維護,查看問題。而且前面也說了它是以一個回調函數的形式給我們,我們只需要把邏輯往裏面“丟”就行~

 

下面是一個組件的生命週期調用順序:

      當一個組件被調用的時候先執行constructor這個函數,然後執行componentWillMount(即將渲染),接下來渲染到dom樹上(觸發render函數),渲染完成觸發componentDidMount函數;

這時候,該組件就進入了一個running狀態,並監視他的props和state以及被移除事件:

  當props發生變化時執行componentWillReceiveProps然後去判斷是否需要重新渲染(shouldComponentUpdate),如果不需要則繼續保持running狀態;如果需要則如初始時一樣,執行componentWillMount(即將渲染),接下來渲染到dom樹上,渲染完成觸發componentDidMount函數,保持running狀態繼續監視;

  當state發生變化時,則直接判斷是否需要重新渲染(shouldComponentUpdate),然後根據是否需要決定執行渲染過程還是繼續保持running狀態;

  當該組件被移除(unmount)時,將直接執行componentWillUnmount,該組件從dom樹上消失;

 

上面都是一些理論知識,然後我們看一下具體一個例子:

 

 

class Clock extends React.Component { 
        constructor(props) {
		super(props);
		this.state = { date: new Date()};
        	}
		
		
        componentDidMount() {
		this.timerID = setInterval( () => this.tick(),1000);
        	}
			
	componentWillUnmount() {
		clearInterval(this.timerID);
		}
			
        tick() {
		this.setState({
		   date: new Date()
		});
	     }
	
        render() {
	      return (
               <div>
                   <h2>It's {this.state.date.toLocaleTimeString()}</h2>
                </div>
		 );
              }
          };
ReactDOM.render(
       <Clock />,
       document.getElementById('app')
 );	

 

這是一個時鐘例子,利用定時器去更新數據,關鍵點在於我們的更新函數需要在組件掛載了之後執行,這樣我們就應該寫在componentDidMount中,當組件將要被卸載的時候消在componentWillUnmount中除定時器即可。

 

----------------------------------------------------------------------------------------------------------------------------------------------

 

五.組件的Refs

 

        jq之所以好用,很大一部分在於它強大的選擇器,讓我們更加輕鬆地直接操作我們想要的DOM節點;同樣React也提供了一種讓我們直接操作DOM節點的方法,其提供了一個refs屬性,我們可以給組件定義一個ref=xxx 類似於我們ID,然後通過this.refs.xxx來直接操作我們的DOM節點,因爲它返回的就是我們的真實DOM節點,這樣我們就可以用原生的各種方法去操作它。

這裏需要注意的兩點就是:

 1.這個屬性是添加在我們組件中的html標籤中的,例如<p>,<div>等,並不是我們自己定義的標籤

 2.需要等到組件componentDidMount之後我們才能訪問到我們的DOM節點

 

       既然這裏說到了jq,那我就順便提一下,React的開放性特別好,在它裏面可以用jq,而且還兼容其他的前端框架,angular,vue等等。因爲React提供給我們的就兩個東西,一個是組件,一個就是狀態,其他的一些功能需要我們自己去組合開發,自己寫業務邏輯。但是angular是不能兼容其他框架的,它本身也封裝好了很多方法給我們使用,比如表單驗證,angular就提供給了我們接口,而React需要我們自己想辦法。

class RefsC extends React.Component { 
        constructor(props) {
		super(props);
		this.state = { date: new Date()};
              }
		
		
        componentDidMount() {
                 var _this=this;
                 $(_this.refs.lzj).click(function(){
		       _this.setState((prevState, props) => ({
			  num:prevState.num+1
		    }));
		   /*_this.setState(function(prevState){
                          return {
                           num:prevState.num+1
                        }
                      });*/
                    })		
                 }
         render() {
	     return (
                <div>
                   <h1 ref="lzj">{this.state.num}</h1>
                </div>
                  );
             }
          };
       ReactDOM.render(
         <RefsC />,
       document.getElementById('app'));

 

 

(我們這裏需要注意的一點就是this,一般的做法就是把當前的作用域this保存下來供後面使用,或者使用.bind去綁定,再或者使用箭頭函數去解決)

 

/*目前就只先談到這裏,路由的話前面寫過一次http://blog.csdn.net/liuzijiang1123/article/details/70800185*/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章