聯(lián)系人列表(續(xù))

2018-05-25 23:28 更新

前一節(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 />
});

很明顯,上面的代碼是有問題的:

  1. Person 組件中的 t 變量沒有賦值,所以 t.name 一定會拋異常;
  2. return <Person /> 看起來沒有問題,但是并沒有傳入 t 值,所以列表的每一項都會是一模一樣的

于是這里遇到了問題:怎么從父級控件向子級控件傳入?yún)?shù)?

從父級控件向子級控件傳入?yún)?shù)

這里可以作個比喻:從父級控件調(diào)用子級控件,就像在某個函數(shù)中調(diào)用其它函數(shù)一樣。那么傳入?yún)?shù)也就像調(diào)用函數(shù)時傳入的參數(shù)一樣。

React 通過 props 向子級控件傳入?yún)?shù),在 JSX 語法中,寫法和 XML 的屬性定義類似。比如向 Person 控件傳入 nametel 參數(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 組件的時候,也向其傳入了 amStyleradius 兩個屬性……等等,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 Attributes (傳播屬性)

“傳播屬性”這個詞翻譯得很別扭,所以我寧愿用“Spread 屬性”。

在 Page 的 render() 中,可以發(fā)現(xiàn) t 的兩個屬性 nametel 都被傳遞給了 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 引擎解釋。

效果

clipboard.png

現(xiàn)在的效果已經(jīng)有點像樣了,但仍然需要改進。不過如之前所述,這個可以通過樣式表來解決,待功能完成得差不多了再來調(diào)整。

Ajax 加載數(shù)據(jù)

到目前為止,數(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 個地方的變化,

  • 1,添加了 getInitialState() 方法
  • 2,添加了 componentDidMount() 方法
  • [3],修改了 map() 的數(shù)據(jù)源。原來的 data 已經(jīng)不存在了,取而代之的是 this.state.persons。

除此之外還有幾點需要注意

  1. $.getJSON().then() 的回調(diào)函數(shù)中,直接使用了 this.isMounted()this.setState() 等。函數(shù)中的 this 怎么還會是組件對象呢?——請注意回調(diào)函數(shù)后的 .bind(this)。這個方法在 React 的各方實例中經(jīng)常出現(xiàn),不失為傳遞 this 的一個好辦法。

  1. this.isMounted() 的作用是判斷當(dāng)前組件仍然處于 mounted 狀態(tài),只有在這個狀態(tài)下 setState 才有意義。雖然在 componentDidMount 事件中寫的這段代碼,但是由于是異步加載,所以并不知道當(dāng)前組件是否已經(jīng)有所變化。

  1. 如果在 render 中寫上日志,可以發(fā)現(xiàn),它在 componentDidMount 之前和之后都有執(zhí)行。很顯然,在之前執(zhí)行的那一次,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)在需要美化的事項包括

  • 去掉 ul 的 margin-top
  • 給 li 加上 padding
  • 在第 1 個 icon 后加上 margin-right

所以在 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)不在正中才加的。

用 className 來定位樣式元素

前面的 CSS 最大的問題是選擇器不夠精準(zhǔn),樣式表內(nèi)容多了之后容易發(fā)生各種沖突。在 HTML 中比較好的解決辦法是添加 class="xxx" 屬性。但是在 React 中添加 class="xxx" 屬性,會被認(rèn)為是 props 數(shù)據(jù)。React 中是用 className 來表示樣式類的。

所以,需要將原來的代碼稍做變動,加上適當(dāng)?shù)?className 屬性

  1. 在 Page 組件中為 <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>);
        }
    });

  1. 在 Person 組件中分別添加 person、person-iconperson-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>
            );
        }
    });
  1. 修改樣式表

    ul.person-list {
        margin-top: 0;
    }

    
    li.person {
        padding: 3px 6px;
    }

    
    li>.person-icon {
        margin-right: 6px;
    }

    
    li>.person-phone {
        margin-top: 4px;
    }

內(nèi)聯(lián)樣式

個人習(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>
}

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號