reactjs教程

介紹

React.js是什么

React是由工作在Facebook開發(fā)出來(lái)的用于開發(fā)用戶交互界面的JS庫(kù)劳翰。其源碼由Facebook和社區(qū)優(yōu)秀的程序員維護(hù),因此其背后有著非常強(qiáng)大的技術(shù)團(tuán)隊(duì)給予技術(shù)支持过椎。React帶來(lái)了很多新的東西,例如組件化鸡捐、JSX鞠鲜、虛擬DOM等。其提供的虛擬DOM使得我們渲染組件呈現(xiàn)非常之快脾猛,讓我們從頻繁操作DOM的繁重工作之中解脫撕彤。了解React的人都知道,它做的工作更多偏重于MVC中的V層猛拴,結(jié)合其它如Flux等一起羹铅,你可以非常容易構(gòu)建強(qiáng)大的應(yīng)用。

為什么使用React.js

React.js教程中一句話簡(jiǎn)潔的概括了其作用:We built React to solve one problem: building large applications with data that changes over time.就是構(gòu)建數(shù)據(jù)隨時(shí)間改變的大型應(yīng)用愉昆。
構(gòu)建那些數(shù)據(jù)會(huì)隨時(shí)間改變的大型應(yīng)用职员,做這些,React有兩個(gè)主要的特點(diǎn):
簡(jiǎn)單簡(jiǎn)單的表述任意時(shí)間點(diǎn)你的應(yīng)用應(yīng)該是什么樣子的跛溉,React將會(huì)自動(dòng)的管理UI界面更新當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候焊切。
聲明式在數(shù)據(jù)發(fā)生變化的時(shí)候扮授,React從概念上講與點(diǎn)擊了F5一樣,實(shí)際上它僅僅是更新了變化的一部分而已蛛蒙。React是關(guān)于構(gòu)造可重用組件的糙箍,實(shí)際上,使用React你做的僅僅是構(gòu)建組建牵祟。通過(guò)封裝深夯,使得組件代碼復(fù)用、測(cè)試以及關(guān)注點(diǎn)分離更加容易诺苹。

另外在React官網(wǎng)上咕晋,通過(guò)《Why did we build React?》為什么我們要建造React的文檔中還可以了解到以下四點(diǎn):
React不是一個(gè)MVC框架
React不使用模板
響應(yīng)式更新非常簡(jiǎn)單
HTML5僅僅是個(gè)開始

原理

Virtual DOM 虛擬DOM
傳統(tǒng)的web應(yīng)用,操作DOM一般是直接更新操作的收奔,但是我們知道DOM更新通常是比較昂貴的掌呜。而React為了盡可能減少對(duì)DOM的操作,提供了一種不同的而又強(qiáng)大的方式來(lái)更新DOM坪哄,代替直接的DOM操作质蕉。就是Virtual DOM
,一個(gè)輕量級(jí)的虛擬的DOM,就是React抽象出來(lái)的一個(gè)對(duì)象翩肌,描述dom應(yīng)該什么樣子的模暗,應(yīng)該如何呈現(xiàn)。通過(guò)這個(gè)Virtual DOM去更新真實(shí)的DOM念祭,由這個(gè)Virtual DOM管理真實(shí)DOM的更新兑宇。
為什么通過(guò)這多一層的Virtual DOM操作就能更快呢? 這是因?yàn)镽eact有個(gè)diff算法粱坤,更新Virtual DOM并不保證馬上影響真實(shí)的DOM隶糕,React會(huì)等到事件循環(huán)結(jié)束,然后利用這個(gè)diff算法站玄,通過(guò)當(dāng)前新的dom表述與之前的作比較枚驻,計(jì)算出最小的步驟更新真實(shí)的DOM。
Components 組件
在DOM樹上的節(jié)點(diǎn)被稱為元素株旷,在這里則不同再登,Virtual DOM上稱為commponent。Virtual DOM的節(jié)點(diǎn)就是一個(gè)完整抽象的組件灾常,它是由commponents組成霎冯。
State 和 Render
React是如何呈現(xiàn)真實(shí)的DOM铃拇,如何渲染組件钞瀑,什么時(shí)候渲染,怎么同步更新的慷荔,這就需要簡(jiǎn)單了解下State和Render了雕什。state屬性包含定義組件所需要的一些數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)生變化時(shí),將會(huì)調(diào)用Render重現(xiàn)渲染贷岸,這里只能通過(guò)提供的setState方法更新數(shù)據(jù)壹士。

基礎(chǔ)

環(huán)境

本文的代碼是基于ES5,ES6規(guī)范編寫,使用了Webpack偿警,Gulp躏救。數(shù)據(jù)流的處理并沒有使用Redux等專門的數(shù)據(jù)流處理框架,一般而言螟蒸,React.js需要配合Flux之類的數(shù)據(jù)流處理框架使用盒使,Redux可以看作是Flux思想的一種簡(jiǎn)化實(shí)現(xiàn)。

component

生命周期

React 組件就是一個(gè)狀態(tài)機(jī)七嫌,它接受兩個(gè)輸入?yún)?shù): this.props 和 this.state少办,返回一個(gè)虛擬DOM。
創(chuàng)建組建的方式如下:

var NotesList = React.createClass({  
  
getDefaultProps: function() {  
console.log("getDefaultProps");  
return {};  
},  
  
getInitialState: function() {  
console.log("geyInitialState");  
return {};  
},  
  
componentWillMount: function() {  
console.log("componentWillMount");  
},  
  
render: function() {  
console.log("render");  
return (  
<div>hello <strong>{this.props.name}</strong></div>  
);  
},  
  
componentDidMount: function() {  
console.log("componentDidMount");  
},  
  
componentWillRecieveProps: function() {  
console.log("componentWillRecieveProps");  
},  
componentWillUpdate: function() {  
console.log("componentWillUpdate");  
},  
componentDidUpdate: function() {  
console.log("componentDidUpdate");  
},  
  
});  
  
var list1 = React.render(  
<NotesList name='aaa'></NotesList>,  
document.getElementById("div1")  
);  
  
var list2 = React.render(  
<NotesList name='bbb'></NotesList>,  
document.getElementById("div2")  
);  

上述代碼的輸出是:
getDefaultProps
geyInitialState
componentWillMount
render
componentDidMount
geyInitialState
componentWillMount
render
componentDidMount

createClass

React組件是有 類 和 實(shí)例的區(qū)別的诵原,通過(guò) React.createClass 創(chuàng)建的是類

實(shí)例化

類創(chuàng)建完成之后英妓,就可以進(jìn)行實(shí)例化。
實(shí)例化一個(gè)類绍赛,由如下過(guò)程組成:

getInitialState: 獲取 this.state 的默認(rèn)值
componentWillMount: 在render之前調(diào)用此方法蔓纠,在render之前需要做的事情就在這里處理
render: 渲染并返回一個(gè)虛擬DOM
componentDidMount: 在render之后,react會(huì)使用render返回的虛擬DOM來(lái)創(chuàng)建真實(shí)DOM惹资,完成之后調(diào)用此方法贺纲。

其中有幾個(gè)點(diǎn)需要注意:

1,this.state 只存儲(chǔ)原始數(shù)據(jù)褪测,不要存儲(chǔ)計(jì)算后的數(shù)據(jù)
比如 this.state.time = 1433245642536猴誊,那么就不要再存一個(gè) this.state.timeString = ‘2015-06-02 19:47:22’ 因?yàn)檫@個(gè)是由 time 計(jì)算出來(lái)的,其實(shí)他不是一種state侮措,他只是 this.state.time 的一種展示方式而已懈叹。
這個(gè)應(yīng)該放在render中計(jì)算出來(lái):
<span>time: {this.formatTime(this.state.time)}</span>

2,componentWillMount 用來(lái)處理render之前的邏輯分扎,不要在render中處理業(yè)務(wù)邏輯澄成。
render就是一個(gè)模板的作用,他只處理和展示相關(guān)的邏輯畏吓,比如格式化時(shí)間這樣的墨状,如果有業(yè)務(wù)邏輯,那么要放在 componentWillMount 中執(zhí)行菲饼。
所以render中一定不會(huì)出現(xiàn)改變 state 之類的操作肾砂。

3,render返回的是虛擬DOM
所謂虛擬DOM宏悦,其實(shí)就是 React.DOM.div 之類的實(shí)例镐确,他就是一個(gè)JS對(duì)象包吝。render方法完成之后,真實(shí)的DOM并不存在源葫。

4诗越,componentDidMount 中處理和真實(shí)DOM相關(guān)的邏輯
這時(shí)候真實(shí)的DOM已經(jīng)渲染出來(lái),可以通過(guò) this.getDOMNode() 方法來(lái)使用了息堂。典型的場(chǎng)景就是可以在這里調(diào)用jquery插件嚷狞。

更新

當(dāng)組件實(shí)例化完成,就進(jìn)入了存在期荣堰,這時(shí)候一般會(huì)響應(yīng)用戶操作和父組件的更新來(lái)更新視圖感耙。

componentWillRecieveProps: 父組件或者通過(guò)組件的實(shí)例調(diào)用 setProps 改變當(dāng)前組件的 props 時(shí)調(diào)用。
shouldComponentUpdate: 是否需要更新持隧,慎用
componentWillUpdate: 調(diào)用 render方之前
render:
componentDidUpdate: 真實(shí)DOM已經(jīng)完成更新即硼。

銷毀

componentWillUnmount

組合與通信

組合

React組件是無(wú)法繼承的,即不存在 React.extend 之類的方法可以定義一個(gè)子類屡拨。
React推崇通過(guò)組合的方式來(lái)組織大規(guī)模的應(yīng)用只酥。
所以所謂父子組件,就和DOM中的父子元素一樣呀狼,他們是有從屬關(guān)系裂允,但沒有繼承關(guān)系。
比如:

var Team = React.createClass({   
 render: function() {   
     return <div>Team onwer is: <People name={this.props.name}></People></div>;   
 }});
var People = React.createClass({  
  render: function() {    
    return <span>{this.props.name}</span>;   
 }});

上述代碼創(chuàng)建了兩個(gè)組建哥艇,分別是Team和People绝编,其中Team在render方法中嵌入了People組建,這樣貌踏,People組建就成了Team的子組件十饥,而Team為父組件。

父子組件通信

組合起來(lái)很簡(jiǎn)單祖乳,那么父子組件怎么通信呢逗堵。
你可能會(huì)想通過(guò)事件來(lái)通信。React 竟然沒有提供一個(gè)自定義事件眷昆,它的事件僅僅用來(lái)處理DOM事件蜒秤,并沒有組件的自定義事件。
比如一個(gè)子組件是無(wú)法通過(guò) trigger(“hungry”) 之類的事件來(lái)通知父組件的亚斋。當(dāng)然作媚,你可以通過(guò)mixin之類的方式來(lái)給組件提供事件能力。
那么這樣帅刊,就只有一種方式可以讓子組件向父組件發(fā)送消息纸泡,就是 this.props 屬性。

var Team = React.createClass({   
 getSubComponentInfo(){   
     alert(this.refs.subComponent.subComponentInfo()) 
   },     
   parentInfo(){   
     return 'info from parent!' 
   },   
 render: function() {  
      return <div>Team onwer is: <People name={this.props.name} parentInfo={this.parentInfo} ref="subComponent"></People></div>;  
  }});
var People = React.createClass({  
  getParentComponentInfo(){   
     alert(this.props.parentInfo())  
  },   
     subComponentInfo(){   
     return 'info from sub!' 
   },    
    render: function() {  
      return <span>{this.props.name}</span>;  
  }});

上述代碼中厚掷,父組件把自己的parentInfo方法作為子組件的屬性傳遞給子組件

parentInfo={this.parentInfo}

子組件試圖調(diào)用父組件的方法弟灼,可以直接通過(guò)屬性拿到此方法調(diào)用

this.props.parentInfo()

父組件調(diào)用子組件中的方法更簡(jiǎn)單,直接給子組件設(shè)置一個(gè)ref屬性冒黑,然后通過(guò)這個(gè)ref屬性拿到子組件田绑,然后直接調(diào)用子組件中的方法即可。

思考

組件是reactjs中對(duì)于視圖分割的最小單元抡爹,每一個(gè)界面都是由多層的(當(dāng)然掩驱,也可以是一層)的組件嵌套而成的。

props與state

組件的用法與原生的 HTML 標(biāo)簽完全一致冬竟,可以任意加入屬性欧穴,比如 <HelloMessage name="John">,就是 HelloMessage 組件加入一個(gè) name屬性泵殴,值為 John涮帘。組件的屬性可以在組件類的 this.props對(duì)象上獲取,比如 name屬性就可以通過(guò) this.props.name讀取,值為John笑诅。

添加組件屬性调缨,有一個(gè)地方需要注意,就是 class屬性需要寫成 className吆你,for屬性需要寫成 htmlFor弦叶,這是因?yàn)?class和 for是 JavaScript 的保留字。

this.props
對(duì)象的屬性與組件的屬性一一對(duì)應(yīng)妇多,但是有一個(gè)例外伤哺,就是 this.props.children
屬性。它表示組件的所有子節(jié)點(diǎn)者祖。

var NotesList = React.createClass({
render: function() {
 return ( 
   <ol> {
          React.Children.map(this.props.children, function (child) {
               return <li>{child}</li>; 
           }) 
           } </ol>
      );
 }});
ReactDOM.render( 
  <NotesList>
      <span>hello</span>
      <span>world</span>
   </NotesList>, 
document.body);

上面代碼的 NoteList組件有兩個(gè) span子節(jié)點(diǎn)立莉,它們都可以通過(guò) this.props.children讀取多艇,運(yùn)行結(jié)果如下艾恼。

bg2015033110.png

這里需要注意, this.props.children的值有三種可能:如果當(dāng)前組件沒有子節(jié)點(diǎn)溯职,它就是 undefined;如果有一個(gè)子節(jié)點(diǎn)烂瘫,數(shù)據(jù)類型是 object媒熊;如果有多個(gè)子節(jié)點(diǎn),數(shù)據(jù)類型就是 array坟比。所以芦鳍,處理 this.props.children的時(shí)候要小心。React 提供一個(gè)工具方法 React.Children 來(lái)處理 this.props.children葛账。我們可以用 React.Children.map來(lái)遍歷子節(jié)點(diǎn)柠衅,而不用擔(dān)心 this.props.children的數(shù)據(jù)類型是 undefined還是 object。更多的 React.Children的方法籍琳,請(qǐng)參考官方文檔菲宴。

實(shí)際開發(fā)中贷祈,組件是不可能一成不變的(這基本相當(dāng)于數(shù)據(jù)是不可能一成不變的),舉一個(gè)很簡(jiǎn)單的例子喝峦,一個(gè)列表要展示遺傳從服務(wù)端獲取的數(shù)據(jù)势誊,那么,獲取之前列表是為空的谣蠢,獲取數(shù)據(jù)之后列表是有數(shù)據(jù)的粟耻,那么列表加載數(shù)據(jù)前后對(duì)應(yīng)兩種狀態(tài),React 的一大創(chuàng)新眉踱,就是將組件看成是一個(gè)狀態(tài)機(jī)挤忙,一開始有一個(gè)初始狀態(tài),然后用戶互動(dòng)谈喳,導(dǎo)致狀態(tài)變化册烈,從而觸發(fā)重新渲染 UI 。

組件的使命周期中有一個(gè)getInitialState方法婿禽,這個(gè)方法要返回一個(gè)對(duì)象茄厘,這個(gè)對(duì)象就是組件初始化時(shí)的初始狀態(tài),這個(gè)對(duì)象中的值可以直接通過(guò)this.state[.stateName]的形式直接引用谈宛。改變狀態(tài)可以調(diào)用setState方法次哈,此方法必須要傳入一個(gè)對(duì)象,也可以額外傳入一個(gè)function對(duì)象吆录,傳入的對(duì)象的屬性會(huì)添加到組件的狀態(tài)中窑滞,需要注意的是,傳入的對(duì)象并不會(huì)覆蓋原有對(duì)象恢筝,如果有同名的屬性哀卫,則原有屬性會(huì)被新的值替代。傳入的function對(duì)象會(huì)在狀態(tài)更新成功后執(zhí)行撬槽。

const ClientMain = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },
  
  getInitialState(){
    return {name:'jhon',age:20}
  },

  componentDidMount(){
    let self = this
    setTimeout(function () {
      self.setState({name:'tinker'})
    }此改,2000)
  },
  
  render(){
    //...省略代碼...
  }
})

上述代碼中,組件初始化時(shí)狀態(tài)中保存了兩個(gè)屬性侄柔,name和age共啃,值分別是jhon和20,在2秒后暂题,狀態(tài)被修改了移剪,name變?yōu)閠inker,age仍然是20.

下面的代碼是state在應(yīng)用中使用的一個(gè)簡(jiǎn)單場(chǎng)景薪者。


const ClientItem = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },

  onClickItem(){
  },

  handleTime(time){
    if(time.length >= 19){
      return time.substring(5,16)
    }
    return time
  },

  render(){
    let self = this;
    return(
      <div className="twoitem bline" onClick={self.onClickItem}>
        <span className="itemname">{self.props.name}</span>
        <span className="itemsubname">{self.props.time?
('創(chuàng)建時(shí)間: ' + self.handleTime(self.props.time)) : ''}</span>
      </div>
    )
  }
})

const ClientMain = React.createClass({
  contextTypes: {
    router: React.PropTypes.object.isRequired
  },

  componentDidMount(){
    this.getgetContactsList()
  },

  // 獲取客戶列表
  getContactsList(){
    HttpUtil.post(function (result,data) {
      if (result){
        self.setState({clientItems:data.list})
      }
    })
  },
  
  render(){
    let self = this;
    let clientItem = [];
    if (this.state && this.state.clientItems){
      if (this.state.clientItems.length > 0){
        clientItem = this.state.clientItems.map((item,index) =>{
          return(
            <ClientItem saveState={self.saveState} key={index} name={item.customerName}
 time={item.createTime} userId={item.id}/>
          )
        })
      }
    }
    return(
      <div>
        <div className={clientItem.length == 0?"hide":"itemgroup p15"}>
          {clientItem}
        </div>
        <div className={clientItem.length == 0?"unBacklog":"hide"}>
          暫無(wú)數(shù)據(jù)
        </div>
      </div>
    )
  }
})

export default ClientMain;

分析上述代碼的執(zhí)行過(guò)程纵苛,開始的時(shí)候,ClientMain 加載的時(shí)候,在第一次加載時(shí)攻人,在render方法中取试,由于本身并沒有初始的state,所以this.state && this.state.clientItems為false,則clientItem長(zhǎng)度為0怀吻,則顯示暫無(wú)數(shù)據(jù)瞬浓,在post請(qǐng)求完成后,如果有有效數(shù)據(jù)烙博,則
this.state && this.state.clientItems為true,clientItem中被push進(jìn)有效數(shù)據(jù)烟逊,則數(shù)據(jù)會(huì)在列表中展示渣窜。

屬性的驗(yàn)證

組件的屬性可以接受任意值,字符串宪躯、對(duì)象乔宿、函數(shù)等等都可以。有時(shí)访雪,我們需要一種機(jī)制详瑞,驗(yàn)證別人使用組件時(shí),提供的參數(shù)是否符合要求臣缀。

組件類的PropTypes屬性坝橡,就是用來(lái)驗(yàn)證組件實(shí)例的屬性是否符合要求

var MyTitle = React.createClass({ 
propTypes: { title: React.PropTypes.string.isRequired, }, 
render: function() {
 return <h1> {this.props.title} </h1>; 
}});

上面的Mytitle組件有一個(gè)title屬性。PropTypes告訴 React精置,這個(gè) title屬性是必須的计寇,而且它的值必須是字符串。現(xiàn)在脂倦,我們?cè)O(shè)置 title屬性的值是一個(gè)數(shù)值番宁。

var data = 123;
ReactDOM.render(
 <MyTitle title={data} />,
 document.body);

這樣一來(lái),title屬性就通不過(guò)驗(yàn)證了赖阻〉海控制臺(tái)會(huì)顯示一行錯(cuò)誤信息。

Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.

更多的PropTypes設(shè)置火欧,可以查看官方文檔網(wǎng)上資源棋电。

思考

React組件對(duì)象是有setProps方法的,但是官方已經(jīng)不推薦使用此方法苇侵,所以這個(gè)方法在未來(lái)版本中是有可能廢除的离陶。一般來(lái)說(shuō),React應(yīng)用的界面設(shè)計(jì)是多層的衅檀,頂層的組件包含狀態(tài)招刨,而底層的組件則是不包含狀態(tài)的,它只渲染傳進(jìn)來(lái)的props數(shù)據(jù)哀军,這樣所有數(shù)據(jù)的維護(hù)都統(tǒng)一由頂層的組件維護(hù)沉眶,這就加強(qiáng)了可維護(hù)性打却,數(shù)據(jù)也更好追蹤(但是不使用Redux之類的數(shù)據(jù)流管理插件,其實(shí)還是不容易追蹤)谎倔,而state的設(shè)計(jì)也是基本和組件的分層相對(duì)應(yīng)的柳击。

mixins

mixins是定義不同的組件使用到的共同的方法而存在的。你可以在mixins里定義一些無(wú)關(guān)業(yè)務(wù)邏輯的方法片习,例如fackbook官方推薦的對(duì)于setTimeout的處理

var SetTimeoutMixin = { 
   componentWillMount: function() {   
     this.timeouts = [];  
  },   
 setTimeout: function() {   
     this.timeouts.push(setTimeout.apply(null, arguments));   
 },  
  clearTimeouts: function() {     
   this.timeouts.forEach(clearTimeout);  
  }, 
   componentWillUnmount: function() {  
      this.clearTimeouts(); 
   }};
export default SetTimeoutMixin;

上述代碼中的生命周期方法不會(huì)覆蓋組件中同名的生命周期方法捌肴,而是會(huì)在組件的同名生命周期方法之前執(zhí)行,例如藕咏,組件在加載時(shí)状知,會(huì)先執(zhí)行mixin中componentWillMount方法,再執(zhí)行組件本身的componentWillMount方法孽查。示例代碼中新建 了一個(gè)timeouts數(shù)組饥悴,setTimeout方法中,在數(shù)組里存放所有的要執(zhí)行的timeout對(duì)象盲再,clearTimeouts方法中遍歷數(shù)組西设,調(diào)用clearTimeout清除已保存的對(duì)象,componentWillUnmount方法保證了在組件卸載后之前存放的timeout一定被清除掉了答朋。

自定義組件引入mixin只需要像如下代碼一樣在createClass時(shí)添加到mixins數(shù)組里就OK

var SetTimeoutMixin = require('...');
React.createClass({ 
mixins: [SetTimeoutMixin ],
 render: function() { 
return <div className={this.props.className}>foo</div>; 
}});

獲取節(jié)點(diǎn)

組件并不是真實(shí)的 DOM 節(jié)點(diǎn)贷揽,而是存在于內(nèi)存之中的一種數(shù)據(jù)結(jié)構(gòu),叫做虛擬 DOM (virtual DOM)梦碗。只有當(dāng)它插入文檔以后擒滑,才會(huì)變成真實(shí)的 DOM 。根據(jù) React 的設(shè)計(jì)叉弦,所有的 DOM 變動(dòng)丐一,都先在虛擬 DOM 上發(fā)生,然后再將實(shí)際發(fā)生變動(dòng)的部分淹冰,反映在真實(shí) DOM上库车,這種算法叫做 DOM diff ,它可以極大提高網(wǎng)頁(yè)的性能表現(xiàn)樱拴。
但是柠衍,有時(shí)需要從組件獲取真實(shí) DOM 的節(jié)點(diǎn),這時(shí)就要用到 ref屬性晶乔。

var MyComponent = React.createClass({
  handleClick: function() {
    this.refs.myTextInput.focus();
  },
  render: function() {
    return (
      <div>
        <input type="text" ref="myTextInput" />
        <input type="button" value="Focus the text input" onClick={this.handleClick} />
      </div>
    );
  }
});

ReactDOM.render(
  <MyComponent />,
  document.getElementById('example')
);

上面代碼中珍坊,組件 MyComponent的子節(jié)點(diǎn)有一個(gè)文本輸入框,用于獲取用戶的輸入正罢。這時(shí)就必須獲取真實(shí)的 DOM 節(jié)點(diǎn)阵漏,虛擬 DOM 是拿不到用戶輸入的。為了做到這一點(diǎn),文本輸入框必須有一個(gè) ref屬性履怯,然后 this.refs.[refName]就會(huì)返回這個(gè)真實(shí)的 DOM 節(jié)點(diǎn)回还。需要注意的是,由于 this.refs.[refName]屬性獲取的是真實(shí) DOM 叹洲,所以必須等到虛擬 DOM 插入文檔以后柠硕,才能使用這個(gè)屬性,否則會(huì)報(bào)錯(cuò)运提。上面代碼中蝗柔,通過(guò)為組件指定 Click事件的回調(diào)函數(shù),確保了只有等到真實(shí) DOM 發(fā)生 Click事件之后民泵,才會(huì)讀取 this.refs.[refName]屬性癣丧。
React 組件支持很多事件,除了 Click事件以外洪灯,還有 KeyDown坎缭、Copy竟痰、Scroll等签钩,完整的事件清單請(qǐng)查看官方文檔

因?yàn)镽eact是可以和其他的JS框架混合使用的坏快,所以可以在React中使用其它插件獲取DOM的方式铅檩,例如Jquery獲取DOM的方式。

項(xiàng)目搭建

建議參考官方入門教程推薦的項(xiàng)目創(chuàng)建方式

項(xiàng)目框架

由于項(xiàng)目用到了webpack莽鸿,gulp昧旨,react-router等其他框架,所以直接給出了完整的空框架祥得,可以直接下載使用兔沃。
項(xiàng)目結(jié)構(gòu)如圖

1.png

實(shí)例解析

UI給出靜態(tài)頁(yè)面后,開發(fā)的第一個(gè)工作就是頁(yè)面的分解级及,按照一定的規(guī)律把界面分為不同的(非常大可能有嵌套的關(guān)系)的組件乒疏,界面的分層一般都對(duì)應(yīng)了數(shù)據(jù)的層次結(jié)構(gòu),甚至來(lái)說(shuō)必須如此饮焦,這樣數(shù)據(jù)在傳遞給組件時(shí)很容易操作怕吴,另外如果搭配redux使用,數(shù)據(jù)管理也更容易县踢。關(guān)于Redux的信息转绷,請(qǐng)參考Redux教程

靜態(tài)頁(yè)面的分解

如下面的示例界面

QQ圖片20161129093023.png

相應(yīng)的界面代碼如下

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no">
<meta name="description" content="">
<meta http-equiv="x-dns-prefetch-control" content="on">

<title>CRM</title>
<link href="../css/css.css" rel="stylesheet" type="text/css">
</head>

<body>
<section class="crmhome mt10">
        <h1 class="green">客戶關(guān)系</h1>
        <div class="mainarea">
              <a href="customer_list.html">
              <div class="left w44 rline p5p">
                  <i class="iconfont left green crmhomeicon">&#xe60a;</i>
                  <div class="left hometext">
                      <span class="hometitle">客戶</span>
                      <span class="homesubtitle">總共 50 個(gè)</span>
                  </div>
              </div>
              </a>
              <div class="left w44 p5p">
                  <i class="iconfont left green crmhomeicon">&#xe60b;</i>
                  <div class="left hometext">
                      <span class="hometitle">聯(lián)系人</span>
                      <span class="homesubtitle">總共 150 個(gè)</span>
                  </div>
              </div>
        </div>
</section>
<section class="crmhome mt10">
        <h1 class="purple">銷售管理</h1>
        <div class="mainarea">
              <div class="left w44 rline p5p">
                  <i class="iconfont left purple crmhomeicon">&#xe60c;</i>
                  <div class="left hometext">
                      <span class="hometitle">活動(dòng)記錄</span>
                      <span class="homesubtitle">今天有20個(gè)更新</span>
                  </div>
              </div>
              <div class="left w44 p5p">
                  <i class="iconfont left purple crmhomeicon">&#xe60d;</i>
                  <div class="left hometext">
                      <span class="hometitle">數(shù)據(jù)看板</span>
                      <span class="homesubtitle">查看詳情</span>
                  </div>
              </div>
        </div>
</section>
<section class="crmhome mt10">
        <h1 class="yellow">銷售支持</h1>
        <div class="mainarea bline">
              <div class="left w44 rline p5p">
                  <i class="iconfont left yellow crmhomeicon">&#xe611;</i>
                  <div class="left hometext">
                      <span class="hometitle">快速委托</span>
                      <span class="homesubtitle">查看詳情</span>
                  </div>
              </div>
              <div class="left w44 p5p">
                  <i class="iconfont left yellow crmhomeicon">&#xe612;</i>
                  <div class="left hometext">
                      <span class="hometitle">通訊錄</span>
                      <span class="homesubtitle">財(cái)拓電子商務(wù)</span>
                  </div>
              </div>
        </div>
        <div class="mainarea">
              <div class="left w44 rline p5p">
                  <i class="iconfont left yellow crmhomeicon">&#xe61d;</i>
                  <div class="left hometext">
                      <span class="hometitle">任務(wù)</span>
                      <span class="homesubtitle">即將過(guò)期 2 個(gè)</span>
                  </div>
              </div>
              <div class="left w44 p5p">
                  <i class="iconfont left yellow crmhomeicon">&#xe613;</i>
                  <div class="left hometext">
                      <span class="hometitle">客戶調(diào)查</span>
                      <span class="homesubtitle">查看詳情</span>
                  </div>
              </div>
        </div>
</section>
</body>
</html>

界面可以看成由三個(gè)列表組成,第一個(gè)列表和第二個(gè)各有兩個(gè)元素硼啤,第三個(gè)列表有四個(gè)元素议经。為了減少組件層次,最外層沒有設(shè)置一個(gè)容器存放三個(gè)列表的形式,而是把三個(gè)列表看成一個(gè)整體爸业,最外層組件render方法返回如下

return(
            <div>
                <div style={{height:"10px"}}></div>
                <section className="crmhome">
                    <h1 className="green">客戶關(guān)系</h1>
                    <div className="mainarea">
                        {cusRel}
                    </div>
                </section>
                <section className="crmhome mt10">
                    <h1 className="purple">銷售管理</h1>
                    <div className="mainarea">
                        {salMan}
                    </div>
                </section>
                <section className="crmhome mt10">
                    <h1 className="yellow">銷售支持</h1>
                    <div className="mainarea bline">
                        {salSup1}
                    </div>
                    <div className="mainarea">
                        {salSup2}
                    </div>
                </section>
            </div>
        )

其中的cusRel其骄,salMan,salSup1,salSup2是列表元素?cái)?shù)組,它們生成的方式如下

let self = this;
        let cusRel;
        cusRel = String.customerRelationship.map(function(item,index){
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(0,index)} type={item.type}/>
            )
        })
        let salMan;
        salMan = String.salesManagement.map(function (item,index) {
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(1,index)} type={item.type}/>
            )
        })
        let salSup1;
        salSup1 = String.salesSupportLine1.map(function (item,index) {
            return(
                <MainItem
                    key={index} position={index} icon= {item.icon} url={item.url}
                    title={item.title} subTitle={self.getHint(2,index)} type={item.type}/>
            )
        })
        let salSup2;
        salSup2 = String.salesSupportLine2.map(function (item,index) {
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(3,index)} type={item.type}/>
            )
        })

代碼中的MainItem就是列表中單個(gè)元素組件扯旷,其中包含一個(gè)圖標(biāo)拯爽,一個(gè)標(biāo)題和一行文字,String中的數(shù)據(jù)是每個(gè)元素中需要的數(shù)據(jù)

customerRelationship: [
        {
            title: '客戶',
            url: 'client',
            subTitle:'總共50個(gè)',
            type:0,
            icon:'&#xe60a;'
        },
        {
            title: '聯(lián)系人',
            url: 'contactsMain',
            subTitle:'總共20個(gè)',
            type:0,
            icon:'&#xe60b;'
        }
    ],

    salesManagement: [
        {
            title: '活動(dòng)記錄',
            url: '/actionList',
            subTitle:'今天有20個(gè)詳情',
            type:1,
            icon:'&#xe60c;'
        },
        {
            title: '數(shù)據(jù)看板',
            url: '/dataBoard',
            subTitle:'查看詳情',
            type:1,
            icon:'&#xe60d;'
        }
    ],

    salesSupportLine1: [
        {
            title: '快速委托',
            url: '/FastDelegate',
            subTitle:'查看詳情',
            type:2,
            icon:'&#xe611;'

        },
        {
            title: '通訊錄',
            url: '/addressList',
            subTitle:'財(cái)拓電商',
            type:2,
            icon:'&#xe612;'
        }
    ],
    salesSupportLine2: [
        {
            title: '任務(wù)',
            url: '/taskList',
            subTitle:'即將過(guò)期2個(gè)',
            type:2,
            icon:'&#xe61d;'
        },
        {
            title: '客戶調(diào)查',
            url: 'surveyList',
            subTitle:'查看詳情',
            type:2,
            icon:'&#xe613;'
        }
    ],

MainItem的代碼如下

const MainItem = React.createClass({
    contextTypes: {
        router: React.PropTypes.object.isRequired
    },

    onClickItem(){
        let self = this;
    },
    getType(){
        if (this.props.type || this.props.type == 0){
            if(this.props.type == 0){
                return 'iconfont left green crmhomeicon'
            }else if (this.props.type == 1){
                return 'iconfont left purple crmhomeicon'
            }else if (this.props.type == 2) {
                return 'iconfont left yellow crmhomeicon'
            }
        }
    },
    render(){
        let remainder = this.props.position % 2;
        return(
            <a onClick={this.onClickItem}>
                <div className={(remainder == 0)?"left w44 rline p5p":"left w44 p5p"}>
                    <i className={this.getType()} dangerouslySetInnerHTML={{__html: this.props.icon}}/>
                    <div className="left hometext">
                        <span className="hometitle">{this.props.title?this.props.title:''}</span>
                        <span className="homesubtitle">{this.props.subTitle?this.props.subTitle:''}</span>
                    </div>
                </div>
            </a>
        )
    }
})

這樣頁(yè)面在加載的時(shí)候就會(huì)出現(xiàn)所要的效果钧忽,最后把真?zhèn)€界面的完整代碼奉上

import React from 'react';
import String from '../Helper/Strings';

/**
 * 主頁(yè)面的每一個(gè)列表單元
 *
 * 屬性
 *
 *
 * url 要跳轉(zhuǎn)的界面路徑
 * key 在列表中的索引
 * title 顯示的標(biāo)題
 * subTitle 顯示的子標(biāo)題
 */
const MainItem = React.createClass({
    contextTypes: {
        router: React.PropTypes.object.isRequired
    },

    onClickItem(){
        let self = this;
    },
    getType(){
        if (this.props.type || this.props.type == 0){
            if(this.props.type == 0){
                return 'iconfont left green crmhomeicon'
            }else if (this.props.type == 1){
                return 'iconfont left purple crmhomeicon'
            }else if (this.props.type == 2) {
                return 'iconfont left yellow crmhomeicon'
            }
        }
    },
    render(){
        let remainder = this.props.position % 2;
        return(
            <a onClick={this.onClickItem}>
                <div className={(remainder == 0)?"left w44 rline p5p":"left w44 p5p"}>
                    <i className={this.getType()} dangerouslySetInnerHTML={{__html: this.props.icon}}/>
                    <div className="left hometext">
                        <span className="hometitle">{this.props.title?this.props.title:''}</span>
                        <span className="homesubtitle">{this.props.subTitle?this.props.subTitle:''}</span>
                    </div>
                </div>
            </a>
        )
    }
})

const Main = React.createClass({

    componentDidMount(){

    },

    getHint(type,index){
        let self = this;
        if (!self.state){
            return ''
        }
        switch (type){
            case 0:
                if (index== 0){
                    if (typeof(self.state.customer) != 'undefined'){
                        return '總共' + self.state.customer + '個(gè)';
                    }
                    return '';
                } else if (index == 1){
                    if (typeof(self.state.contacts) != 'undefined'){
                        return '總共' + self.state.contacts + '個(gè)';
                    }
                    return '';
                }
                break
            case 1:
                if (index== 0){
                    if (typeof(self.state.activityUpdateCount) != 'undefined'){
                        return '今天有' + self.state.activityUpdateCount + '個(gè)更新';
                    }
                    return '';
                } else if (index == 1){
                    return '查看詳情';
                }
                break
            case 2:
                if (index== 0){
                    return '查看詳情';
                } else if (index == 1){
                    return self.state.companyName;
                }
                break
            case 3:
                if (index== 0){
                    if (typeof(self.state.missionSoonComplate) != 'undefined'){
                        return '待辦' + self.state.missionSoonComplate + '個(gè)';
                    }
                    return '';
                } else if (index == 1){
                    return '查看詳情';
                }
                break
        }
    },

    render(){
        let self = this;
        let cusRel;
        cusRel = String.customerRelationship.map(function(item,index){
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(0,index)} type={item.type}/>
            )
        })
        let salMan;
        salMan = String.salesManagement.map(function (item,index) {
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(1,index)} type={item.type}/>
            )
        })
        let salSup1;
        salSup1 = String.salesSupportLine1.map(function (item,index) {
            return(
                <MainItem
                    key={index} position={index} icon= {item.icon} url={item.url}
                    title={item.title} subTitle={self.getHint(2,index)} type={item.type}/>
            )
        })
        let salSup2;
        salSup2 = String.salesSupportLine2.map(function (item,index) {
            return(
                <MainItem key={index} position={index} icon= {item.icon} url={item.url}
                          title={item.title} subTitle={self.getHint(3,index)} type={item.type}/>
            )
        })
        return(
            <div>
                <div style={{height:"10px"}}></div>
                <section className="crmhome">
                    <h1 className="green">客戶關(guān)系</h1>
                    <div className="mainarea">
                        {cusRel}
                    </div>
                </section>
                <section className="crmhome mt10">
                    <h1 className="purple">銷售管理</h1>
                    <div className="mainarea">
                        {salMan}
                    </div>
                </section>
                <section className="crmhome mt10">
                    <h1 className="yellow">銷售支持</h1>
                    <div className="mainarea bline">
                        {salSup1}
                    </div>
                    <div className="mainarea">
                        {salSup2}
                    </div>
                </section>
            </div>
        )
    }
})

export default Main;

路由與界面的跳轉(zhuǎn)

由于項(xiàng)目引入了React-Router毯炮,Router控制著界面的跳轉(zhuǎn),所以耸黑,界面的配置只需要在Router里配置就好桃煎,項(xiàng)目目錄下有一個(gè)App.jsx的文件,其中的Router節(jié)點(diǎn)就是用來(lái)配置路由的

<Router history={hashHistory}>
        <Route path='/' component={App}>
            <IndexRoute component={HomePage}/>
            <Route path='homePage' component={HomePage}/>
            <Route path='todoApp' component={TodoApp}/>
        </Route>
    </Router>

其中的IndexRoute是應(yīng)用進(jìn)入時(shí)的默認(rèn)界面大刊,Route節(jié)點(diǎn)對(duì)應(yīng)就是應(yīng)用中的一個(gè)界面为迈,代碼中,說(shuō)明應(yīng)用中只有兩個(gè)界面缺菌,HomePage和TodoApp葫辐,其中HomePage是進(jìn)入應(yīng)用后第一個(gè)展示的界面。訪問(wèn)Route中的path就是訪問(wèn)時(shí)頁(yè)面對(duì)應(yīng)的路徑伴郁,如代碼中TodoApp的path是todoApp耿战,那么在瀏覽器中打出地址http://localhost:3939/#/todoApp可以訪問(wèn)此界面,代碼中的跳轉(zhuǎn)也需要使用配置的path跳轉(zhuǎn)焊傅。
代碼中跳轉(zhuǎn)的方法很簡(jiǎn)單剂陡,如果從A組件中跳轉(zhuǎn)到下一個(gè)界面,那么在A組件中狐胎,要引入下面的代碼

contextTypes: {    router: React.PropTypes.object.isRequired}

然后在組件中通過(guò)如下形式代碼就可跳轉(zhuǎn)

this.context.router.push('todoApp')

為完整形式如下

import React from 'react';

const HomePage = React.createClass({
    contextTypes: {
        router: React.PropTypes.object.isRequired
    },

    getInitialState(){
        return {
            info: {
                phoneQtyAll: 2,
                regQtyAll: 3,
                regQtyAllTime: 4,
                tradeWeightAll: 6,
                visitQtyAll: 7
            }
        }
    },

    gotoNextPage(){
        this.context.router.push('todoApp')
    },

    render(){
        return (
            <button onClick={this.gotoNextPage}>跳轉(zhuǎn)按鈕</button>
        )
    }
});


export default HomePage;

這樣鸭栖,從HomePage中就可以跳轉(zhuǎn)到TodoApp界面

界面間跳轉(zhuǎn)攜帶數(shù)據(jù)

由于項(xiàng)目中并沒有使用Redux之類的數(shù)據(jù)流框架,所以數(shù)據(jù)需要開發(fā)者自己管理了握巢,設(shè)想從A界面跳轉(zhuǎn)到B界面晕鹊,數(shù)據(jù)的流轉(zhuǎn)方式主要是A存取,B讀取镜粤,B消費(fèi)后刪除捏题。所以數(shù)據(jù)存儲(chǔ)使用sessionStorage。

參考
http://www.reibang.com/p/ae482813b791
http://blog.csdn.net/lihongxun945/article/category/5195241
http://www.reactjs.cn/react/
React官方教程
http://www.reactjs.cn/react/docs/getting-started-zh-CN.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肉渴,一起剝皮案震驚了整個(gè)濱河市公荧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌同规,老刑警劉巖循狰,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窟社,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡绪钥,警方通過(guò)查閱死者的電腦和手機(jī)灿里,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)程腹,“玉大人匣吊,你說(shuō)我怎么就攤上這事〈缌剩” “怎么了色鸳?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)见转。 經(jīng)常有香客問(wèn)我命雀,道長(zhǎng),這世上最難降的妖魔是什么斩箫? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任吏砂,我火速辦了婚禮,結(jié)果婚禮上乘客,老公的妹妹穿的比我還像新娘狐血。我一直安慰自己,他們只是感情好寨典,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布氛雪。 她就那樣靜靜地躺著房匆,像睡著了一般耸成。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浴鸿,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天井氢,我揣著相機(jī)與錄音,去河邊找鬼岳链。 笑死花竞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掸哑。 我是一名探鬼主播约急,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼苗分!你這毒婦竟也來(lái)了厌蔽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤摔癣,失蹤者是張志新(化名)和其女友劉穎奴饮,沒想到半個(gè)月后纬向,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡戴卜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年逾条,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片投剥。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡师脂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出江锨,到底是詐尸還是另有隱情危彩,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布泳桦,位于F島的核電站汤徽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏灸撰。R本人自食惡果不足惜谒府,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浮毯。 院中可真熱鬧完疫,春花似錦、人聲如沸债蓝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)饰迹。三九已至芳誓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啊鸭,已是汗流浹背锹淌。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赠制,地道東北人赂摆。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像钟些,于是被迫代替她去往敵國(guó)和親烟号。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容