iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
JavaScript

JavaScript為什麼筆記本系列 第 12

Day 12|Object 到底是什麼?從會員資料再偷看一下 Supabase

  • 分享至 

  • xImage
  •  

Day 11 最後,我們把選單從單純的字串:

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

慢慢改成:

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

當時其實已經偷偷碰到一個新的東西:

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

這個 {} 到底是什麼?

如果只是背一句:

這叫 Object,中文叫物件。

大概過幾天又忘了。

所以這次不從定義開始,直接拿一個實務上很常碰到的東西來看:會員系統

而且今天不只停在 Object。

我也想先偷看一下:如果未來會員資料真的放進 Supabase,資料到底怎麼一路跑到 React 前端?中間為什麼不一定需要再架一個 Node.js 後端,我覺得也可以稍微說一下,雖然也不是什麼大問題


找個地方放會員的值?

假設網站現在有一個會員:

姓名:Anthony
Email:anthony@example.com
角色:member
是否登入:true

最直覺的做法,可以全部拆成變數:

const userName = 'Anthony'
const userEmail = 'anthony@example.com'
const userRole = 'member'
const isLoggedIn = true

這樣不是不能用。

但問題是,這四個東西其實都在描述同一個人

如果未來還要加:

會員編號
頭像
電話
註冊時間
權限
方案

變數就會愈來愈多。

這時候通常比較想要的是:

能不能把「Anthony 這個會員」相關的資料包在一起?

JavaScript 的 Object 就很適合做這件事。

const user = {
  id: 1,
  name: 'Anthony',
  email: 'anthony@example.com',
  role: 'member',
  isLoggedIn: true
}

現在不是五個散落的變數。

而是有一個:

user

裡面放著這個會員的不同資料。

可以先把 Object 想成:

一筆有欄位名稱的資料。


name: 'Anthony' 到底怎麼看?

例如:

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

裡面有兩組:

name → Anthony
role → member

JavaScript 寫成:

key: value

所以:

name: 'Anthony'

可以先讀成:

name 這個欄位 它的值是 Anthony

而:

role: 'member'

就是:

role 這個欄位 它的值是 member

那我要怎麼拿到 Object 裡面的值?

如果:

const user = {
  id: 1,
  name: 'Anthony',
  email: 'anthony@example.com',
  role: 'member'
}

我要拿名字,可以寫:

user.name

結果是:

Anthony

拿角色:

user.role

結果是:

member

拿 Email:

user.email

結果就是:

anthony@example.com

所以這個 . 可以先把它念成人話:

user.name

就是:

user 的 name。

而:

user.role

就是:

user 的 role。

這樣其實比先背「dot notation」好記很多。


Array 跟 Object 的差異

Day 11 的 Array:

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

比較像:

一串有順序的資料。

所以可以靠位置拿:

menuItems[0]
menuItems[1]
menuItems[2]

但 Object:

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

不是靠「第幾個」。

而是靠欄位名稱:

user.name
user.role

可以先這樣區分:

Array
→ 我有一串資料
→ 常常在意順序
→ 用 [0]、[1] 取資料

Object
→ 我有一筆資料
→ 裡面有很多欄位
→ 用 .name、.role 取資料

那為什麼 Array 裡面又可以放 Object?

因為會員通常不會只有一個。

假設有三個會員:

const users = [
  {
    id: 1,
    name: 'Anthony',
    role: 'member'
  },
  {
    id: 2,
    name: 'Amy',
    role: 'admin'
  },
  {
    id: 3,
    name: 'Tom',
    role: 'member'
  }
]

這時候外面:

[
  ...
]

是 Array。

裡面的每一筆:

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

是一個 Object。

所以整體可以想成:

users Array
│
├─ user Object
│  ├─ id
│  ├─ name
│  └─ role
│
├─ user Object
│  ├─ id
│  ├─ name
│  └─ role
│
└─ user Object
   ├─ id
   ├─ name
   └─ role

這時候 Day 11 的 map() 就又回來了。

users.map((user) => {
  return user.name
})

每一輪的 user 分別會是:

{ id: 1, name: 'Anthony', role: 'member' }
{ id: 2, name: 'Amy', role: 'admin' }
{ id: 3, name: 'Tom', role: 'member' }

所以:

user.name

會依序拿到:

Anthony
Amy
Tom

Day 11 的:

map()

跟今天的:

Object

就在這裡接起來了。


放回 React:會員資料真的可以直接變成畫面

例如現在有:

const user = {
  id: 1,
  name: 'Anthony',
  email: 'anthony@example.com',
  role: 'member'
}

React 可以直接寫:

function MemberCard() {
  const user = {
    id: 1,
    name: 'Anthony',
    email: 'anthony@example.com',
    role: 'member'
  }

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <p>{user.role}</p>
    </div>
  )
}

畫面就會得到類似:

Anthony
anthony@example.com
member

這裡其實沒有什麼神奇的 React 魔法。

只是:

user.name
user.email
user.role

把 Object 裡的值拿出來,再放進 JSX。


會員系統的選單也可以是一堆 Object

真的會員網站通常還會有選單。

例如:

const menuItems = [
  {
    id: 1,
    name: '首頁',
    url: '/',
    requiresLogin: false
  },
  {
    id: 2,
    name: '會員中心',
    url: '/member',
    requiresLogin: true
  }
]

這次每一筆 menu 不只有名字。

還知道:

它叫什麼
網址在哪
需不需要登入

也就是:

item.name
item.url
item.requiresLogin

React 就可以根據這些資料決定要畫什麼。

例如先做一個非常簡化的版本:

function Header() {
  const user = {
    id: 1,
    name: 'Anthony',
    isLoggedIn: true
  }

  const menuItems = [
    {
      id: 1,
      name: '首頁',
      url: '/',
      requiresLogin: false
    },
    {
      id: 2,
      name: '會員中心',
      url: '/member',
      requiresLogin: true
    }
  ]

  return (
    <nav>
      {menuItems.map((item) => {
        if (item.requiresLogin && !user.isLoggedIn) {
          return null
        }

        return (
          <a key={item.id} href={item.url}>
            {item.name}
          </a>
        )
      })}
    </nav>
  )
}

這段開始有點像真的網站了。

但今天先不要一次拆太多。

現在只要先看懂:

item.requiresLogin

是在讀 Object 裡的欄位。

user.isLoggedIn

也是在讀 Object 裡的欄位。

至於:

&&
!
return null

之後再說好了。


這些程式碼到底放哪?是在 dist 嗎?

我原本有一個疑問:

如果真的做會員系統,這些東西是放在 dist 裡,還是 components 裡?

現在可以先建立一個很重要的分界。

平常自己寫的 React 原始碼,主要是在:

src/

例如未來可能長成:

src/
├─ components/
│  ├─ Header.jsx
│  └─ MemberCard.jsx
│
├─ pages/
│  ├─ Login.jsx
│  └─ Member.jsx
│
├─ App.jsx
└─ main.jsx

components/ 只是 src/ 裡的一部分。

像:

Header
MemberCard
Button
Sidebar

這種可以重複使用的小區塊,很適合放 components/

但整個會員系統不會全部塞在一個 component 裡。

而:

dist/

不是平常寫程式的地方。

通常是執行:

npm run build

之後,Vite 幫我們產生的部署成品。

可以先記:

src/
→ 我寫的原始碼

npm run build
↓

dist/
→ Vite 整理、打包後準備部署的成品

所以不要跑去 dist 裡手改會員系統。


但真正的會員資料,總不會一直手寫在 React 裡吧?

到目前為止,我們都是自己寫:

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

這當然只是練習。

真正的網站裡,會員資料通常會放在資料庫。

後面我們使用 Supabase,大概會開始看到這種資料流:

PostgreSQL
↓
Supabase Data API
↓
supabase-js
↓
React
↓
畫面

假設資料庫的 profiles table 裡有:

id | name    | role
1  | Anthony | member
2  | Amy     | admin

前端之後可能會寫出類似:

const { data, error } = await supabase
  .from('profiles')
  .select('*')

這段今天先不用會寫。

先注意 data 回到 JavaScript 世界之後,概念上可能長得像:

[
  {
    id: 1,
    name: 'Anthony',
    role: 'member'
  },
  {
    id: 2,
    name: 'Amy',
    role: 'admin'
  }
]

咦?

這不就是:

Array
↓
裡面很多 Object

又回到 Day 11、Day 12 了。

所以前面學:

user.name

或:

users.map((user) => {
  return user.name
})

不只是拿來操作我們自己手寫的假資料。

之後從後端 API 拿回來的資料,也常常會變成這種 JavaScript Array / Object,再交給 React 顯示。


Supabase 到前端,中間為什麼沒有我自己的 Node.js?

使用 Supabase 時,不一定。

Supabase 本身已經幫我們提供一層可以存取 PostgreSQL 的 Data API,而且 supabase-js 可以直接從瀏覽器呼叫它。

所以一個簡單的 React + Supabase 專案,可以先長成:

Browser
↓
React
↓
supabase-js
↓ HTTPS
Supabase Data API
↓
PostgreSQL

也就是:

我不一定要先自己寫一個 Express / Node.js API Server,React 才能拿到資料庫資料。

這也是 Supabase 很方便的地方之一。

它已經幫我們做掉很多傳統後端會先處理的基礎能力,例如資料 API、登入驗證、資料庫與檔案儲存等。


前端可以直接碰資料庫 API,那安全嗎?

看到這裡下一個直覺問題大概就是:

等等,如果瀏覽器可以直接呼叫 Supabase,那使用者打開 DevTools,不就也看得到這些請求?

對,所以正式系統不會因為「可以從前端直接呼叫」就把所有資料全部開放。

後面會碰到一個很重要的東西:

RLS
Row Level Security

它會負責限制:

這個登入者
到底可以讀哪些 row
可以改哪些 row

例如:

Anthony
→ 可以讀自己的 profile
→ 不代表可以亂讀所有會員資料

RLS 今天先停在這一句就好。

現在如果直接開始寫 policy、auth.uid()、權限角色,Day 12 又會瞬間變成另外一門課。


把今天整條資料流接起來

Day 11 我們有:

Array
↓
map()
↓
JSX

Day 12 加入 Object 之後:

Array
↓
很多 Object
↓
map()
↓
user.name / user.role
↓
JSX

如果再稍微偷看未來的 Supabase:

PostgreSQL
↓
Supabase Data API
↓
supabase-js
↓
JavaScript Array / Object
↓
map()
↓
React JSX
↓
畫面

這時候 Object 就不再只是一本 JavaScript 教科書裡的語法啦!(撒花
它開始站在真正前後端資料流的中間。

資料庫裡的一列:

id | name    | role
1  | Anthony | member

來到 JavaScript 世界,很可能就變成:

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

React 再去拿:

user.name
user.role

把資料畫出來。


今天真正學到的不是「會員登入」

我們只是拿會員系統當例子,把幾個原本分開的世界第一次接起來:

JavaScript Object
↓
React
↓
Supabase Data API
↓
PostgreSQL

現在至少可以先回答:

一個會員
→ 可以用一個 Object 表示

很多會員
→ 可以是 Array 裡很多 Object

React 怎麼顯示
→ 讀 Object 欄位,再產生 JSX

真正資料從哪裡來
→ 之後可以從 Supabase / PostgreSQL 查回來

中間一定要自己的 Node.js 嗎
→ 不一定,Supabase 可以提供前端直接呼叫的 Data API

那安全怎麼辦
→ 後面再學 RLS

但這個會員系統現在還有一個很假的地方

目前我們寫:

const user = {
  name: 'Anthony',
  isLoggedIn: true
}

它從程式一開始就是登入狀態。

可是正常會員系統應該比較像:

一開始沒有登入
↓
user 可能是 null
↓
使用者輸入帳號密碼
↓
按下登入
↓
user 變成一個會員 Object
↓
畫面跟著改變

問題來了:

JavaScript 裡的資料變了,React 怎麼知道畫面也要跟著變?

這個問題,後面就會把我們帶到 state

但在進 state 以前,我還想先把 Object 怎麼傳進 Component,以及畫面怎麼從設計稿拆成 Component 看清楚。

所以接下來,這個「假會員」會慢慢長成真的會員系統。

每天都好累...寶寶睡了、太太睡了,然後再整理筆記是現在晚上的日常 Zz 晚安


上一篇
# Day 11|用map()把Array加工變成畫面,選單10個不用寫10次
下一篇
Day 13|props.user 哪裡來?先從瀏覽器看到的畫面開始
系列文
JavaScript為什麼筆記本20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言