1. React概覽前端
最初聽到React而還未深刻了解它時,大多數人可能和個人想法同樣:難道又是一個新的MVC/MVVM前端framework?深刻了解後發現不是這麼一回事,React關注的東西很單純,就是view,而且它也確實解決了前端目前的一些問題,好比view代碼的複用,封裝組件。應該說React提出了一些新的東西,讓前端開發人員有機會從新審視view層的開發策略。react
先來快速看一個React的簡單例子:git
HelloMessage組件:github
)web
使用HelloMessage組件:算法
固然別忘了還有組件要掛載到的節點:數組
效果:緩存
這個例子很好理解,在HelloMessage.react.js中,咱們用React建立了一個名叫HelloMessage的UI組件,它的輸出是數據結構
1 <div>Hello, {this.props.name}!</div>
this.props.name就是在使用HelloMessage組件時傳入的name屬性:架構
1 <HelloMessage name="Tom"/>
因而會顯示 "Hello, Tom!" 就很好理解了,很簡單吧?
要跑起這個例子咱們還須要一些引入一些庫和配置一些自動化構建的工做流,這部分比較枯燥,放在最後給出,想立刻動手試一試的童鞋能夠先跳轉到[環境搭建]一節中。
React 的核心思想是:封裝組件
簡單來講就是,各個組件維護本身的狀態(state)和UI,當狀態變動時,自動從新渲染整個組件。這種作法的一個好處就在於咱們能夠獲得一個個UI組件,而且它們的狀態是自管理的。好比當用戶在某個UI組件上觸發一個事件時,咱們沒必要編寫查找DOM的代碼去找到要操做的DOM元素,再施加操做;取而代之的是咱們在編寫組件時,就已經寫好組件能響應什麼事件,要如何響應事件。沒有沒繁瑣的DOM查找,代碼也變得清晰不少。
React基本上由如下部分組成:
1. 組件
2. JSX
3. Virtual DOM
4. Data Flow
組件
組件是React的核心,剛纔的HelloMessage就是一個組件,組件包含兩個核心概念,props和state。props是組件的配置屬性,是不會變化的,就像剛纔咱們看到的示例,在聲明組件時咱們傳入了name="Tom",就是指定了HelloMessage這個組件的name屬性值爲"Tom",能夠定義多個不一樣的組件屬性。state是組件的狀態,是會變化的,當state變化時組件會自動從新渲染本身。有一篇討論React的[文章](http://www.infoq.com/cn/articles/react-art-of-simplity/)中談到組件,如下引用這篇文章的這段話:
所謂組件,就是狀態機器
React將用戶界面看作簡單的狀態機器。當組件處於某個狀態時,那麼就輸出這個狀態對應的界面。經過這種方式,就很容易去保證界面的一致性。
在React中,你簡單的去更新某個組件的狀態,而後輸出基於新狀態的整個界面。React負責以最高效的方式去比較兩個界面並更新DOM樹。
JSX
可能有人會發現使用React時JS代碼的寫法有點奇怪,直接把HTML嵌入在JS中,固然JS引擎是沒法解釋這種語法的,必須由咱們把JSX代碼編譯輸出後,纔是JS引擎可讀的代碼。JSX是一種把HTML封裝成組件的有效手段,它把組件的數據和UI融合在一塊兒,使組件化成爲可能。這裏可能有童鞋會表示疑惑,咱們在作界面開發的時候不是常常講要"表現和邏輯分離"嗎,爲何React把表現和邏輯整合在一塊兒就沒問題呢?好吧,這是個問題,咱們放在後面再來討論。
Virtual DOM
當組件state發生變化的時候,React會調用組件的render方法自動從新渲染組件UI。能夠預想到的一種狀況是,若是某個組件很大,其中可能還包含了不少其餘組件,那一次從新渲染代價將會很大(由於要遍歷這個組件的整個DOM結構進行渲染),不過React對此作了優化,React在內部實現了一個Virtual DOM,組件的DOM結構映射到這個Virtual DOM上,React在這個Virtual DOM上實現了一個diff算法,簡單來講就是React會使用這個diff算法來判斷某個組件是否須要更新,只更新有變化的組件。這個更新會先發生在Virtual DOM上,Virtual DOM是內存中的一個數據結構,所以更新起來很快,最後再真正地去更新真實的DOM。
Data Flow
React推崇一種叫作"單向數據綁定"的模式, 結合Flux這種應用架構構建客戶端web應用。
2. JSX
傳統的MVC是將模板寫成模板文件獨立放在某個地方,在須要時去引用這個模板,這樣作確實是把表現成單獨分離了,可是這又帶來一些新的問題:咱們要用什麼姿式引用這些模板,或者說我要怎麼把數據注入模板,模板存放在哪裏等問題。也就是說這個模板和代碼邏輯看似是分離的(物理上的分離),其實仍是耦合在一塊兒的(邏輯上是耦合的)。爲了實現組件化,React引入了JSX的概念。
React實現了組件模板和組件邏輯關聯,因此纔有了JSX這種語法,把HTML模板直接嵌入到JS中,編譯輸出後可用。能夠認爲JSX是一種"中間層",或者說"膠水層",它把HTML模板和JS邏輯代碼粘合在一塊兒。
JSX是可選的
React會解析JSX代碼來生成JS引擎可讀的代碼,所以最後的輸出代碼都是JS引擎可讀的,也就是咱們日常用的JS代碼,所以若是有必要能夠無視JSX,直接用React提供的DOM方法來構建HTML模板,好比下面這行代碼是用JSX寫:
1 <a href="http://facebook.github.io/react/">Hello!</a>
也可使用React.createElement來構建DOM樹,第一個參數是標籤名,第二個參數是屬性對象,第三個參數是子元素:
1 React.createElement('a', {href: 'http://facebook.github.io/react/'}, 'Hello!')
利用JSX來寫HTML模板,能夠用原生的HTML標籤,也能夠像使用原生標籤那樣引用React組件。React約定經過首字母大小寫來區分這二者。原生的HTML標籤和平時同樣,都是小寫的,React組件首字母須要大寫。使用HTML標籤:
1 var render = require('react-dom').render; 2 var myDivElement = <div className="foo" />; 3 render(myDivElement, document.body);
使用React組件:
1 var render = require('react-dom').render; 2 var MyComponent = require('./MyComponet'); 3 var myElement = <MyComponent someProperty={true} />; 4 render(myElement, document.body);
使用JavaScript表達式
能夠在屬性值和子組件中使用JavaScript表達式,使用{}包裹表達式。稍微改造一下HelloMessage的例子,
在屬性中使用JavaScript表達式:
1 //HelloMessage.react.js 2 var React = require('react'); 3 4 var HelloMessage = React.createClass({ 5 render: function() { 6 return < div > Hello, 7 { 8 this.props.sex === 'm' ? 'Mr.': 'Mrs.' 9 } { 10 this.props.fname 11 } ! </div>; 12 } 13 }); 14 15 module.exports = HelloMessage;/
1 //使用HelloMessage 2 var React = require('react'); 3 var render = require('react-dom').render; 4 var HelloMessage = require('./components/HelloMessage.react.js'); 5 6 var num = 1; 7 8 render( < HelloMessage fname = 'Smith'sex = { 9 num === 0 ? 'm': 'f' 10 } 11 />, 12 document.getElementById('helloMessage') 13 );/
輸出:
Hello, Mr. Smith!
在子組件中使用JavaScript表達式:
1 //HelloMessage.react.js 2 var React = require('react'); 3 4 var HelloMessage = React.createClass({ 5 render: function() { 6 return < div > Hello, 7 { 8 this.props.sex === 'm' ? 'Mr.': 'Mrs.' 9 } { 10 this.props.fname 11 } ! </div>; 12 } 13 }); 14 15 module.exports = HelloMessage;/
1 //使用HelloMessage 2 var React = require('react'); 3 var render = require('react-dom').render; 4 var HelloMessage = require('./components/HelloMessage.react.js'); 5 6 render( < HelloMessage fname = 'Smith'sex = 'm' / >, document.getElementById('helloMessage'));
輸出:
Hello, Mrs. Smith!
註釋
在JSX中使用註釋和在JS差很少,惟一要注意的是在一個組件的子元素位置使用註釋要用{}包裹起來,看下面這個能夠工做的例子:
1 var React = require('react'); 2 3 var HelloMessage = React.createClass({ 4 render: function() { 5 return ( 6 /*這裏是註釋1*/ 7 < div 8 /*這裏 9 是 10 註釋2*/ 11 > { 12 /*這裏是註釋3*/ 13 } 14 Hello, { 15 this.props.sex === 'm' ? 'Mr.': 'Mrs.' 16 } { 17 this.props.fname 18 } ! </div> 19 ); 20 } 21 }); 22 23 module.exports = HelloMessage;/
注意到只有註釋3須要被包裹在{}內,由於存在於一個子元素的位置。
屬性擴散(ES6支持)
屬性擴散使用了ES6起支持的擴展運算符,擴展運算符用三個點號表示,功能是把數組或類數組對象展開成一系列用逗號隔開的值,使用屬性擴散能夠知足咱們懶惰的心理:
1 var React = require('react'); 2 var render = require('react-dom').render; 3 var HelloMessage = require('./components/HelloMessage.react.js'); 4 5 var props = { 6 fname: 'Smith', 7 sex: 'm' 8 }; 9 10 render( < HelloMessage {...props 11 } 12 />, 13 document.getElementById('helloMessage') 14 ); 15 16 /
輸出:
Hello, Mr. Smith!
還能夠顯式覆蓋屬性擴散的值:
1 var React = require('react'); 2 var render = require('react-dom').render; 3 var HelloMessage = require('./components/HelloMessage.react.js'); 4 5 var props = { 6 fname: 'Smith', 7 sex: 'm' 8 }; 9 10 render( < HelloMessage {...props 11 } 12 fname = 'Johnson'sex = 'f' / >, document.getElementById('helloMessage'));
這裏後面的fname和sex會覆蓋前面的值,輸出:
Hello, Mrs. Johnson!
JSX和HTML的差別
1. 在JSX中,class要寫成className。
2. JSX中能夠自定義標籤和屬性。
3. JSX支持JavaScript表達式。
3. React組件
React應用是構建在組件之上的。組件的兩個核心概念:
1. props
2. state
組件經過props和state生成最終的HTML,須要注意的是,*經過組件生成的HTML結構只能有一個根節點*。
props
props就是一個組件的屬性,咱們能夠認爲屬性是不變的,一旦經過屬性設置傳入組件,就不該該去改變props,雖然對於一個JS對象咱們能夠改變幾乎任何東西。
state
state是組件的狀態,以前說到組件是一個狀態機,根據state改變本身的UI。組件state一旦發生變化,組件會自動調用render從新渲染UI,這個動做會經過this.setState方法觸發。
如何劃分props和state
應該儘量保持組件狀態的數量越少越好,狀態越少組件越容易管理。那些不會變化的數據、變化沒必要更新UI的數據,均可以歸爲props;須要變化的、變化須要更新UI的則歸爲state。
無狀態組件
一些很簡單的組件可能並不須要state,只須要props便可渲染組件。在ES6中能夠用純函數(沒有反作用,無狀態)來定義:
1 const HelloMessage = (props) => <div> Hello, {props.name}!</div>; 2 render(<HelloMessage name="Smith" />, mountNode);
組件的生命週期
一些重要函數
1. getInitialState:
初始化this.state的值,只在組件裝載以前調用一次。
2. getDefaultProps
只在組件建立時調用一次並緩存返回的對象(即在 React.createClass 以後就會調用)。在組件加載以後,這個方法的返回結果會保證當訪問this.props的屬性時,就算在JSX中沒有設置相應屬性,也老是能取到一個默認的值。
3. render
每一個組件都必須實現的方法,用來構造組件的HTML結構。能夠返回null或者false,此時ReactDOM.findDOMNode(this)會返回null。
生命週期函數:
裝載組件觸發
1. componentWillMount
只在組件裝載以前調用一次,在render以前調用,能夠在這裏面調用setState改變狀態,而且不會致使額外的一次render。
2. componentDidMount
只會在組件裝載完成以後調用一次,在render以後調用,從這裏開始能夠經過ReactDOM.findDOMNode(this)獲取到組件的DOM節點。
更新組件觸發
這些方法不會在首次render組件的週期調用
1. componentWillReceiveProps
2. shouldComponentUpdate
3. componentWillUpdate
4. componentDidUpdate
DOM操做
1. findDOMNode
當組件加載到頁面上之後,就能夠用react-dom的findDOMNode方法來得到組件對用的DOM元素了,注意,findDOMNode不能用在無狀態組件上。
2. refs
還有一種方法是在要引用的DOM元素上設置一個ref屬性,經過this.refs.name能夠引用到相應的對象。若是ref是設置在原生HTML元素上,它拿到的就是DOM元素,若是設置在自定義組件上,它拿到的就是組件實例,這時候就須要經過findDOMNode來拿到組件的DOM元素。須要注意的是,剛纔提到的無狀態組件沒有實例,它就是個函數,因此ref屬性不能設置在無狀態組件上。由於無狀態組件沒有實例方法,不須要用ref去拿實例而後調用實例方法,若是真的想得到無狀態組件的DOM元素的時候,須要用一個有狀態組件封裝一層,而後用this.refs.name和findDOMNode去獲取DOM。
總結
1. 可使用ref調用組件內子組件的實例方法,好比this.refs.myInput.focus();
2. refs 是訪問到組件內部 DOM 節點惟一可靠的方法。
3. refs 會自動銷燬對子組件的引用(當子組件刪除時)
注意事項
1. 不要在render或者render以前訪問refs
2. 不要濫用refs,好比只是用它來按照傳統的方式操做界面UI:找到DOM->更新DOM
組件間通訊
1. 父子組件間通訊
父子組件間通訊能夠經過props屬性來傳遞,在父組件中給子組件設置props,子組件就能夠經過props訪問到父組件的屬性和方法。
2. 非父子組件間通訊
使用全局事件Pub/Sub模式,在componentDidMount裏面訂閱事件,在componentWillUnmount裏面取消訂閱,當收到事件觸發的時候調用setState更新UI。