iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
JavaScript

JavaScript為什麼筆記本系列 第 11

# Day 11|用map()把Array加工變成畫面,選單10個不用寫10次

  • 分享至 

  • xImage
  •  

Day 10 我們終於把網站拆得比較像一個真的 React 專案:

main.jsx
↓
App.jsx
├─ Header.jsx
├─ main
└─ Footer.jsx

Header 裡面可能有:

<nav>
  首頁|服務|關於我們
</nav>

三個選單還好。

但如果網站慢慢長大:

首頁
服務
關於我們
部落格
常見問題
聯絡我們
登入

難道我要全部手寫?

<nav>
  <a>首頁</a>
  <a>服務</a>
  <a>關於我們</a>
  <a>常見問題</a>
  <a>聯絡我們</a>
  <a>登入</a>
</nav>

其實可以。

但仔細看會發現,我們一直重複寫的是:

<a>...</a>

真正一直改變的,其實只有裡面的資料。


結構跟資料混在一起了

例如:

<a>首頁</a>
<a>服務</a>
<a>關於我們</a>

可以拆成兩件事情看。

結構是:

<a>...</a>

資料則是:

首頁
服務
關於我們

如果今天公司說:

幫我把「關於我們」移到第二個,然後再加一個「案例分享」。

全部手寫當然還是可以改。

但當選單愈來愈多時,資料跟畫面結構全部黏在一起,就開始不好管理了。

所以我們可以先把資料拿出來。


先認識 Array

JavaScript 可以這樣寫:

const menuItems = [
  '首頁',
  '服務',
  '關於我們'
]

這是一個 Array,也就是陣列。

現在先不用背完整定義。

可以先把它理解成:

menuItems
↓
一串有順序的資料

裡面有三筆:

首頁
服務
關於我們

如果想拿第一筆:

menuItems[0]

結果是:

首頁

第二筆:

menuItems[1]

結果是:

服務

JavaScript 很經典的一件事情就是:

第一個位置不是 1
而是 0

所以:

const menuItems = ['首頁', '服務', '關於我們']

可以想成:

[0] 首頁
[1] 服務
[2] 關於我們

但有資料之後,怎麼變回畫面?

現在我們有:

const menuItems = [
  '首頁',
  '服務',
  '關於我們'
]

但瀏覽器最後還是要看到類似:

<a>首頁</a>
<a>服務</a>
<a>關於我們</a>

這時候就碰到 React 裡非常常看到的:

map()

例如:

menuItems.map((item) => {
  return item
})

這一串第一次看真的滿像咒語。

我以前看到:

(item) =>

第一反應也是:這箭頭到底哪來的?

箭頭函式後面會再正式拆。

今天先知道:

(item) => {
  ...
}

是一個 function 就可以了。


map() 到底在幹嘛?

假設:

const menuItems = [
  '首頁',
  '服務',
  '關於我們'
]

寫:

menuItems.map((item) => {
  return item
})

map() 會把資料一筆一筆拿出來。

概念上:

第 1 次:item = '首頁'
第 2 次:item = '服務'
第 3 次:item = '關於我們'

這裡的:

item

不是整個 menuItems

它只是「這一輪拿到的那一筆資料」。

然後每一輪都會執行:

return item

所以:

首頁 → return 首頁
服務 → return 服務
關於我們 → return 關於我們

最後 map() 會把每一次 return 的結果收集起來。

結果仍然是一個新的 Array:

[
  '首頁',
  '服務',
  '關於我們'
]

map()這個函式括號裡面的參數可以隨便取。可以叫做fruit,就像是從Array裡面拿出水果。


map() 比較像一台加工機

如果只是:

return item

看不太出 map() 有什麼用。

換一個例子。

const prices = [100, 200, 300]

現在全部打八折:

const result = prices.map((price) => {
  return price * 0.8
})

它會一筆一筆處理:

100 → 80
200 → 160
300 → 240

最後:

result

就是:

[80, 160, 240]

這時候 map() 的感覺就比較明顯了。

原本每一筆資料
↓
經過某個加工
↓
return 新的東西
↓
全部組成新的 Array

map最後回傳的東西就是一個新的Array。


map() 不會自己加逗號

那~
如果結果是:

['首頁', '服務', '關於我們']

那是不是 map() 自己幫我們用逗號隔開?

不是。

逗號只是 Array 裡不同資料之間的分隔。

[
  '首頁',
  '服務',
  '關於我們'
]

代表裡面有三筆資料。

如果真的希望把 Array 變成:

首頁|服務|關於我們

比較像是:

menuItems.join('|')

map()join() 做的是不同事情。

可以先這樣分:

map()
→ 每一筆拿去加工
→ 得到新的 Array

join()
→ 把 Array 裡面的資料串成一個字串

所以map()join()都只是處理Array的方法之一,還有很多種方法~~


那 React 為什麼這麼愛 map()

回到我們真正的問題。

原本資料:

const menuItems = [
  '首頁',
  '服務',
  '關於我們'
]

如果:

100 → 80

可以用 map()

那:

首頁 → <a>首頁</a>

其實也是一種加工。

所以可以寫:

menuItems.map((item) => {
  return <a>{item}</a>
})

概念上就是:

'首頁'
↓
<a>首頁</a>

'服務'
↓
<a>服務</a>

'關於我們'
↓
<a>關於我們</a>

我們只是把:

數字 → 新數字

換成:

資料 → JSX

而已。

你可能會問JSX是什麼?JSX就是看起來很像是html的語法,但實際上是寫在JavaScript裡面:

例如這個function裡面像是html的都是jsx

function App() {
  return (
    <div>
      <h1>Cruxilion</h1>
      <p>Hello React</p>
    </div>
  )
}

這時候突然就比較能理解,為什麼 React 裡到處都是:

something.map(...)

因為前端很常做的事情,就是:

把一堆資料轉成一堆畫面。


放回 Header.jsx

現在 Header 可以變成:

function Header() {
  const menuItems = [
    '首頁',
    '服務',
    '關於我們'
  ]

  return (
    <header>
      <nav>
        {menuItems.map((item) => {
          return <a>{item}</a>
        })}
      </nav>
    </header>
  )
}

export default Header

這裡又看到 Day 10 出現過的:

{}

在 JSX 裡:

{menuItems.map(...)}

可以先理解成:

JSX 走到這裡,請執行這段 JavaScript。

map() 最後產生:

<a>首頁</a>
<a>服務</a>
<a>關於我們</a>

React 再把這些元素顯示到畫面上。


資料的身分證?

寫到這裡,很可能會看到 React 跳出:

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

第一次看到其實滿莫名其妙。

明明畫面都出來了。

為什麼還要 key

假設資料是:

const menuItems = [
  { id: 1, name: '首頁' },
  { id: 2, name: '服務' },
  { id: 3, name: '關於我們' }
]

我們可以寫:

menuItems.map((item) => {
  return (
    <a key={item.id}>
      {item.name}
    </a>
  )
})

這裡:

key={item.id}

可以先把它想成每一筆資料的「身分證」。然後照這個寫法,就是在JS的function裡面使用{key:value}。


React 為什麼需要知道「誰是誰」?

一開始:

key=1 → 首頁
key=2 → 服務
key=3 → 關於我們

後來我們把順序改成:

const menuItems = [
  { id: 1, name: '首頁' },
  { id: 3, name: '關於我們' },
  { id: 2, name: '服務' }
]

現在變成:

key=1 → 首頁
key=3 → 關於我們
key=2 → 服務

因為 key 沒變,React 可以知道:

服務沒有消失
只是移到別的位置

關於我們也不是新出現
只是換位置

很像班上換座位。

如果:

A001 王小明
A002 陳小華
A003 林小美

今天三個人全部換位置,老師還是知道誰是誰。

因為老師不是靠:

第一個座位
第二個座位
第三個座位

認人。

而是靠:

A001
A002
A003

React 的 key 大概就是這種角色。

所以可以先記:

map()
→ 把資料變成很多 JSX

key
→ 幫 React 認出每一筆資料

為什麼通常用 id 當 key?

如果資料本來就有:

{
  id: 2,
  name: '服務'
}

那:

key={item.id}

通常很合理。

因為 id 本來就是這筆資料自己的身分。

例如:

function Header() {
  const menuItems = [
    { id: 1, name: '首頁' },
    { id: 2, name: '服務' },
    { id: 3, name: '關於我們' }
  ]

  return (
    <header>
      <nav>
        {menuItems.map((item) => {
          return (
            <a key={item.id}>
              {item.name}
            </a>
          )
        })}
      </nav>
    </header>
  )
}

照上面這串程式碼,可以得到的結果:

[
  <a key={1}>首頁</a>,
  <a key={2}>服務</a>,
  <a key={3}>關於我們</a>
]

現在 Header 的選單資料和畫面結構終於分開了。

想新增:

案例分享

主要是新增一筆資料,而不是再複製一整段 <a>


今天到底學了什麼?

今天一開始的問題其實很單純。

原本我們是這樣寫選單:

<a>首頁</a>
<a>服務</a>
<a>關於我們</a>

後來把真正會改變的資料抽出來:

const menuItems = [
  '首頁',
  '服務',
  '關於我們'
]

再用:

map()

把 Array 裡的資料一筆一筆拿出來。

menuItems.map((item) => {
  return <a>{item}</a>
})

所以今天其實串起了這條路:

Array
↓
map() 一筆一筆拿資料
↓
item 代表這一輪拿到的資料
↓
return 加工後的結果
↓
得到新的 Array

在一般 JavaScript 裡,可能是:

100
↓
80

在 React 裡,則很常是:

'首頁'
↓
<a>首頁</a>

也就是:

資料
↓
JSX
↓
畫面

這也是為什麼 React 裡這麼常看到 map()

另外今天還碰到了:

join()

它和 map() 不一樣。

map()
→ 一筆一筆加工
→ 最後得到新的 Array

join()
→ 把 Array 裡的資料接在一起
→ 最後得到一個 String

最後,當 React 要顯示一整串 map() 產生的 JSX 時,我們又碰到了:

key={item.id}

key 可以先把它理解成 React 用來辨認每一筆清單元素的「身分證」。

如果 map() 這一輪直接回傳:

<a key={item.id}>
  {item.name}
</a>

最外層是 <a>,所以 key 就放在 <a> 上。


但我們好像偷偷用了還沒正式學的東西

為了讓每個選單都有自己的 id,剛才我們把資料改成了:

const menuItems = [
  { id: 1, name: '首頁' },
  { id: 2, name: '服務' },
  { id: 3, name: '關於我們' }
]

這時候 Array 裡面裝的,已經不只是:

'首頁'

而是:

{
  id: 1,
  name: '首頁'
}

這個 {} 不是 JSX。

它是 JavaScript 的 Object。

裡面的資料是:

id   → 1
name → '首頁'

也就是一組一組的:

key: value

現在只有 idname 還看不太出它的必要。

但網站不可能永遠只有:

首頁
服務
關於我們

假設一個選單之後還要知道:

文字
網址
是否顯示
icon
權限

那一筆資料可能就會慢慢長成:

{
  id: 1,
  name: '首頁',
  href: '/',
  visible: true,
  icon: 'home',
  role: 'all'
}

這時候就會發現:

Array
→ 負責裝「很多筆選單」

Object
→ 負責描述「其中一筆選單有哪些資料」

所以我們下一個問題就很自然了:

下一篇就從 JavaScript 的 Object 開始~~

孩子睡了~晚安ZzZz 好累QQ


上一篇
Day 10|把 Header、Footer 拆成不同的 JSX
下一篇
Day 12|Object 到底是什麼?從會員資料再偷看一下 Supabase
系列文
JavaScript為什麼筆記本20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言