前面已經(jīng)完成了聯(lián)系人列表,然而似乎與 Cordova 沒啥關(guān)系,隨便找個(gè) Web 服務(wù)器就可以跑。那么標(biāo)題上的 Cordova 就只是一個(gè)簡單的 Web 窗口而已么?
當(dāng)然不是,目前我們的數(shù)據(jù)是保存在 json 文件中的,以后會(huì)放在數(shù)據(jù)庫中,以便于實(shí)現(xiàn)數(shù)據(jù)的增刪改。數(shù)據(jù)庫選用 SQLite,操作 SQLite 必須使用 Android 提供的一些 API,這時(shí)候就需要通過 Cordova 的插件來實(shí)現(xiàn)了。甚至再往后添加二維碼等功能,都需要用到 Cordova。但是 Cordova 的 API 參與了開發(fā)之后,調(diào)試時(shí)就要部署在手機(jī)上進(jìn)行了,操作多有不變,所以先處理前端不需要 Cordova 的部分,待前端成熟之后再來連接 Cordova。
聯(lián)系人列表完成之后,現(xiàn)在開始考慮詳情頁面。之所以需要詳情頁面,是因?yàn)檫€有一些不便于在列表中展示的信息(列表項(xiàng)顯示范圍有限),所以再在測(cè)試數(shù)據(jù)中添加更多的字段,比如性別、城市等
詳情頁面寫在 detail.html 中,對(duì)應(yīng)的在 js 目錄中創(chuàng)建一個(gè) detail.jsx 保存 React 組件和腳本。樣式表暫時(shí)仍然使用 index.css,在里面添加詳情頁面所需要的樣式。所以新的 www 目錄結(jié)構(gòu)會(huì)是這樣
詳情頁面仍然需要顯示一個(gè)頁頭,標(biāo)題就是姓名。之后的內(nèi)容仍然用列表顯示。用 HTML 表示大概會(huì)像這樣
<ul>
<li>
<span class="label">姓名</span>
<span>張三</span>
</li>
<li>
<span class="label">電話</span>
<span>13801234567</span>
</li>
<li>
<span class="label">性別</span>
<span>男</span>
</li>
<li>
<span class="label">城市</span>
<span>四川省綿陽市</span>
</li>
</ul>
detail.html 的代碼和 index.html 幾乎一樣,唯一的區(qū)別是
<script type="text/jsx" src="js/index.jsx"></script>
換成了
<script type="text/jsx" src="js/detail.jsx"></script>
detail.jsx 中主要定義 3 個(gè)組件
因?yàn)槭庆o態(tài)頁面,不能處理 GET 或 POST 參數(shù),所以由地址欄的 HASH 傳遞聯(lián)系人的 ID 參數(shù),再在頁面中通過 AJAX 獲取數(shù)據(jù),篩選出該詳情頁面需要顯示的聯(lián)系人信息。而數(shù)據(jù)獲取就由 Page 組件處理(處理過程參考 index.jsx 中的 Page 組件。
之后由 Page 通過屬性方式向 Detail 傳遞聯(lián)系人數(shù)據(jù);而 Detail 則拆分?jǐn)?shù)據(jù)項(xiàng),仍然通過 props 方式,逐項(xiàng)向 DetailItem 傳遞數(shù)據(jù)。
在容錯(cuò)方面,為了簡化處理過程,如果沒能取得聯(lián)系人數(shù)據(jù),則用一個(gè)姓名叫“查無此人”的默認(rèn)的聯(lián)系人數(shù)據(jù)代替。
var Detail = React.createClass({
render: function() {
var person = this.props.person;
return (
<A.List data-id={person.id}>
<DetailItem label="姓名" value={person.name} />
<DetailItem label="電話" value={person.tel} />
<DetailItem label="性別" value={person.isMan ? "男" : "女"} />
<DetailItem label="城市" value={person.city} />
</A.List>
);
}
});
注意,這里在 <A.List>
中傳入了一個(gè) data-id
屬性,這個(gè)屬性在作為 React 對(duì)象屬性的同時(shí),也會(huì)以標(biāo)簽屬性形式渲染到 HTML 中——React 會(huì)把 data-
前綴的屬性直接渲染為 HTML 標(biāo)簽屬性。
var DetailItem = React.createClass({
render: function() {
return (
<A.ListItem>
<span className="label">{this.props.label}</span>
<span>{this.props.value}</span>
</A.ListItem>
);
}
});
這個(gè)組件沒什么懸念,只是直接把 label 的文本顯示出來而已。
var Page = React.createClass({
defaultPerson: {
id: "0000",
name: "查無此人",
phone: "00000000000"
},
getInitialState: function() {
return {
person: null
};
},
componentDidMount: function() {
$.getJSON("/js/data.json").then(function(data) {
if (this.isMounted()) {
this.setState({
person: data.filter(function(p) {
return "#" + p.id === window.location.hash;
})[0]
});
}
}.bind(this));
},
render: function() {
var person = this.state.person || this.defaultPerson;
return (
<div>
<A.Header title={person.name} />
<Detail person={person} />
</div>
);
}
});
Page 組件和之前列表頁面的 Page 組件一樣,在 componentDidMount()
中通過 AJAX 加載數(shù)據(jù)。之后,通過 window.location.hash
按 ID 精確查找聯(lián)系人,設(shè)置到 state 中。
顯示的時(shí)候,如果沒有找到 this.state.person
,則使用默認(rèn)的“查無此人”聯(lián)系人信息。
最后當(dāng)然不能忘了渲染根組件:Page。
React.render(<Page />, document.body);
這時(shí)候,通過 http://localhost/detail.html#1001
這個(gè)地址,已經(jīng)可以看到數(shù)據(jù)顯示出來,只不過由于沒有添加樣式,還不夠美觀。
按照 Amazi UI React 文檔,在列表頁面上添加到詳情頁面的連接,只需要在 Person 組件中為 <A.ListItem>
添加 href
屬性即可,
<A.ListItem className="person" href={"detail.html#" + this.props.id}>
</A.ListItem>
然后添加之后顯示出來的效果卻大大出乎意料。主要原因是 Amaze UI 將 li>a
的 display 設(shè)置為 block了,所以帶鏈接的電話圖標(biāo)會(huì)顯示在下一行。當(dāng)然通過修改 CSS 是可以解決的,但是我想用另一種方法來解決:點(diǎn)擊事件。
React 是支付事件處理的,在其 Event System 一章中說明了事件處理的方式和注意事項(xiàng)。React 可以處理的事件分幾大類共計(jì)數(shù)十個(gè),都在 Event System 中列舉出來了。這里需要用到的是 onClick
事件。
注意,下面說的內(nèi)容不是在 detail.jsx 而是在 index.jsx 中
首先為 Person 組件定義一個(gè)處理函數(shù),用于處理列表項(xiàng)被點(diǎn)擊后的動(dòng)作:設(shè)置 window.location.href 跳轉(zhuǎn)到詳情頁。
var Person = React.createClass({
handleClick: function(event) {
window.location.href = "detail.html#" + this.props.id;
},
render() { ... }
});
可以看到,數(shù)據(jù)來源仍然可以是 this.props
,同理,也可以是 this.state
。
之后在 <A.ListItem>
中綁定事件處理函數(shù)
render: function() {
var link = "tel:" + this.props.tel;
return (
<A.ListItem className="person" onClick={this.handleClick}>
...
</A.ListItem>
);
}
注意到 onClick={this.handleClick}
的寫法,聯(lián)想到在 HTML 標(biāo)簽屬性中綁定事件處理函數(shù)的情況,函數(shù)在執(zhí)行時(shí) this
指針會(huì)變?yōu)槿謱?duì)象 (window)。這里是不是需要 bind(this)
呢?
不需要!
React 已經(jīng)處理了 this 的問題,所以這里只需要簡單綁定 this.handleClick
,而在 handleClick
中使用 this
就是當(dāng)前組件對(duì)象,不會(huì)是全局對(duì)象。
如果有強(qiáng)迫癥,可能還需要給 <A.ListItem>
加個(gè)內(nèi)聯(lián)樣式:style={{ cursor: "pointer" }}
,不過我認(rèn)為沒必要,因?yàn)槲覀兊哪繕?biāo)是手機(jī) App,指哪點(diǎn)哪,完全沒有 hover 一說。
要讓電話可點(diǎn)擊只需要加 <a href="tel:xxxxxx">
即可。但是在詳情頁,每個(gè)數(shù)據(jù)項(xiàng)都是這個(gè)結(jié)構(gòu):
<ListItem>
<span>{label}</span>
<span>{value}</span>
</ListItem>
如果要加鏈接,就會(huì)變成下面的結(jié)構(gòu)
<ListItem>
<span>{label}></span>
<span>
<a href={href}>{value}</a>
</span>
</ListItem>
然后再在 Detail 中使用組件的時(shí)候多傳入一個(gè) href
參數(shù)。
說起來,兩個(gè) DetailItem 中的區(qū)別只有第 2 個(gè)的 <span>
中的內(nèi)容那一點(diǎn),有沒有辦法可以重用呢?——可以試試 mixins,因?yàn)閺?Reusable Components 的理解,mixins 有點(diǎn)像繼承。
var DetailItem = React.createClass({
getValueContent: function() {
return this.props.value;
},
render: function() {
return (
<A.ListItem className="person-detail-item">
<span className="label">{this.props.label}</span>
<span>{this.getValueContent()}</span>
</A.ListItem>
);
}
});
var DetailLinkItem = React.createClass({
mixins: [DetailItem],
getValueContent: function() {
return <a href={this.props.href}>{this.props.value}</a>;
}
});
結(jié)果失敗了。再次閱讀 Reusable Components 中的示例,然后發(fā)現(xiàn) mixins 數(shù)組中應(yīng)該是一個(gè)原型對(duì)象更貼切,嘗試
var DetailLinkItem = React.createClass({
mixins: [DetailItem.prototype],
getValueContent: function() {
return <a href={this.props.href}>{this.props.value}</a>;
}
});
這次是因?yàn)橹貜?fù)定義了 constructor。DetailItem.prototype
中有一個(gè) constructor
,而 React.createClass()
又定義了一個(gè)??磥聿荒芡祽?,只能定義個(gè)獨(dú)立對(duì)象了
var detailBase = {
render: function() {
return (
<A.ListItem className="person-detail-item">
<span className="label">{this.props.label}</span>
<span>{this.getValueContent()}</span>
</A.ListItem>
);
}
};
var DetailItem = React.createClass({
mixins: [detailBase],
getValueContent: function() {
return this.props.value
}
});
var DetailLinkItem = React.createClass({
mixins: [detailBase],
getValueContent: function() {
return <a href={this.props.href}>{this.props.value}</a>;
}
});
這個(gè)變更是在第 1 次嘗試的時(shí)候就修改了的,主要是處理“電話”那一項(xiàng)時(shí)用 <DetailLinkItem>
代替了 <DetailItem>
。
var Detail = React.createClass({
render: function() {
var person = this.props.person;
return (
<A.List className="person-detail" data-id={person.id}>
<DetailItem label="姓名" value={person.name} />
<DetailLinkItem label="電話" value={person.tel} href={"tel:" + person.tel} />
<DetailItem label="性別" value={person.isMan ? "男" : "女"} />
<DetailItem label="城市" value={person.city} />
</A.List>
);
}
});
美化通常放在最后,但不代表不需要,現(xiàn)在來加 className 和 CSS。
從效果上來看,主要是需要把 label 和后面的內(nèi)容區(qū)分開來,順便參照列表頁面的列表樣式,做一些細(xì)節(jié)上的美化。
先為 <A.List>
添加 className="person-detail"
,再為 <A.ListItem>
添加 className="person-detail-item"
。className="label"
是有先見之明早就加了的。最后修改 CSS,去掉 li
的類限定,再加上 ul.person-detail
和 li.person-detail-item
的樣式
ul.person-list {
margin-top: 0;
}
li {
padding: 3px 6px;
}
li>.person-icon {
margin-right: 6px;
}
li>.person-phone {
margin-top: 4px;
}
ul.person-detail li {
margin-bottom: 4px;
border-top: 0;
}
li.person-detail-item .label {
margin-right: 8px;
padding: 2px 5px;
background-color: #41afff;
border-radius: 3px;
color: white;
}
最終效果還是比較令人滿意的
更多建議: