iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
Modern Web

Angular、React、Vue 三框架實戰養成:從零打造高品質前端履歷網站系列 第 29

Day 29 React 資料化 – 用 Props 與 useState 管理 Skills 與 Projects

  • 分享至 

  • xImage
  •  

今日目標

  • 建立 資料檔(skills.ts / projects.ts)
  • map() 渲染 Skills 與 Projects
  • 在 Projects 加入 useState,準備之後做篩選 / 搜尋
  • 認識 Props:如何讓元件接受外部資料

1. 建立資料檔

src/data/skills.ts

export type Skill = {
  name: string
  category: 'frontend' | 'backend' | 'tools'
}

export const skills: Skill[] = [
  { name: 'HTML / CSS / SCSS', category: 'frontend' },
  { name: 'TypeScript', category: 'frontend' },
  { name: 'React / Vue / Angular', category: 'frontend' },
  { name: 'Node.js / Express', category: 'backend' },
  { name: 'Git / GitHub / Docker', category: 'tools' },
  { name: 'Vite / Webpack', category: 'tools' }
]

src/data/projects.ts

export type Project = {
  id: number
  slug: string
  title: string
  tech: string
  desc: string
  repo: string
  demo?: string
  featured?: boolean
}

export const projects: Project[] = [
  {
    id: 1,
    slug: 'maomao-shop',
    title: '毛毛購物(寵物電商)',
    tech: 'React + Node.js|購物車、結帳、RWD',
    desc: '主導前端架構,完成商品列表、購物流程與訂單頁。',
    repo: '#',
    demo: '#',
    featured: true
  },
  {
    id: 2,
    slug: 'line-bot-reservation',
    title: 'LINE Bot 預約系統',
    tech: 'Cloud Functions + LINE API|時段預約',
    desc: '整合 LINE 聊天介面與雲端排程,完成會員預約流程。',
    repo: '#'
  }
]


2. Skills 元件:用 Props 接資料

src/components/Skills.tsx

import React from 'react'
import type { Skill } from '../data/skills'

type Props = {
  items: Skill[]
}

export default function Skills({ items }: Props) {
  return (
    <section id="skills" className="container section">
      <h2>技能 Skillset</h2>
      <ul className="skill-grid">
        {items.map((s, i) => (
          <li key={i}>{s.name}</li>
        ))}
      </ul>
    </section>
  )
}


3. Projects 元件:用 useState 管理

src/components/Projects.tsx

import React, { useState } from 'react'
import type { Project } from '../data/projects'

type Props = {
  items: Project[]
}

export default function Projects({ items }: Props) {
  // 之後會加入篩選 / 搜尋,先用 useState 管理 view
  const [view, setView] = useState(items)

  return (
    <section id="projects" className="container section">
      <h2>作品集 Projects</h2>
      <div className="project-grid">
        {view.map((p) => (
          <article className="card" key={p.id}>
            <h3>{p.title}</h3>
            <p className="muted">{p.tech}</p>
            <p>{p.desc}</p>
            <div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
              <a className="btn small" href={p.demo}>Demo</a>
              <a className="btn small btn-outline" href={p.repo}>GitHub</a>
            </div>
          </article>
        ))}
      </div>
    </section>
  )
}


4. 在 App.tsx 匯入資料

import React from 'react'
import SiteHeader from './components/SiteHeader'
import Hero from './components/Hero'
import About from './components/About'
import Skills from './components/Skills'
import Projects from './components/Projects'
import Contact from './components/Contact'
import SiteFooter from './components/SiteFooter'

import { skills } from './data/skills'
import { projects } from './data/projects'

export default function App() {
  return (
    <><SiteHeader />
      <main>
        <Hero />
        <About />
        <Skills items={skills} />
        <Projects items={projects} />
        <Contact />
      </main>
      <SiteFooter />
    </>
  )
}


成果檢查清單 ✅

  • 頁面 Skills 改用 map() 生成列表
  • Projects 改用 map() 生成卡片
  • 元件由 靜態 → 資料驅動
  • Props 正確傳遞,Skills 與 Projects 可重複使用
  • Projects 已有 useState,準備明天加篩選

小心踩雷

  1. 忘了加 key
    • items.map((s) => <li>{s.name}</li>)
    • <li key={i}>…</li>(最好用 id,而不是 index)
  2. Props 型別漏掉
    • 在 TS 專案要明確定義 type Props,否則很難維護
  3. 把 useState 寫死
    • const [view] = useState(items)
    • ✅ 要留 setView,未來能更新

下一步(Day 30 預告)

  • 在 Projects 加入 搜尋框 + 篩選按鈕
  • useState 更新 view
  • 實作小小互動(全部 / 精選 / 關鍵字)
  • React 章節初步告一段落 🎉

上一篇
Day 28 React 起手式 – 用 Vite + TypeScript 初始化專案,建立骨架 ## 今日目標
系列文
Angular、React、Vue 三框架實戰養成:從零打造高品質前端履歷網站29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言