壹 ❀ 引
在從零開始的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:for,angularjs中的的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的小工具,例如
shortid或者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。以上知識就是本文闡述的幾個核心點了,時間也不早了,那么到這里本文結束,晚安。
