iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
JavaScript

JavaScript為什麼筆記本系列 第 13

Day 13|props.user 哪裡來?先從瀏覽器看到的畫面開始

  • 分享至 

  • xImage
  •  

Day 12 我們已經知道,一筆會員資料可以先用 Object 表示:

const user = {
  id: 1,
  name: 'Anthony',
  role: 'member'
}

也稍微看到 Supabase 查回來的資料,最後也可能長得很像這個 Object。

接下來,
我們先從瀏覽器真的要看到什麼內容開始,再一塊一塊把程式搬出去。


先不要有 MemberPage,也不要有 MemberCard

假設今天我要做一個很普通的會員頁。

畫面只要先長這樣:

會員中心

會員資料
姓名:Anthony
角色:member

最直覺的寫法,其實可以全部先放在 App.jsx

function App() {
  return (
    <main>
      <h1>會員中心</h1>

      <section>
        <h2>會員資料</h2>
        <p>姓名:Anthony</p>
        <p>角色:member</p>
      </section>
    </main>
  )
}

export default App

這時候先不要想 Component、props、Supabase。

只看結果。

App.jsx 現在就是在叫 React 顯示:

會員中心

會員資料
姓名:Anthony
角色:member

這一步很重要。

因為等一下出現的 MemberPageMemberCard,都不是突然冒出來的新東西。

它們原本都已經在這份 App 裡,只是還沒有被我們取名字、搬出去而已。


第一刀:把「整個會員頁」搬出去

現在回頭看剛才的 App.jsx

function App() {
  return (
    <main>
      <h1>會員中心</h1>

      <section>
        <h2>會員資料</h2>
        <p>姓名:Anthony</p>
        <p>角色:member</p>
      </section>
    </main>
  )
}

這整塊其實就是一個「會員頁」。

所以我們把它搬到:

src/pages/MemberPage.jsx
function MemberPage() {
  return (
    <main>
      <h1>會員中心</h1>

      <section>
        <h2>會員資料</h2>
        <p>姓名:Anthony</p>
        <p>角色:member</p>
      </section>
    </main>
  )
}

export default MemberPage

注意,畫面內容根本沒有變。

只是原本這一大塊寫在:

App.jsx

現在搬去:

MemberPage.jsx

而且幫這塊畫面取了一個名字:

MemberPage

App.jsx 怎麼辦?

既然剛才那一整塊會員頁已經搬走了,App.jsx 現在只需要把它拿回來使用:

import MemberPage from './pages/MemberPage'

function App() {
  return <MemberPage />
}

export default App

所以:

return <MemberPage />

不是一句很抽象的「App 顯示 MemberPage」。

它真正的意思是:

剛才原本直接寫在 App 裡的那整塊會員頁,現在搬到 MemberPage.jsx 了;App 在這裡把那整塊畫面叫回來。

瀏覽器看到的東西還是一樣:

會員中心

會員資料
姓名:Anthony
角色:member

只是程式從:

App
├─ 會員中心
└─ 會員資料

變成:

App
└─ MemberPage
   ├─ 會員中心
   └─ 會員資料

MemberPage 到底是什麼?

MemberPage 是我們幫「整個會員頁畫面」取的 Component 名字。

就是剛才這整塊:

<main>
  <h1>會員中心</h1>

  <section>
    <h2>會員資料</h2>
    <p>姓名:Anthony</p>
    <p>角色:member</p>
  </section>
</main>

這樣再看到App.jsx裡面的這個:

<MemberPage />

腦中就不應該只是一個英文單字。

應該可以直接想到:

會員中心
會員資料
姓名:Anthony
角色:member

第二刀:會員資料這一塊也有自己的名字

現在 MemberPage.jsx 是:

function MemberPage() {
  return (
    <main>
      <h1>會員中心</h1>

      <section>
        <h2>會員資料</h2>
        <p>姓名:Anthony</p>
        <p>角色:member</p>
      </section>
    </main>
  )
}

裡面有一小塊很明顯是在顯示會員資料:

<section>
  <h2>會員資料</h2>
  <p>姓名:Anthony</p>
  <p>角色:member</p>
</section>

那這一塊也可以搬出去。

而且這一塊看起來就像一張會員資料卡,所以我們幫它取名:

MemberCard

新增:

src/components/MemberCard.jsx
function MemberCard() {
  return (
    <section>
      <h2>會員資料</h2>
      <p>姓名:Anthony</p>
      <p>角色:member</p>
    </section>
  )
}

export default MemberCard

原本 MemberPage.jsx 裡的那一塊就可以換成:

<MemberCard />

完整變成:

import MemberCard from '../components/MemberCard'

function MemberPage() {
  return (
    <main>
      <h1>會員中心</h1>
      <MemberCard />
    </main>
  )
}

export default MemberPage

畫面還是完全一樣:

會員中心

會員資料
姓名:Anthony
角色:member

我們只是又把其中一塊搬出去。

所以 Component 關係現在變成:

App
└─ MemberPage
   ├─ 會員中心
   └─ MemberCard
      ├─ 會員資料
      ├─ 姓名:Anthony
      └─ 角色:member

壓成程式之間的關係就是:

App
↓
MemberPage
↓
MemberCard

這三個其實全部都是 Component。

MemberPage 並不是因為放在 pages/ 就不是 Component。

MemberCard 也不是因為放在 components/ 才突然變成 Component。

今天先用一個最簡單的判斷方法:

誰的 JSX 裡出現 <某個大寫名字 />,就代表這一層正在使用那個 Component。

例如:

function App() {
  return <MemberPage />
}

所以:

App → MemberPage

而:

function MemberPage() {
  return <MemberCard />
}

所以:

MemberPage → MemberCard

接起來就是:

App → MemberPage → MemberCard

(App用了MemberPage的component,MemberPage用了MemberCard的component)

現在還沒有 props

這點也很重要。

現在的 MemberCard.jsx 還是把資料直接寫死:

function MemberCard() {
  return (
    <section>
      <h2>會員資料</h2>
      <p>姓名:Anthony</p>
      <p>角色:member</p>
    </section>
  )
}

也就是說:

MemberCard
自己就寫死知道:
name = Anthony
role = member

可是 Day 12 我們明明已經學過,可以把一個會員整理成 Object:

const user = {
  id: 1,
  name: 'Anthony',
  role: 'member'
}

那是不是可以不要把 Anthony 寫死在 MemberCard 裡?

可以。

接下來才是今天真正要實務的 props。


先把 user 放在 MemberPage

先把會員資料放在 MemberPage.jsx

import MemberCard from '../components/MemberCard'

function MemberPage() {
  const user = {
    id: 1,
    name: 'Anthony',
    role: 'member'
  }

  return (
    <main>
      <h1>會員中心</h1>
      <MemberCard />
    </main>
  )
}

export default MemberPage

現在的情況是:

MemberPage
│
├─ 有 user 資料
│  ├─ name: Anthony
│  └─ role: member
│
└─ MemberCard

MemberCard 還不知道 user 的存在。

它還是寫死:

<p>姓名:Anthony</p>

所以問題來了:

MemberPage 有一包 user 資料,要怎麼交給下面的 MemberCard

這才輪到 props 出場。


props 其實 Day 10 已經看過一次

Day 10 我們有用props把資料傳給Header的範例。

App.jsx:

const siteName = 'Cruxilion'

<Header siteName={siteName} />

Header 裡:

function Header(props) {
  return <h1>{props.siteName}</h1>
}

當時的資料路線是:

App
│
│ siteName = 'Cruxilion'
│
│ <Header siteName={siteName} />
↓
Header
│
│ props.siteName
↓
Cruxilion

Day 13 現在其實要做完全一樣的事情。

只是換成:

MemberPage
│
│ user = { name: 'Anthony', role: 'member' }
│
│ ???
↓
MemberCard

把 Day 10 的寫法套過來,就是:

<MemberCard user={user} />

所以 MemberPage.jsx 變成:

import MemberCard from '../components/MemberCard'

function MemberPage() {
  const user = {
    id: 1,
    name: 'Anthony',
    role: 'member'
  }

  return (
    <main>
      <h1>會員中心</h1>
      <MemberCard user={user} />
    </main>
  )
}

export default MemberPage

user={user} 左右到底差在哪?

再把這一小段放大:

user={user}

左邊:

user=

是我們替這份 prop 取的名字。

右邊:

{user}

是在讀 MemberPage 裡這個 JavaScript 變數:

const user = {
  id: 1,
  name: 'Anthony',
  role: 'member'
}

所以:

<MemberCard user={user} />

可以先翻成人話:

把 MemberPage 裡的 user 這包資料,用 user 這個名字交給 MemberCard。


MemberCard 收到之後呢?

現在 MemberCard.jsx 就可以從:

function MemberCard() {
  return (
    <section>
      <h2>會員資料</h2>
      <p>姓名:Anthony</p>
      <p>角色:member</p>
    </section>
  )
}

改成:

function MemberCard(props) {
  return (
    <section>
      <h2>會員資料</h2>
      <p>姓名:{props.user.name}</p>
      <p>角色:{props.user.role}</p>
    </section>
  )
}

export default MemberCard

為什麼會有:

props.user

因為外面剛才寫的是:

<MemberCard user={user} />

所以可以先把 MemberCard 收到的 props 想成:

props = {
  user: {
    id: 1,
    name: 'Anthony',
    role: 'member'
  }
}

因此:

props.user

拿到:

{
  id: 1,
  name: 'Anthony',
  role: 'member'
}

再往裡面拿:

props.user.name

就是:

Anthony

這裡又回到 Day 12 的 Object。

昨天是:

user.name

今天只是這個 user 被包在 props 裡,所以變成:

props.user.name

再看一次:我們其實沒有換畫面

一開始全部寫在 App.jsx

App
├─ 會員中心
└─ 會員資料
   ├─ Anthony
   └─ member

第一次拆:

App
└─ MemberPage
   ├─ 會員中心
   └─ 會員資料
      ├─ Anthony
      └─ member

第二次拆:

App
└─ MemberPage
   ├─ 會員中心
   └─ MemberCard
      ├─ Anthony
      └─ member

加入 Object 與 props:

App
↓
MemberPage
│
│ user = {
│   name: 'Anthony',
│   role: 'member'
│ }
│
│ <MemberCard user={user} />
↓
MemberCard
│
│ props.user.name
│ props.user.role
↓
畫面

瀏覽器從頭到尾看到的還是:

會員中心

會員資料
姓名:Anthony
角色:member

我們只是把原本塞在一起的程式,一步一步拆成比較容易管理的 Component,然後再把資料接回去。


Supabase 現在才接進來

到目前為止,MemberPage 裡的會員資料是自己手寫的:

const user = {
  id: 1,
  name: 'Anthony',
  role: 'member'
}

但真正的會員網站,這筆資料通常會從資料庫來。

假設 Supabase 的 profiles 裡有:

id | name    | role
------------------------
1  | Anthony | member

查詢可能會看到:

const { data, error } = await supabase
  .from('profiles')
  .select('id, name, role')
  .eq('id', 1)
  .single()

今天先不看 await,也先不看 React 的 state。

只看查詢成功後的 data

{
  id: 1,
  name: 'Anthony',
  role: 'member'
}

等等。

這不就是前面手寫的 user 嗎?

所以可以先把未來真正的資料流記成:

Supabase
↓
data
↓
React 裡使用的 user
↓
<MemberCard user={user} />
↓
props.user
↓
props.user.name
↓
Anthony

也就是說,今天前面學的 Component 和 props 不會因為資料改成來自 Supabase 就全部作廢。

真正改變的主要是:

以前:user 是自己手寫

之後:user 的內容來自 Supabase

關掉上面的程式碼,試著回答

如果不往上偷看,現在能不能回答:

1. 一開始 MemberPage 裡的畫面原本放在哪裡?

2. 為什麼 App.jsx 裡可以只剩 <MemberPage />?

3. MemberCard 原本是哪一塊畫面?

4. MemberPage 和 MemberCard 哪一個才是 Component?

5. <MemberCard user={user} /> 左邊的 user 跟右邊的 user 各是什麼?

6. props.user.name 最後為什麼會拿到 Anthony?

如果第 1~3 題可以直接在腦中畫出畫面,我覺得這次就比單純背:

props = 傳資料

有用了。

因為真正重要的是知道:

這塊畫面原本在哪
↓
為什麼被搬出去
↓
誰把誰叫回來
↓
資料又怎麼沿著同一條路傳下去

今天到底學了什麼?

今天不是突然多學三個陌生檔案。

其實只是從一份最普通的 App.jsx 開始:

App
├─ 會員中心
└─ 會員資料

慢慢把它拆成:

App
↓
MemberPage
↓
MemberCard

然後把 Day 12 的會員 Object 沿著這條 Component 路線傳下去:

MemberPage
↓ user
MemberCard
↓ props.user.name
Anthony

最後才發現,未來 Supabase 查回來的資料,也只是接到這條已經看懂的路前面:

Supabase
↓
data
↓
user
↓
MemberCard
↓
畫面

先這樣..快睡著了ZzZz


上一篇
Day 12|Object 到底是什麼?從會員資料再偷看一下 Supabase
下一篇
Day 14|Figma 速覽:從 Community 範例快速到 React Component
系列文
JavaScript為什麼筆記本20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言