前一節(jié)已經(jīng)講述了表頭和列表的組件應(yīng)用,但組件列表項的簡單應(yīng)用并不能滿足我們的需求。本節(jié)將繼續(xù)深入探討聯(lián)系人列表的細節(jié)實現(xiàn)
本來列表項并不需要自定義,只需要在 <A.ListItem>
中加入合適的組件就行,比如
var items = data.map(function(t) {
return (
<A.ListItem>
<A.Icon icon="user" />
{t.name}
<A.Icon icon="phone" />
</A.ListItem>
);
});
然而為了將列表項抽象出來,以及便于對列表項的細節(jié)調(diào)整和后期可能需要實現(xiàn)的操作,還是把它定義成一個組件的好,這個組件就叫 Pserson
,只需要把原來 map()
中的代碼拷貝到 render()
的 return
中,再把 map()
中的 return
稍稍改一下
var Person = React.createClass({
render: function() {
return (
<A.ListItem>
<A.Icon icon="user" />
{t.name}
<A.Icon icon="phone" />
</A.ListItem>
);
}
});
var items = data.map(function(t) {
return <Person />
});
很明顯,上面的代碼是有問題的:
t
變量沒有賦值,所以 t.name
一定會拋異常;return <Person />
看起來沒有問題,但是并沒有傳入 t
值,所以列表的每一項都會是一模一樣的于是這里遇到了問題:怎么從父級控件向子級控件傳入?yún)?shù)?
這里可以作個比喻:從父級控件調(diào)用子級控件,就像在某個函數(shù)中調(diào)用其它函數(shù)一樣。那么傳入?yún)?shù)也就像調(diào)用函數(shù)時傳入的參數(shù)一樣。
React 通過 props
向子級控件傳入?yún)?shù),在 JSX 語法中,寫法和 XML 的屬性定義類似。比如向 Person 控件傳入 name
和 tel
參數(shù)——應(yīng)該叫屬性更準(zhǔn)確,就可以這樣
var items = data.map(function(t) {
return <Person name={t.name} tel={t.tel} />
});
注意到屬性值是用的花括號
{}
包起來的,這表示傳入的是表達式,需要先計算其結(jié)果。如果是已知的字符串,不需要計算的,可以像 XML 屬性那樣用使用引號。
然后,在 Person 組件中,通過 this.props
對象可以使用傳入的屬性值,比如 this.props.name
就引用了傳入的 name
屬性。同時,為了稍稍解決一點顯示仍然不夠美觀的問題,這里可以用 AMUIReact.Badge
組件包裝一下第 2 個 Icon。
正確的 Person 組件定義如下
var Person = React.createClass({
render: function() {
return (
<A.ListItem>
<A.Icon icon="user" />
{this.props.name}
<A.Badge amStyle="success" radius>
<A.Icon icon="phone" />
</A.Badge>
</A.ListItem>
);
}
});
對了,這里 {this.props.name}
是引用了輸入的 name
屬性,而傳入的 tel
屬性暫時還沒使用。同時在使用 Badge 組件的時候,也向其傳入了 amStyle
和 radius
兩個屬性……等等,radius
是屬性?
正確,radius
是屬性,作為布爾值屬性,是可以省略值,這時其值會被當(dāng)作 true
。這當(dāng)然不符合 XML 的語法,不過這不是問題,因為這是 JSX 不是 XML。當(dāng)然對于有強迫癥的朋友,也可以顯示的指定布爾值:radius={true}
或者 radius={false}
。
千萬注意
{true}
和{false}
是用花括號而不是引號包起來的。當(dāng)然如果用"true"
也不會有問題,但是用"false"
就有問題了——因為在 JavaScript 中"false"
是“真”值(不懂原因的找度娘)!
說起來,添加撥號功能真不難——只需要提供一個到 "tel:電話號碼
的鏈接就可以了。上例的 Badge 中,使用一個 <a>
標(biāo)簽包裹 Icon 組件即可,只不過第一次嘗試通常都不怎么順利:
<A.Badge amStyle="success" radius={false}>
<a href="tel:{this.props.tel}">
<A.Icon icon="phone" />
</a>
</A.Badge>
上例中 {this.props.tel}
并沒有被計算出來,直接作為 URI 字符串的一部分了。好吧,JSX 解釋器不認(rèn)識字符串中的 {}
表達式,所以只好換個寫法
<A.Badge amStyle="success" radius={false}>
<a href={"tel:" + this.props.tel}>
<A.Icon icon="phone" />
</a>
</A.Badge>
由于個人潔癖,最終把 {"tel:" + this.props.tel}
先賦值給一個變量再在 JSX 中引用,所以最終的 Person 組件定義如下
var Person = React.createClass({
render: function() {
var link = "tel:" + this.props.tel;
return (
<A.ListItem>
<A.Icon icon="user" />
{this.props.name}
<A.Badge amStyle="success" radius>
<a href={link}>
<A.Icon icon="phone" />
</a>
</A.Badge>
</A.ListItem>
);
}
});
有沒有注意到,link
變量定義和賦值都在 return
之前——因為,一定記住,return
后面的是個表達式,不能寫語句!
“傳播屬性”這個詞翻譯得很別扭,所以我寧愿用“Spread 屬性”。
在 Page 的 render()
中,可以發(fā)現(xiàn) t
的兩個屬性 name
和 tel
都被傳遞給了 Person 組件對象。還好這里只需要傳遞 2 個屬性,如果需要傳遞的屬性是十個八個的,光寫屬性傳遞就得累死。
React 當(dāng)然不會想不到這個問題,所以 JSX 提供了“Spread 屬性”語法。只需要簡單的使用 {...t}
就可以將 t
的所有屬性拷貝到組件的 props 中。因此,map()
部分可以簡化為
var items = this.state.persons.map(function(t) {
return <Person {...t} />
});
Spread 屬性很容易讓人聯(lián)想到 ES2015(ES6)中的“可變參數(shù)”(或稱“不定參數(shù)”)語法——那么在不支持 ESA015 的瀏覽器中是不是就不能使用 Spread 屬性了呢?——當(dāng)然不會,因為 Spread 屬性是 JSX 提供的語法,則 React 解釋,而不是由 JavaScript 引擎解釋。
現(xiàn)在的效果已經(jīng)有點像樣了,但仍然需要改進。不過如之前所述,這個可以通過樣式表來解決,待功能完成得差不多了再來調(diào)整。
到目前為止,數(shù)據(jù)仍然是以硬代碼的形式寫在 index.jsx 中的,這是一個同步過程。雖然目前這么做沒有問題,但是如果數(shù)據(jù)需要保存在數(shù)據(jù)庫中,而從數(shù)據(jù)庫獲取數(shù)據(jù)像 AJAX 一樣是一個異步過程(到目前狀態(tài),是同步還是異步并不清楚,這涉及到 Corodva 對數(shù)據(jù)庫的操作,暫未研究)就麻煩了。因此,現(xiàn)在先把數(shù)據(jù)獨立出來保存在 /js/data.json
中,通過 AJAX 方式先研究一下異步加載數(shù)據(jù)。
說實在的,為了研究這個問題費了不少腦筋,最后還是在 React 文檔中得到了答案(參閱:Load Initial Data vi AJAX)。解決這個問題涉及到了 React 組件數(shù)據(jù)的另一種保存形式:state,以及 render
之外的兩個組件生命周期方法 getInitalState()
和 `componentDidMount()。
到目前為止,一共只寫了兩個組件:Page 和 Person。很顯示,加載整個列表數(shù)據(jù)的任務(wù)應(yīng)該落在 Page 上。依葫蘆畫瓢,先把功能實現(xiàn)了再說
var Page = React.createClass({
// [1]
getInitialState: function() {
return {
persons: []
}
},
// [2]
componentDidMount: function() {
$.getJSON("/js/data.json").then(function(data) {
if (this.isMounted()) {
this.setState({
persons: data
});
}
}.bind(this));
},
render: function() {
// [3]
var items = this.state.persons.map(function(t) {
return <Person {...t} />
});
return (<div>
<A.Header title="通訊錄" />
<A.List>
{items}
</A.List>
</div>);
}
});
注意 Page 組件中 3 個地方的變化,
getInitialState()
方法componentDidMount()
方法map()
的數(shù)據(jù)源。原來的 data
已經(jīng)不存在了,取而代之的是 this.state.persons
。除此之外還有幾點需要注意
$.getJSON().then()
的回調(diào)函數(shù)中,直接使用了 this.isMounted()
和 this.setState()
等。函數(shù)中的 this
怎么還會是組件對象呢?——請注意回調(diào)函數(shù)后的 .bind(this)
。這個方法在 React 的各方實例中經(jīng)常出現(xiàn),不失為傳遞 this
的一個好辦法。this.isMounted()
的作用是判斷當(dāng)前組件仍然處于 mounted 狀態(tài),只有在這個狀態(tài)下 setState 才有意義。雖然在 componentDidMount
事件中寫的這段代碼,但是由于是異步加載,所以并不知道當(dāng)前組件是否已經(jīng)有所變化。this.state.persons
是不存在的。如果沒有 getIntialState()
,會發(fā)現(xiàn)第1次 render 的時候連 this.state
都還不存在(不過是 null
不是 undefined
)。由此證明在使用了React 組件狀態(tài)數(shù)據(jù)的情況下,從 getInitalSate()
返回初始的狀態(tài)對象是非常有必要的。
現(xiàn)在聯(lián)系人列表的功能部分已經(jīng)基本完成,是時候美化一下了。通過瀏覽器的 Inspect 功能可以看到列表部分的 HTML 渲染出來是這樣的(只保留了一個 <li>
示例)
<ul class="am-list">
<li class="">
<i icon="user" class="am-icon-user"></i>
<span>張三</span>
<span class="am-badge am-badge-success am-radius">
<a href="tel:13801234567">
<i icon="phone" class="am-icon-phone"></i>
</a>
</span>
</li>
</ul>
現(xiàn)在需要美化的事項包括
所以在 index.css
中刪除原來的內(nèi)容,改為如下內(nèi)容
ul {
margin-top: 0;
}
li {
padding: 3px 6px;
}
li i:first-child {
margin-right: 8px;
}
li span:last-child {
margin-top: 4px;
}
最后一句是在調(diào)試的時候發(fā)現(xiàn)電話圖標(biāo)不在正中才加的。
前面的 CSS 最大的問題是選擇器不夠精準(zhǔn),樣式表內(nèi)容多了之后容易發(fā)生各種沖突。在 HTML 中比較好的解決辦法是添加 class="xxx"
屬性。但是在 React 中添加 class="xxx"
屬性,會被認(rèn)為是 props 數(shù)據(jù)。React 中是用 className
來表示樣式類的。
所以,需要將原來的代碼稍做變動,加上適當(dāng)?shù)?className
屬性
<A.List>
添加 person-list
類 var Page = React.createClass({
// ...... 省略代碼若干
render: function() {
var items = this.state.persons.map(function(t) {
return <Person {...t} />
});
return (<div>
<A.Header title="通訊錄" />
<A.List className="person-list">
{items}
</A.List>
</div>);
}
});
person
、person-icon
、person-phone
類 var Person = React.createClass({
render: function() {
var link = "tel:" + this.props.tel;
return (
<A.ListItem className="person">
<A.Icon icon="user" className="person-icon" />
{this.props.name}
<A.Badge amStyle="success" radius className="person-phone">
<a href={link}>
<A.Icon icon="phone" />
</a>
</A.Badge>
</A.ListItem>
);
}
});
ul.person-list {
margin-top: 0;
}
li.person {
padding: 3px 6px;
}
li>.person-icon {
margin-right: 6px;
}
li>.person-phone {
margin-top: 4px;
}
個人習(xí)慣,我不太喜歡使用內(nèi)聯(lián)樣式。但如果確實需要使用內(nèi)聯(lián)樣式,可以通過組件的 style
屬性設(shè)置,其值可以是一個對象,示例:
render: function() {
var styles = {
color: "#666666",
"background-color": "#efefef"
};
return <A.List style={styles}></A.List>
}
更多建議: