前面已經(jīng)完成了聯(lián)系人列表,然而似乎與 Cordova 沒啥關系,隨便找個 Web 服務器就可以跑。那么標題上的 Cordova 就只是一個簡單的 Web 窗口而已么?
當然不是,目前我們的數(shù)據(jù)是保存在 json 文件中的,以后會放在數(shù)據(jù)庫中,以便于實現(xiàn)數(shù)據(jù)的增刪改。數(shù)據(jù)庫選用 SQLite,操作 SQLite 必須使用 Android 提供的一些 API,這時候就需要通過 Cordova 的插件來實現(xiàn)了。甚至再往后添加二維碼等功能,都需要用到 Cordova。但是 Cordova 的 API 參與了開發(fā)之后,調(diào)試時就要部署在手機上進行了,操作多有不變,所以先處理前端不需要 Cordova 的部分,待前端成熟之后再來連接 Cordova。
聯(lián)系人列表完成之后,現(xiàn)在開始考慮詳情頁面。之所以需要詳情頁面,是因為還有一些不便于在列表中展示的信息(列表項顯示范圍有限),所以再在測試數(shù)據(jù)中添加更多的字段,比如性別、城市等
詳情頁面寫在 detail.html 中,對應的在 js 目錄中創(chuàng)建一個 detail.jsx 保存 React 組件和腳本。樣式表暫時仍然使用 index.css,在里面添加詳情頁面所需要的樣式。所以新的 www 目錄結(jié)構會是這樣
詳情頁面仍然需要顯示一個頁頭,標題就是姓名。之后的內(nèi)容仍然用列表顯示。用 HTML 表示大概會像這樣
<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 個組件
因為是靜態(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 則拆分數(shù)據(jù)項,仍然通過 props 方式,逐項向 DetailItem 傳遞數(shù)據(jù)。
在容錯方面,為了簡化處理過程,如果沒能取得聯(lián)系人數(shù)據(jù),則用一個姓名叫“查無此人”的默認的聯(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>
中傳入了一個 data-id
屬性,這個屬性在作為 React 對象屬性的同時,也會以標簽屬性形式渲染到 HTML 中——React 會把 data-
前綴的屬性直接渲染為 HTML 標簽屬性。
var DetailItem = React.createClass({
render: function() {
return (
<A.ListItem>
<span className="label">{this.props.label}</span>
<span>{this.props.value}</span>
</A.ListItem>
);
}
});
這個組件沒什么懸念,只是直接把 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)系人,設置到 state 中。
顯示的時候,如果沒有找到 this.state.person
,則使用默認的“查無此人”聯(lián)系人信息。
最后當然不能忘了渲染根組件:Page。
React.render(<Page />, document.body);
這時候,通過 http://localhost/detail.html#1001
這個地址,已經(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 設置為 block了,所以帶鏈接的電話圖標會顯示在下一行。當然通過修改 CSS 是可以解決的,但是我想用另一種方法來解決:點擊事件。
React 是支付事件處理的,在其 Event System 一章中說明了事件處理的方式和注意事項。React 可以處理的事件分幾大類共計數(shù)十個,都在 Event System 中列舉出來了。這里需要用到的是 onClick
事件。
注意,下面說的內(nèi)容不是在 detail.jsx 而是在 index.jsx 中
首先為 Person 組件定義一個處理函數(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 標簽屬性中綁定事件處理函數(shù)的情況,函數(shù)在執(zhí)行時 this
指針會變?yōu)槿謱ο?(window)。這里是不是需要 bind(this)
呢?
不需要!
React 已經(jīng)處理了 this 的問題,所以這里只需要簡單綁定 this.handleClick
,而在 handleClick
中使用 this
就是當前組件對象,不會是全局對象。
如果有強迫癥,可能還需要給 <A.ListItem>
加個內(nèi)聯(lián)樣式:style={{ cursor: "pointer" }}
,不過我認為沒必要,因為我們的目標是手機 App,指哪點哪,完全沒有 hover 一說。
要讓電話可點擊只需要加 <a href="tel:xxxxxx">
即可。但是在詳情頁,每個數(shù)據(jù)項都是這個結(jié)構:
<ListItem>
<span>{label}</span>
<span>{value}</span>
</ListItem>
如果要加鏈接,就會變成下面的結(jié)構
<ListItem>
<span>{label}></span>
<span>
<a href={href}>{value}</a>
</span>
</ListItem>
然后再在 Detail 中使用組件的時候多傳入一個 href
參數(shù)。
說起來,兩個 DetailItem 中的區(qū)別只有第 2 個的 <span>
中的內(nèi)容那一點,有沒有辦法可以重用呢?——可以試試 mixins,因為從 Reusable Components 的理解,mixins 有點像繼承。
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ù)組中應該是一個原型對象更貼切,嘗試
var DetailLinkItem = React.createClass({
mixins: [DetailItem.prototype],
getValueContent: function() {
return <a href={this.props.href}>{this.props.value}</a>;
}
});
這次是因為重復定義了 constructor。DetailItem.prototype
中有一個 constructor
,而 React.createClass()
又定義了一個??磥聿荒芡祽?,只能定義個獨立對象了
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>;
}
});
這個變更是在第 1 次嘗試的時候就修改了的,主要是處理“電話”那一項時用 <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ū)分開來,順便參照列表頁面的列表樣式,做一些細節(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;
}
最終效果還是比較令人滿意的
更多建議: