從零開始的react入門教程(四),了解常用的條件渲染、列表渲染與獨一無二的key


壹 ❀ 引

從零開始的react入門教程(三),了解react事件與使用注意項一文中,我們了解了react中事件命名規則,綁定事件時對於this的處理,以及事件中可使用的e對象。那么這篇文章中我們來熟悉react中常用的條件渲染語法。

貳 ❀ 條件渲染

在開發中,我們常有根據一個變量值的真或假來決定渲染A或者B內容的情況,這種需求不管用三元或者if語句都能輕松實現,比如實現一個簡單的登錄是否成功的文案提示功能:

class IsLogin extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            isLogin: false
        }
    }
    handleLogin = () => {
        this.setState({ isLogin: true });
    }
    handleLogout = () => {
        this.setState({ isLogin: false });
    }
    renderLogin = () => {
        return <h1>歡迎回來!</h1>
    }
    renderLogout = () => {
        return <h1>你好,請登錄!</h1>
    }
    render() {
        return (
            <div className="isLogin">
                <div>{this.state.isLogin ? this.renderLogin() : this.renderLogout()}</div>
                <button className="login" onClick={this.handleLogin}>login</button>
                <button className="logout" onClick={this.handleLogout}>logout</button>
            </div>
        );
    }
};

ReactDOM.render(<IsLogin />, document.getElementById('root'));

在這個例子中,我們可以通過兩個按鈕改變state中關於isLogin的值,而這個值又決定了最終在render中應該渲染哪個文案。通過isLogin的變化,我們甚至都不需要同時顯示兩個按鈕,同樣通過變量的變化來決定渲染哪個按鈕,修改render為如下,那么效果就是這樣了:

render() {
    const buttonType = this.state.isLogin
        ? <button className="logout" onClick={this.handleLogout}>logout</button>
        : <button className="login" onClick={this.handleLogin}>login</button>
    return (
        <div className="isLogin">
            <div>{this.state.isLogin ? this.renderLogin() : this.renderLogout()}</div>
            {buttonType}
        </div>
    );
}

在react的花括號中,我們還能用JS邏輯運算符玩一些花樣,比如前面的例子是根據情況顯示A或者B,現在我們希望要么顯示A,要么什么都不顯示,這里就可以用邏輯運算符&&,其實效果與滿足if條件完全一致,比如這個官網提供的例子:

function UnreadMessage(props) {
    const unreadMessage = props.msg;
    return (
        <div className="unreadMessage">
            <h1>你好!</h1>
            {unreadMessage.length > 0 &&
                <h2>
                    你有{unreadMessage.length}條未讀信息。
                </h2>
            }
        </div>
    )
}
const emailMsg = [1, 2, 3];
ReactDOM.render(<UnreadMessage msg={emailMsg} />, document.getElementById('root'));

在這個例子中就凸顯出了JSX語法的特點,我們將JS的邏輯判斷與react元素糅合在了一起,並由{}去提供解析。站在JS的角度,這里所做的其實就是下面這段代碼:

const emailMsg = [1, 2, 3];
let unreadMessageText = '';
if(unreadMessage.length){
    unreadMessageText = `你有${emailMsg.length}條未讀信息。`
}

邏輯運算符除了&&之外還有||,這兩個的區別簡單介紹下,首先是A&&B,它的執行為當執行到A為真時才會繼續執行到后面的B,看個例子:

function A(bool) {
    return bool;
};
function B() {
    console.log(1);
};
A(true) && B();//B輸出1
A(false) && B();//B不會執行

A||B的意思是當A為真時就不會繼續執行B了,因為只要有一個為真就可以了,所以當A為假時才會跑后面的B。

A(true) || B();//B不會執行
A(false) || B();//B輸出1

對於||的場景,比較常見的是程序中需要傳遞某個值,假設前者為假,我們會提供一個默認值傳遞,保證后續的邏輯不會出錯:

const arr = props.arr || [];
arr.filter(ele => ele);

在前面的例子中,我們都是根據條件決定組件內部渲染什么,還有種可能性,就是在特定情況下,我們希望組件內部什么都不要渲染;雖然這個組件有被調用,但不管是函數組件還是class組件,都需要通過return來返回需要渲染的react元素,所以在特定條件下,我們可以在return元素前直接return null來達到目的。

function IsShow(props) {
    if(!props.show){
        return null;
    }
    return (
        <div >hello!</div>
    )
}

ReactDOM.render(<IsShow show={false}/>, document.getElementById('root'));

這個例子中,雖然組件IsShow有被調用,但因為組件並未返回任何dom,所以在界面上我們看不到任何東西。

那么到這里我們介紹了react中一些常見的條件渲染場景,在{}中你可以根據需要任何組合這些條件並拿到自己想要的最終結果。

叄 ❀ 列表渲染

在實際開發中,我們常有將數組類數據渲染成列表的需求,在vue或者或者小程序中我們可以借用指令來達到目的,比如vue中的v-for,小程序中的wx:forangularjs中的的ng-repeat等,以vue為例遍歷一個數組可以這樣做:

const app = new Vue({
  el: '#list',
  data: {
    users: [
      { name: '聽風' },
      { name: '是風' }
    ]
  }
});
<ul id="list">
  <!-- 利用v-for遍歷 -->
  <li v-for="user in users" :key="user.name">
    {{ user.name }}
  </li>
</ul>

但我們在react中的列表渲染會有所不同,我們不會借用類似的指令,而是通過數組API直接遍歷數據並得到我們想要的react元素塊,再加入render中進行解析渲染。

在JS中我們想要將一個數組中所有的元素都乘以2可以這么做:

const doubled = [1,2,3].map(ele => ele*2);// [2, 4, 6]

而react遍歷列表也類似如此,比如我們需要在ul中通過li展示上面這些結果,我們則需要將要展示的所有li都提前遍歷出來,再作為一個變量賦予給ul,像這樣:

function List(props) {
    const list = props.nums.map(ele => (
        <li>{ele * 2}</li>
    ));
    return <ul>{list}</ul>
}
const nums = [1, 2, 3];
ReactDOM.render(<List nums={nums} />, document.getElementById('root'));

在這個例子中,我們先通過map遍歷,得到了包含多個li標簽的合集,並保存在了變量list中,之后又將list賦予給ul標簽內部,從而實現了我們想要的效果。看似完美的效果,當打開控制台就不那么完美了,這段代碼報給出了紅色警告:

list中的每個child都應該有一個獨一無二的屬性作為key。這個問題我想大家在vue或者小程序中都有類似的處理,我們來看看react如何解決。

肆 ❀ 獨一無二的key

肆 ❀ 壹 為什么要用key

為什么要添加key?我想大家應該都有聽說diff算法,對於react而言,每次的props或者state修改都會觸發render重新渲染視圖,如果是完整的重新渲染代價是昂貴的,而添加key的目的是便於react在數據修改后,能記錄元素知道它對應的是先前的誰並進行對比,比如我們有個數組[0,1,2]被渲染,之后被修改為[0,2,2],對於react而言,它只要找到第二個li並修改它的渲染內容即可,而不是完整去渲染。

所以回到上面的列表渲染的例子,我們可以這樣為li添加key屬性:

const list = props.nums.map(ele => (
    <li key={ele}>{ele * 2}</li>
));

我們直接將數組遍歷的每個元素自身作為key賦予給了li,保存代碼,你會發現控制台的警告已經沒有了。

肆 ❀ 貳 不推薦使用index作為key

你也許在想,為什么不用index作為key呢?像這樣:

const list = props.nums.map((ele, index) => (
    <li key={index}>{ele * 2}</li>
));

但用index做為key其實是有風險的,我們來看個由官網改寫的例子:

class Item extends React.Component {
    render() {
        return (
            <div>
                <label>{this.props.name}</label>
                <div>
                    <input type='text' />
                </div>
            </div>
        )
    }
}

class Example extends React.Component {
    constructor() {
        super();
        this.state = {
            list: [
                { name: '聽風是風', id: 1 },
                { name: '行星飛行', id: 2 }
            ]
        };
    }

    addItem = () => {
        const id = +new Date;
        this.setState({
            list: [{ name: '時間跳躍' + id + id, id }, ...this.state.list]
        });
    }

    render() {
        return (
            <div className="example">
                <button onClick={this.addItem}>clie me</button>
                <div className="form">
                    <form>
                        <h3>不好的做法 <code>key=index</code></h3>
                        {this.state.list.map((todo, index) =>
                            <Item {...todo}
                                key={index} />
                        )}
                    </form>
                    <form>
                        <h3>更好的做法 <code>key=id</code></h3>
                        {this.state.list.map((todo) =>
                            <Item {...todo}
                                key={todo.id} />
                        )}
                    </form>
                </div>

            </div>
        )
    }
}
ReactDOM.render(<Example />, document.getElementById('root'))

當我們提前為input輸入了值,並點擊按鈕新建輸入框時效果就很明顯了,我們的本意是在現有輸入框頭部插入新的輸入框。但當使用inde作為key時react對比了新舊index為0的input,由於index前后都是0,所以react認為此時的item組件是可以復用的,它並沒有完全替換掉它,而是單純更新了item內部的label標簽,所以你會發現input創建出來是有值的。

而當我們使用第一無二的標識作為key時點擊創建,由於前后根本不是一個東西,react選擇了重新創建一個全新的lable與input,並插入到了現有DOM節點之前。

通常來說,我們始終不推薦使用index作為key,更好的做法是為需要遍歷的數據都提供獨一無二的id,常規來說后端返回的數據都會滿足這一點。

但如果你說我的數據就是沒id,這可怎么辦,在react官網介紹的博客中,也推薦了用於隨機生成id的小工具,例如shortidshortid或者Nano ID,有興趣大家可以自己看看用法。

肆 ❀ key與組件

在上一個介紹index作為key會造成問題的例子中,不知道大家有沒有發現key是寫在需要遍歷的組件Item上,而非item內部的div上,其實不難理解,對於react而言,組件Item就是一個整體,我們希望這個整體帶有唯一標識,在數據變化時,當前的Item是否應該更新或是新建,所以下面這樣的寫法就是錯誤的:

function ListItem(props) {
    const value = props.value;
    return (
        // 錯誤!你不需要在這里指定 key:
        <li key={value.toString()}>
            {value}
        </li>
    );
}

function List(props) {
    const listItems = [1, 2, 3].map((number) =>
        // 錯誤!元素的 key 應該在這里指定:
        <ListItem value={number} />
    );
    return (
        <ul>
            {listItems}
        </ul>
    );
}

ReactDOM.render(<List />, document.getElementById('root'))

一個規則就是,key永遠加在你所用的數組API內部的元素上,修改成如下這樣就好了:

function ListItem(props) {
    const value = props.value;
    return (
        <li>
            {value}
        </li>
    );
}

function List(props) {
    const listItems = [1, 2, 3].map((number) =>
        <ListItem value={number}  key={number.toString()}/>
    );
    return (
        <ul>
            {listItems}
        </ul>
    );
}

關於key最后一點說明就是,雖然我們說key應該獨一無二,但並不是說它在全局是獨一無二,而是只針對於兄弟元素之前,在我們前面展示index作為key的例子中,其實我們也將數組給了form中去遍歷,由於不是兄弟關系,你會發現它們之間的key就算重名也沒任何關系。

伍 ❀ 總

好了,那么到這里我們介紹了react中幾種常見的條件渲染用法,其實總結來說,在react的{}中我們能做到很多JS中的條件判斷騷操作。

除了條件渲染,我們還介紹了列表渲染,這才開發中將非常普遍,與常規框架不同,react並未提供對應的指令,而是借用數組API直接渲染react元素,而說到列表渲染總是離不開與之配對的key,我們了解了為什么要提供key,以及使用index作為key可能造成的問題,所以在開發中總是建議不要使用index作為key。以上知識就是本文闡述的幾個核心點了,時間也不早了,那么到這里本文結束,晚安。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM