Day 12 我們已經知道,一筆會員資料可以先用 Object 表示:
const user = {
id: 1,
name: 'Anthony',
role: 'member'
}
也稍微看到 Supabase 查回來的資料,最後也可能長得很像這個 Object。
接下來,
我們先從瀏覽器真的要看到什麼內容開始,再一塊一塊把程式搬出去。
假設今天我要做一個很普通的會員頁。
畫面只要先長這樣:
會員中心
會員資料
姓名: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
這一步很重要。
因為等一下出現的 MemberPage、MemberCard,都不是突然冒出來的新東西。
它們原本都已經在這份 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 是我們幫「整個會員頁畫面」取的 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
這點也很重要。
現在的 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。
先把會員資料放在 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 出場。
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.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,然後再把資料接回去。
到目前為止,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