介紹
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é)果如下艾恼。
這里需要注意, 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)如圖
實(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è)面的分解
如下面的示例界面
相應(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"></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"></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"></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"></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"></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"></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"></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"></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:''
},
{
title: '聯(lián)系人',
url: 'contactsMain',
subTitle:'總共20個(gè)',
type:0,
icon:''
}
],
salesManagement: [
{
title: '活動(dòng)記錄',
url: '/actionList',
subTitle:'今天有20個(gè)詳情',
type:1,
icon:''
},
{
title: '數(shù)據(jù)看板',
url: '/dataBoard',
subTitle:'查看詳情',
type:1,
icon:''
}
],
salesSupportLine1: [
{
title: '快速委托',
url: '/FastDelegate',
subTitle:'查看詳情',
type:2,
icon:''
},
{
title: '通訊錄',
url: '/addressList',
subTitle:'財(cái)拓電商',
type:2,
icon:''
}
],
salesSupportLine2: [
{
title: '任務(wù)',
url: '/taskList',
subTitle:'即將過(guò)期2個(gè)',
type:2,
icon:''
},
{
title: '客戶調(diào)查',
url: 'surveyList',
subTitle:'查看詳情',
type:2,
icon:''
}
],
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