2-12 JSX 中迴圈的使用

在上個單元中我們把程式碼重構之後,現在我們要來說明如何在 JSX 中使用迴圈,以達到快速重複使用多個元件的方式。

還記得我們曾經說過,使用元件(component)的好處在於可以快速地重複使用已經寫好的元件,而且每個元件的狀態都是獨立的,也就是說,你不會因為點了「第一個」計數器的向上按鈕,使得剩下其他計數器的數字也都加一,而是只有「第一個」計數器的數字會改變而已。

假設今天我們想要在畫面上產生多個計數器,可以來看看要怎麼做。

直接使用多個 Component#

其中最簡單直觀的方式,就是在 JSX 中去直接帶入多個 Component,這裡也就是指 <Counter /> 這個元件。因為 React 中每個元件其實都是各自獨立的,因此當我們想要一次產出非常多的計數器時,只需要寫很多次 <Counter />,讓我們先產生 7 個計數器就好,同時透過行內樣式的方式,在最外層的 <div> 加上 style 屬性來進行簡單的樣式調整,像下面這樣:

const Counter = () => {
/* ... */
};
ReactDOM.render(
<div
style={{
display: 'flex',
justifyContent: 'space-around',
flexWrap: 'wrap',
}}
>
<Counter />
<Counter />
<Counter />
<Counter />
<Counter />
<Counter />
<Counter />
</div>,
document.getElementById('root')
);
caution

💡 提醒:由於一個 JSX 元素最多只能有一個最外層的元素,因此當我們要轉譯很多的 <Counter /> 時,為了要讓外層只有一個元素,可以加上一個額外的 <div> 把所有 <Counter /> 包起來。

除了在最外層的 <div> 元素上透過 style 屬性來簡單進行樣式的調整,也稍微修改一下 <Counter /> 元件的 <div class="container"> 這個元素的樣式,加上 minWidth 來讓各個計數器之間保有一定的間距:

const Counter = () => {
// ...
return (
<div className="container" style={{ minWidth: 160 }}>
{/* .. */}
</div>
);
};

現在我們的畫面應該會像這樣子:

Imgur

實際操作時你會發現到,每個元件間的資料狀態都是各自獨立的,並不會互相干擾。你可以想像如果沒有 React,使用原生的 JavaScript 來做的話,會需要選取出多少個元素和綁定多少個事件呢?

使用迴圈重複產生多個計數器#

你可能會說,既然 JSX 本質上都是 JavaScript 了,難道還得要手動複製貼上 <Counter /> ,不能用迴圈的方式,看要幾個有幾個嗎?

當然是可以的!既然 JSX 本質上就是 JavaScript ,那麼你當然可以使用 JavaScript 學到的方式來重複產生多個計數器。當在 JavaScript 中要重複執行某一個內容或動作時,很直覺的會想到可以用 for 迴圈。首先你可能會很直覺的這麼寫:

// ❌ 錯誤寫法:for 不是 expressions 不能直接放在 JSX 的 {} 內
// ...
ReactDOM.render(
<div>
{
for (let i = 0; i < 10; i ++) {
<Counter />
}
}
</div>,
document.getElementById('root')
);

和我們在條件轉譯中提到的情況一樣,這麼做程式並沒有辦法正確執行,原因在於 for 迴圈 本身是個 statements 而非 expressions,執行的時候並不會有回傳值,因此不能直接放到 JSX 中的 {} 內去執行。那麼實際上可以怎麼做呢?

在 React 中,當我們要做重複轉譯多個元件時,最常使用到的是透過陣列的 map 方法,因為 map 這個方法會有回傳值,所以可以直接在 JSX 中使用。

實際的做法會像這樣:

  1. 先產生一個帶有多個元素的陣列
  2. 在 JSX 中將這個陣列使用 map 方法,並且每次都回傳 <Counter /> 元素

產生帶有多個元素的陣列#

在 JavaScript 有許多不同的方式都可以產生帶有多個元素的陣列,這裡我們使用 Array.from({ length: n }) 這個方法來產生帶有 n 個 undefined 的陣列,像是這樣:

const counters = Array.from({ length: 8 }); // [undefined, undefined, ..., undefined]

透過陣列的 map 方法來執行迴圈#

接下來,就可以在 JSX 中透過在 {} 內使用 counters.map(() => <Counter />) 的方式,就可以產生帶有多個 <Counter /> 的陣列,像是這樣:

const Counter = () => {
/* ... */
};
const counters = Array.from({ length: 8 }); // [undefined, undefined, ..., undefined]
ReactDOM.render(
<div style={/* ... */}>
{counters.map(() => (
<Counter />
))}
</div>,
document.getElementById('root')
);

這樣寫的意思其實就等同於:

ReactDOM.render(
<div style={/* ... */}>
{[
<Counter />,
<Counter />,
<Counter />,
// ...
]}
</div>,
document.getElementById('root')
);
info

💡 Tips:map 回傳的內容除了可以是 React 元件外,更常見的會是 DOM 元素,像是透過迴圈重複產生多個 <li>

這時候我們就會看到畫面瞬間產生了 8 個計數器,是不是非常快速方便呢!然而,若我們打開瀏覽器的 console 視窗,會看到有錯誤提示產生:

Warning: Each child in a list should have a unique "key" prop.

意思是要提示我們最好把每個透過迴圈重複產生的元件加上 key 這個屬性,而每個 key 的值都應該要是唯一不重複的,如此 React 才會比較清楚迴圈中有哪些項目是被修改或操作過的,一般來說,如果我們有多個使用者的資料需要使用迴圈呈現時,可以直接使用每個使用者的 id 當作 key 值,因為使用者的 id 一般來說是唯一不會重複的,例如:

({
users.map(user => {
<li key={user.id}>
{user.name}
</li>
})
})

但因為在我們的計數器中並沒有唯一的 id 存在,在沒有其他選擇的情況下,我們可以把陣列的 index 當成 key 帶入,像是這樣:

ReactDOM.render(
<div style={/* ... */}>
{counters.map((_, index) => (
<Counter key={index} />
))}
</div>,
document.getElementById('root')
);

當我們把 key 補上之後,錯誤提示就不會出現了。

tip

💡 補充:React 並不建議我們直接拿陣列的 index 來當作 key 的值帶入,特別是在這些元素的順序有可能會有改變的情況下,對於效能會有不好的影響;但這裡因為主要是示範用途,並且只是單純用來呈現資料,沒有要操作或修改這些元素,因此對於效能的影響不大。

換你了:透過迴圈產生多個計數器吧#

在這裡我們提到了如何在 JSX 中搭配陣列的 map 方法來快速產生多個 React 元件,要提醒的是在 map 中回傳的內容,除了可以是 React 元件之外,也可以是 HTML 元素,像是透過 <li> 來產生一系列的清單等等。

現在,請你試著練習看看,如何透過 map 的方式一次產生多個計數器在畫面中吧!過程中如果有任何不清楚的地方,都可以從下方連結中檢視實作的程式碼,或於 Github 專案說明頁點擊連結「JSX 中迴圈的使用」:

https://codepen.io/PJCHENder/pen/abdZKeQ

Imgur