介紹
React.js是什么
React是由工作在Facebook開發(fā)出來的用于開發(fā)用戶交互界面的JS庫。其源碼由Facebook和社區(qū)優(yōu)秀的程序員維護(hù),因此其背后有著非常強(qiáng)大的技術(shù)團(tuán)隊(duì)給予技術(shù)支持。React帶來了很多新的東西,例如組件化、JSX、虛擬DOM等。其提供的虛擬DOM使得我們渲染組件呈現(xiàn)非常之快,讓我們從頻繁操作DOM的繁重工作之中解脫。了解React的人都知道,它做的工作更多偏重于MVC中的V層,結(jié)合其它如Flux等一起,你可以非常容易構(gòu)建強(qiáng)大的應(yīng)用。
為什么使用React.js
React.js教程中一句話簡潔的概括了其作用: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):
簡單簡單的表述任意時(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)建組建。通過封裝,使得組件代碼復(fù)用、測試以及關(guān)注點(diǎn)分離更加容易。
另外在React官網(wǎng)上,通過《Why did we build React?》為什么我們要建造React的文檔中還可以了解到以下四點(diǎn):
React不是一個(gè)MVC框架
React不使用模板
響應(yīng)式更新非常簡單
HTML5僅僅是個(gè)開始
原理
Virtual DOM 虛擬DOM
傳統(tǒng)的web應(yīng)用,操作DOM一般是直接更新操作的,但是我們知道DOM更新通常是比較昂貴的。而React為了盡可能減少對DOM的操作,提供了一種不同的而又強(qiáng)大的方式來更新DOM,代替直接的DOM操作。就是Virtual DOM
,一個(gè)輕量級的虛擬的DOM,就是React抽象出來的一個(gè)對象,描述dom應(yīng)該什么樣子的,應(yīng)該如何呈現(xiàn)。通過這個(gè)Virtual DOM去更新真實(shí)的DOM,由這個(gè)Virtual DOM管理真實(shí)DOM的更新。
為什么通過這多一層的Virtual DOM操作就能更快呢? 這是因?yàn)镽eact有個(gè)diff算法,更新Virtual DOM并不保證馬上影響真實(shí)的DOM,React會(huì)等到事件循環(huán)結(jié)束,然后利用這個(gè)diff算法,通過當(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í)候渲染,怎么同步更新的,這就需要簡單了解下State和Render了。state屬性包含定義組件所需要的一些數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)生變化時(shí),將會(huì)調(diào)用Render重現(xiàn)渲染,這里只能通過提供的setState方法更新數(shù)據(jù)。
基礎(chǔ)
環(huán)境
本文的代碼是基于ES5,ES6規(guī)范編寫,使用了Webpack,Gulp。數(shù)據(jù)流的處理并沒有使用Redux等專門的數(shù)據(jù)流處理框架,一般而言,React.js需要配合Flux之類的數(shù)據(jù)流處理框架使用,Redux可以看作是Flux思想的一種簡化實(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ū)別的,通過 React.createClass 創(chuàng)建的是類
實(shí)例化
類創(chuàng)建完成之后,就可以進(jìn)行實(shí)例化。
實(shí)例化一個(gè)類,由如下過程組成:
getInitialState: 獲取 this.state 的默認(rèn)值
componentWillMount: 在render之前調(diào)用此方法,在render之前需要做的事情就在這里處理
render: 渲染并返回一個(gè)虛擬DOM
componentDidMount: 在render之后,react會(huì)使用render返回的虛擬DOM來創(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ì)算出來的,其實(shí)他不是一種state,他只是 this.state.time 的一種展示方式而已。
這個(gè)應(yīng)該放在render中計(jì)算出來:
<span>time: {this.formatTime(this.state.time)}</span>
2,componentWillMount 用來處理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對象。render方法完成之后,真實(shí)的DOM并不存在。
4,componentDidMount 中處理和真實(shí)DOM相關(guān)的邏輯
這時(shí)候真實(shí)的DOM已經(jīng)渲染出來,可以通過 this.getDOMNode() 方法來使用了。典型的場景就是可以在這里調(diào)用jquery插件。
更新
當(dāng)組件實(shí)例化完成,就進(jìn)入了存在期,這時(shí)候一般會(huì)響應(yīng)用戶操作和父組件的更新來更新視圖。
componentWillRecieveProps: 父組件或者通過組件的實(shí)例調(diào)用 setProps 改變當(dāng)前組件的 props 時(shí)調(diào)用。
shouldComponentUpdate: 是否需要更新,慎用
componentWillUpdate: 調(diào)用 render方之前
render:
componentDidUpdate: 真實(shí)DOM已經(jīng)完成更新。
銷毀
componentWillUnmount
組合與通信
組合
React組件是無法繼承的,即不存在 React.extend 之類的方法可以定義一個(gè)子類。
React推崇通過組合的方式來組織大規(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為父組件。
父子組件通信
組合起來很簡單,那么父子組件怎么通信呢。
你可能會(huì)想通過事件來通信。React 竟然沒有提供一個(gè)自定義事件,它的事件僅僅用來處理DOM事件,并沒有組件的自定義事件。
比如一個(gè)子組件是無法通過 trigger(“hungry”) 之類的事件來通知父組件的。當(dāng)然,你可以通過mixin之類的方式來給組件提供事件能力。
那么這樣,就只有一種方式可以讓子組件向父組件發(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)用父組件的方法,可以直接通過屬性拿到此方法調(diào)用
this.props.parentInfo()
父組件調(diào)用子組件中的方法更簡單,直接給子組件設(shè)置一個(gè)ref屬性,然后通過這個(gè)ref屬性拿到子組件,然后直接調(diào)用子組件中的方法即可。
思考
組件是reactjs中對于視圖分割的最小單元,每一個(gè)界面都是由多層的(當(dāng)然,也可以是一層)的組件嵌套而成的。
props與state
組件的用法與原生的 HTML 標(biāo)簽完全一致,可以任意加入屬性,比如 <HelloMessage name="John">,就是 HelloMessage 組件加入一個(gè) name屬性,值為 John。組件的屬性可以在組件類的 this.props對象上獲取,比如 name屬性就可以通過 this.props.name讀取,值為John。
添加組件屬性,有一個(gè)地方需要注意,就是 class屬性需要寫成 className,for屬性需要寫成 htmlFor,這是因?yàn)?class和 for是 JavaScript 的保留字。
this.props
對象的屬性與組件的屬性一一對應(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),它們都可以通過 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 來處理 this.props.children。我們可以用 React.Children.map來遍歷子節(jié)點(diǎn),而不用擔(dān)心 this.props.children的數(shù)據(jù)類型是 undefined還是 object。更多的 React.Children的方法,請參考官方文檔。
實(shí)際開發(fā)中,組件是不可能一成不變的(這基本相當(dāng)于數(shù)據(jù)是不可能一成不變的),舉一個(gè)很簡單的例子,一個(gè)列表要展示遺傳從服務(wù)端獲取的數(shù)據(jù),那么,獲取之前列表是為空的,獲取數(shù)據(jù)之后列表是有數(shù)據(jù)的,那么列表加載數(shù)據(jù)前后對應(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è)對象,這個(gè)對象就是組件初始化時(shí)的初始狀態(tài),這個(gè)對象中的值可以直接通過this.state[.stateName]的形式直接引用。改變狀態(tài)可以調(diào)用setState方法,此方法必須要傳入一個(gè)對象,也可以額外傳入一個(gè)function對象,傳入的對象的屬性會(huì)添加到組件的狀態(tài)中,需要注意的是,傳入的對象并不會(huì)覆蓋原有對象,如果有同名的屬性,則原有屬性會(huì)被新的值替代。傳入的function對象會(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è)簡單場景。
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"}>
暫無數(shù)據(jù)
</div>
</div>
)
}
})
export default ClientMain;
分析上述代碼的執(zhí)行過程,開始的時(shí)候,ClientMain 加載的時(shí)候,在第一次加載時(shí),在render方法中,由于本身并沒有初始的state,所以this.state && this.state.clientItems為false,則clientItem長度為0,則顯示暫無數(shù)據(jù),在post請求完成后,如果有有效數(shù)據(jù),則
this.state && this.state.clientItems為true,clientItem中被push進(jìn)有效數(shù)據(jù),則數(shù)據(jù)會(huì)在列表中展示。
屬性的驗(yàn)證
組件的屬性可以接受任意值,字符串、對象、函數(shù)等等都可以。有時(shí),我們需要一種機(jī)制,驗(yàn)證別人使用組件時(shí),提供的參數(shù)是否符合要求。
組件類的PropTypes屬性,就是用來驗(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)在,我們設(shè)置 title屬性的值是一個(gè)數(shù)值。
var data = 123;
ReactDOM.render(
<MyTitle title={data} />,
document.body);
這樣一來,title屬性就通不過驗(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組件對象是有setProps方法的,但是官方已經(jīng)不推薦使用此方法,所以這個(gè)方法在未來版本中是有可能廢除的。一般來說,React應(yīng)用的界面設(shè)計(jì)是多層的,頂層的組件包含狀態(tài),而底層的組件則是不包含狀態(tài)的,它只渲染傳進(jìn)來的props數(shù)據(jù),這樣所有數(shù)據(jù)的維護(hù)都統(tǒng)一由頂層的組件維護(hù),這就加強(qiáng)了可維護(hù)性,數(shù)據(jù)也更好追蹤(但是不使用Redux之類的數(shù)據(jù)流管理插件,其實(shí)還是不容易追蹤),而state的設(shè)計(jì)也是基本和組件的分層相對應(yīng)的。
mixins
mixins是定義不同的組件使用到的共同的方法而存在的。你可以在mixins里定義一些無關(guān)業(yè)務(wù)邏輯的方法,例如fackbook官方推薦的對于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對象,clearTimeouts方法中遍歷數(shù)組,調(diào)用clearTimeout清除已保存的對象,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)頁的性能表現(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ò)。上面代碼中,通過為組件指定 Click事件的回調(diào)函數(shù),確保了只有等到真實(shí) DOM 發(fā)生 Click事件之后,才會(huì)讀取 this.refs.[refName]屬性。
React 組件支持很多事件,除了 Click事件以外,還有 KeyDown、Copy、Scroll等,完整的事件清單請查看官方文檔。
因?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)頁面后,開發(fā)的第一個(gè)工作就是頁面的分解,按照一定的規(guī)律把界面分為不同的(非常大可能有嵌套的關(guān)系)的組件,界面的分層一般都對應(yīng)了數(shù)據(jù)的層次結(jié)構(gòu),甚至來說必須如此,這樣數(shù)據(jù)在傳遞給組件時(shí)很容易操作,另外如果搭配redux使用,數(shù)據(jù)管理也更容易。關(guān)于Redux的信息,請參考Redux教程
靜態(tài)頁面的分解
如下面的示例界面
相應(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">即將過期 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:'即將過期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>
)
}
})
這樣頁面在加載的時(shí)候就會(huì)出現(xiàn)所要的效果,最后把真?zhèn)€界面的完整代碼奉上
import React from 'react';
import String from '../Helper/Strings';
/**
* 主頁面的每一個(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)就是用來配置路由的
<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)對應(yīng)就是應(yīng)用中的一個(gè)界面,代碼中,說明應(yīng)用中只有兩個(gè)界面,HomePage和TodoApp,其中HomePage是進(jìn)入應(yīng)用后第一個(gè)展示的界面。訪問Route中的path就是訪問時(shí)頁面對應(yīng)的路徑,如代碼中TodoApp的path是todoApp,那么在瀏覽器中打出地址http://localhost:3939/#/todoApp可以訪問此界面,代碼中的跳轉(zhuǎn)也需要使用配置的path跳轉(zhuǎn)。
代碼中跳轉(zhuǎn)的方法很簡單,如果從A組件中跳轉(zhuǎn)到下一個(gè)界面,那么在A組件中,要引入下面的代碼
contextTypes: { router: React.PropTypes.object.isRequired}
然后在組件中通過如下形式代碼就可跳轉(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.lxweimin.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