跳至主要内容

87 篇文章 含有標籤「前端工程」

檢視所有標籤

GraphQL 入門教學筆記(ESM 模組版)| 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在傳統的 REST API 中,前端往往需要根據不同的需求,呼叫多個端點取得資料,有時還會遇到資料過多(Over-fetching)或不足(Under-fetching)的問題。為了解決這些痛點,Facebook 在 2015 年開源了 GraphQL,一種靈活且高效率的查詢語言。

GraphQL 讓前端可以明確指定想要的資料欄位,伺服器僅回傳需要的資料,改善資料浪費與重複請求的問題。此語言既可以查詢(Query)、也可以修改資料(Mutation),更支援訂閱(Subscription)即時變化,成為 REST 的強大替代方案。

本文將以 ESM(ECMAScript Module)模組格式,搭配現代 Node.js 環境與 Apollo Server,手把手實作一個簡易 GraphQL API。


重點摘要

  • GraphQL 是什麼? 一種用於 API 的查詢語言(Query Language),由 Facebook 開發。它不是資料庫,而是用來建立伺服器與用戶端之間通訊的一種規格。

  • 特色與優點

    • 客戶端可以精確指定所需資料
    • 單一端點處理所有請求
    • 減少 over-fetching / under-fetching 問題
    • 強型別系統(Schema 定義清楚 API 結構)
    • 自帶文件化功能(如 Apollo Studio / GraphQL Playground)
  • 核心概念

    • Schema:定義資料型別與查詢結構
    • Query:讀取資料
    • Mutation:修改資料(新增、刪除、更新)
    • Resolver:實際執行資料存取邏輯的函式
    • Type:定義資料欄位與型別,例如 type Book { title: String }
  • GraphQL vs REST

    特性RESTGraphQL
    資料請求多個端點單一端點
    回傳資料固定欄位,可能過多或不足客戶端可自訂所需欄位
    文件維護手動撰寫 Swagger/OpenAPI自動產生於 Playground / Studio
    資料關聯查詢多次 HTTP 請求一次查詢可取得巢狀關聯資料

實作範例(Node.js + Apollo Server + ESM)

一、專案初始化

建立資料夾並初始化:

mkdir graphql-esm-demo
cd graphql-esm-demo
npm init -y
npm install @apollo/server graphql

修改 package.json 啟用 ESM 模組:

{
"type": "module"
}

二、撰寫 GraphQL API(server.js)

建立 server.js

// server.js
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// 模擬資料
const books = [
{ title: 'GraphQL 教學指南', author: 'Alice' },
{ title: '現代 JavaScript', author: 'Bob' },
];

// 定義 Schema
const typeDefs = `
type Book {
title: String
author: String
}

type Query {
books: [Book]
}
`;

// 定義 Resolver
const resolvers = {
Query: {
books: () => books,
},
};

// 建立 Apollo Server 實例
const server = new ApolloServer({ typeDefs, resolvers });

// 啟動伺服器
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});

console.log(`🚀 Server ready at ${url}`);

三、啟動伺服器並測試

執行伺服器:

node server.js

啟動後會顯示伺服器位置(通常是 http://localhost:4000 Apollo Studio,或使用 Postman / Altair / curl 測試。


四、查詢範例(GraphQL Playground 中輸入)

query {
books {
title
author
}
}

我們也可以僅查詢某個欄位,例如:

query {
books {
title
}
}

這正是 GraphQL 的威力所在:我們只拿我們需要的資料。


延伸功能與建議學習方向

  • Mutation 實作(新增、刪除資料) 可以加入 Mutation 類型與 resolver,實作 POST/PUT 功能。

  • 資料來源整合 Resolver 可以整合 REST API、資料庫、第三方服務等來源。

  • 驗證與權限 可透過 Context 傳入使用者驗證資訊,進行資料存取控管。

  • 使用工具

    • Apollo Studio:視覺化介面與測試工具
    • GraphQL Codegen:產生型別定義與前端 Hooks
    • Relay / urql / Apollo Client:前端整合工具

總結

GraphQL 提供比 REST 更彈性與效率的 API 設計方式,尤其適合需要頻繁前後端協作或資料結構變化頻繁的專案。透過本篇教學,我們已經學會如何使用 Apollo Server 以 ESM 模組撰寫簡單的 GraphQL API,並實際查詢資料。

未來我們可以嘗試進一步加入 Mutation、串接資料庫(如 MongoDB、PostgreSQL)、前端 Apollo Client 等,打造一個全端的 GraphQL 應用。

Monorepo 入門教學筆記:使用 npm Workspaces 管理多專案 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

一、什麼是 Monorepo?

Monorepo(Monolithic Repository) 是一種將多個相互關聯的專案(如前端、後端、共用函式庫等)統一放在同一個 Git 倉庫中進行版本管理的架構策略。這種做法可提升跨專案開發效率、共用程式碼的一致性,並簡化 CI/CD 流程。

相較於 Multi-repo(每個專案獨立倉庫),Monorepo 具有以下幾項優勢:

優點

  • 統一依賴管理:使用者可於 root 層集中管理所有套件與版本。
  • 共用程式碼方便:子專案間可以引用彼此模組,而不需額外發佈。
  • 版本一致性高:統一管理 Node.js 與 TypeScript 設定,避免各專案設定不一致。
  • 原子性變更:可一次提交多個模組的改動,有利於大型重構與追蹤問題。

缺點

  • 權限與管理更複雜:團隊需要良好的規範以避免耦合過深。
  • 初期學習門檻略高:需要理解 workspace 機制與設定方式。
  • 大型 repo 操作效能問題:隨專案規模增長,Git 操作與工具效能需特別注意。

二、何謂 npm Workspaces?

npm 7 開始,Node.js 官方就原生支援 Workspaces。它允許開發者在單一專案下,透過一個 package.json 管理多個子專案(workspace packages)。這讓 npm 也能像 Yarn 或 pnpm 一樣支援 monorepo。

特點:

  • 原生支援,不需額外工具
  • 使用方式簡單直觀
  • 適合小型與中型 monorepo 專案

三、建立 Monorepo 專案實例(使用 npm workspaces)

1. 初始化根目錄

mkdir my-monorepo
cd my-monorepo
npm init -y

修改根目錄下的 package.json 加入 workspaces 設定:

{
"name": "my-monorepo",
"version": "1.0.0",
"private": true,
"workspaces": ["packages/*"]
}

這表示所有子專案會放在 packages/ 目錄中,支援通配符 *


2. 建立三個子專案

  • packages/utils:共用函式庫
  • packages/backend:Node.js API
  • packages/frontend:前端應用(可用 React/Vite)
mkdir -p packages/utils
mkdir -p packages/backend
mkdir -p packages/frontend

初始化子專案:

cd packages/utils
npm init -y

cd ../backend
npm init -y

cd ../frontend
npm init -y

3. 撰寫共用模組(utils)

packages/utils/index.js

function greet(name) {
return `Hello, ${name}`;
}

module.exports = { greet };

packages/utils/package.json

{
"name": "@my/utils",
"version": "1.0.0",
"main": "index.js"
}

4. 使用共用模組於 backend

packages/backend/index.js

const { greet } = require("@my/utils");

console.log(greet("Backend"));

packages/backend/package.json 加入依賴:

{
"name": "@my/backend",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@my/utils": "1.0.0"
}
}

當你在 monorepo 根目錄執行:

npm install

npm 會自動建立 workspace 之間的 symbolic link,將 @my/utils 套件安裝給 @my/backend,無需手動 npm publish

執行 backend:

node packages/backend/index.js

輸出結果為:

Hello, Backend

5. 多專案腳本管理

在 root 的 package.json 中可設定統一指令:

{
"scripts": {
"start:backend": "npm --workspace @my/backend start",
"start:frontend": "npm --workspace @my/frontend start"
}
}

若在 @my/backendpackage.json 中定義了:

{
"scripts": {
"start": "node index.js"
}
}

則可於根目錄執行:

npm run start:backend

四、進階使用技巧

1. 同時執行所有 workspace 指令

npm run build --workspaces

這會同時執行所有子專案中名為 build 的 script。

2. 安裝依賴給特定子專案

npm install lodash --workspace @my/backend

等同於進到 packages/backend 下執行 npm install lodash

3. 使用 TypeScript 建構共用型別

若你使用 TypeScript,可將 packages/utils 改為 index.ts 並加上 types 欄位:

{
"name": "@my/utils",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts"
}

建議搭配 tsc -b 支援 project references,以改善大型專案建置效能。


五、適合使用 Monorepo 的情境

  • 前後端一體化開發(Fullstack)
  • 套件開發與組合應用(如微前端架構)
  • 共用型別與邏輯模組(utility / types / domain model)
  • 多人協作、模組劃分清晰的大型產品

六、總結

Monorepo 是一種有效提升多專案整合效率的開發模式。透過 npm Workspaces,你可以不用額外安裝任何工具,即可快速建立一個結構清晰的 monorepo。這種架構不僅適合大型產品團隊,也非常適合個人或小型團隊開發多模組系統時採用。

建議開發者從簡單的 monorepo 起步,並根據團隊需求逐步導入版本控制策略、模組邊界規範與自動化部署流程,以發揮 Monorepo 架構的最大價值。


若你有意將這份架構應用到 React、Next.js、NestJS、TypeScript、Lerna 等進階框架或部署情境,歡迎告訴我,我可以提供進一步的範例與最佳實踐建議。

React 與 Next.js 入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

1. React 簡介

React 是由 Facebook(現 Meta)開發的 JavaScript 前端函式庫,主要用於構建 UI 元件。它採用組件化開發方式,並透過 Virtual DOM 提升效能。

2. Next.js 簡介

Next.js 是一個基於 React 的框架,提供伺服器端渲染(SSR)、靜態網站生成(SSG)等功能,讓開發者能夠更輕鬆地開發 SEO 友好的應用程式。

3. 安裝 Next.js

使用 create-next-app 初始化 Next.js 專案:

npx create-next-app@latest my-next-app
cd my-next-app
npm run dev

這將會啟動開發伺服器,預設運行於 http://localhost:3000

4. Next.js 核心概念

4.1 頁面(Pages)

Next.js 使用 pages/ 目錄來定義路由,每個 .js.tsx 文件會自動成為一個頁面。

範例:pages/index.js

export default function Home() {
return <h1>歡迎來到 Next.js!</h1>;
}

新增 pages/about.js

export default function About() {
return <h1>關於我們</h1>;
}

瀏覽 / 會載入 index.js,瀏覽 /about 會載入 about.js

使用 next/link 來建立導航連結:

import Link from "next/link";

export default function Navbar() {
return (
<nav>
<Link href="/">首頁</Link> | <Link href="/about">關於</Link>
</nav>
);
}

4.3 頁面中的 props

Next.js 支援 getServerSideProps(伺服器端渲染)和 getStaticProps(靜態生成)。

伺服器端渲染(SSR)

export async function getServerSideProps() {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
return { props: { data } };
}

export default function Page({ data }) {
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

靜態生成(SSG)

export async function getStaticProps() {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
return { props: { data } };
}

export default function Page({ data }) {
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

5. API 路由(API Routes)

pages/api/ 目錄下建立 API 端點。

範例:pages/api/hello.js

export default function handler(req, res) {
res.status(200).json({ message: "Hello, API!" });
}

請求 /api/hello 會返回 JSON 資料。

6. 使用全域狀態管理(React Context)

import { createContext, useContext, useState } from "react";

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}

export function useTheme() {
return useContext(ThemeContext);
}

_app.js 中使用 Provider:

import { ThemeProvider } from "../context/ThemeContext";

export default function MyApp({ Component, pageProps }) {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
);
}

在元件中存取狀態:

import { useTheme } from "../context/ThemeContext";

export default function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
切換主題(目前:{theme}
</button>
);
}

7. 總結

Next.js 提供了比 React 更豐富的功能,如內建路由、伺服器端渲染(SSR)和 API 路由,適合開發高效能與 SEO 友好的網站。熟悉這些核心概念後,你可以更輕鬆地構建現代化的前端應用程式。

補充:可選搭配技術(實務常見)

技術目的
Tailwind CSS快速開發 UI 樣式
Zustand/Redux全域狀態管理
NextAuth.js登入驗證(支援 Google, GitHub 等)
PrismaORM 操作 MySQL/PostgreSQL
React Query前端資料抓取與快取
shadcn/ui組件庫(Tailwind 美型元件)

Vue Router 入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

Vue Router 是 Vue.js 官方提供的前端路由解決方案,專為構建單頁應用(SPA, Single Page Application)而設計。它讓我們可以根據網址變化動態切換畫面而不重新載入頁面,是開發 Vue 應用不可或缺的工具之一。

本文將介紹 Vue Router 的基本概念、安裝方式、核心語法,並透過簡單實作幫助我們快速入門。


一、什麼是前端路由

在傳統網頁架構中,網址的改變會導致瀏覽器重新向伺服器請求一個新的 HTML 頁面。但在 SPA 中,整個網站的內容是透過 JavaScript 管理畫面切換,網址改變時並不會重新載入整個頁面,而是由「前端路由器」來處理畫面更新。

Vue Router 就是 Vue 的前端路由器。


二、安裝 Vue Router

在 Vue 3 專案中,可以透過以下指令安裝 Vue Router:

npm install vue-router@4

安裝完成後,在 src/router/index.js(或 router.ts)中建立路由設定。


三、基本使用範例

1. 建立路由元件

建立幾個簡單的元件:

// src/views/Home.vue
<template>
<h1>首頁</h1>
</template>

// src/views/About.vue
<template>
<h1>關於我們</h1>
</template>

2. 設定路由

// src/router/index.js
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
import About from "../views/About.vue";

const routes = [
{ path: "/", component: Home },
{ path: "/about", component: About },
];

const router = createRouter({
history: createWebHistory(),
routes,
});

export default router;

3. 在 main.js 掛載路由

// src/main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";

createApp(App).use(router).mount("#app");

4. 使用 <router-view> 顯示頁面

<!-- src/App.vue -->
<template>
<div>
<h1>我的網站</h1>
<nav>
<router-link to="/">首頁</router-link>
|
<router-link to="/about">關於</router-link>
</nav>
<router-view></router-view>
</div>
</template>

<router-link> 是 Vue Router 提供的元件,用來建立不重新載入的內部連結。<router-view> 則是畫面會顯示對應元件的插槽。


四、動態路由參數

可使用 :id 的方式定義動態參數:

// router/index.js
import User from "../views/User.vue";

const routes = [{ path: "/user/:id", component: User }];

在元件中取得參數:

<!-- views/User.vue -->
<template>
<div>使用者 ID:{{ $route.params.id }}</div>
</template>

五、巢狀路由

當需要在某個頁面內部再切換子頁面時,可使用巢狀路由。

// router/index.js
import Dashboard from "../views/Dashboard.vue";
import Profile from "../views/Profile.vue";
import Settings from "../views/Settings.vue";

const routes = [
{
path: "/dashboard",
component: Dashboard,
children: [
{ path: "profile", component: Profile },
{ path: "settings", component: Settings },
],
},
];

Dashboard.vue 中放置 <router-view> 來呈現子路由內容:

<template>
<div>
<h2>儀表板</h2>
<router-link to="/dashboard/profile">個人資料</router-link>
<router-link to="/dashboard/settings">設定</router-link>
<router-view></router-view>
</div>
</template>

六、導覽守衛(Navigation Guard)

我們可以用來保護某些頁面,例如使用者未登入不得進入:

// router/index.js
const router = createRouter({...})

router.beforeEach((to, from, next) => {
const isLoggedIn = false // 假設未登入
if (to.path === '/dashboard' && !isLoggedIn) {
next('/') // 導回首頁
} else {
next()
}
})

七、路由模式(Hash vs History)

Vue Router 支援兩種模式:

  1. Hash 模式(預設): 網址含 #,如 /#/about,適用於靜態伺服器
  2. History 模式(推薦): 網址乾淨,需伺服器配合設定

選擇 History 模式時,需設定:

createRouter({
history: createWebHistory(),
routes,
});

若使用 Vite / Vue CLI,也需額外設定伺服器的 404 fallback。


八、程式化導航

我們也可以在程式中使用 $router 導覽:

this.$router.push("/about");

或者使用命名路由與參數:

this.$router.push({ name: "user", params: { id: 123 } });

九、總結

概念說明
<router-view>顯示當前路由對應的元件
<router-link>建立內部連結
$route取得當前路由資訊(如參數)
$router控制路由導航的方法
動態路由使用 :id 定義參數
巢狀路由路由中可包含子路由
導覽守衛控制進入頁面的權限
模式hashhistory 模式

結語

Vue Router 是構建 Vue SPA 必備的工具之一,掌握它能幫助我們設計更有結構、可導航的前端應用。在進階應用中,Vue Router 還支援命名路由、懶加載、滾動行為、過渡動畫等功能。

如果我們有興趣了解 Vue Router 與後端整合、登入驗證、或 Nuxt.js 中的路由自動生成,歡迎再提出更進一步的需求。

Element Plus 介紹入門教學筆記 | 學習筆記

· 閱讀時間約 6 分鐘
kdchang

前言

在現代前端開發中,UI 框架對於提升開發效率、提升用戶體驗具有至關重要的作用。Element Plus 是一個基於 Vue 3 的開源 UI 框架,它提供了豐富的組件,並且與 Vue 3 的 Composition API 完美兼容,能幫助開發者快速構建高質量的前端應用。

在筆記中,我們將使用 Vue 3 的 setup() 語法來講解如何在專案中使用 Element Plus,並展示如何搭配 Vue 3 的新特性來實現更加清晰和模組化的開發模式。

重點摘要

  • Element Plus:基於 Vue 3 的 UI 組件庫,提供了豐富的 UI 元件,支持 Composition API 和 TypeScript。
  • Vue 3 Setup:使用 Vue 3 的 Composition API 和 setup() 來構建組件邏輯,並將 UI 與邏輯分開。
  • 組件設置:展示如何在 setup() 中引入並使用 Element Plus 組件。
  • 全域配置:設置 Element Plus 的全域屬性,例如默認主題或組件大小。
  • 實際範例:通過簡單的範例,演示如何在 Vue 3 中使用 Element Plus 進行開發。

安裝與設置

安裝 Element Plus

在 Vue 3 專案中使用 Element Plus,首先需要安裝它。假設你的專案已經配置了 Vue 3,則可以通過以下命令安裝 Element Plus。

npm install element-plus --save

或者使用 yarn

yarn add element-plus

設置 Vue 3 與 Element Plus

在 Vue 3 的專案中,通過 setup() 語法來設置 Element Plus,首先需要在 main.jsmain.ts 中引入 Element Plus 並配置樣式:

import { createApp } from 'vue';
import App from './App.vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';

const app = createApp(App);
app.use(ElementPlus);
app.mount('#app');

這樣,Element Plus 就成功集成到你的 Vue 3 專案中,接下來可以開始使用其組件。

使用 Vue 3 Setup 語法進行開發

按鈕 (Button) 組件

在 Vue 3 中,setup() 是一個組件的初始化函數,所有的組件邏輯應該放在這個函數中。以下是如何在 setup() 中使用 Element Plus 的按鈕組件。

<template>
<el-button type="primary">主要按鈕</el-button>
<el-button type="success">成功按鈕</el-button>
<el-button type="danger">危險按鈕</el-button>
</template>

<script setup>
import { ElButton } from 'element-plus';
</script>

在此範例中,使用 import 引入 ElButton 組件,並在模板中直接使用。這樣,我們就能夠根據需要渲染出不同樣式的按鈕。Element Plus 的組件會自動按照設置渲染出對應的 UI。

對話框 (Dialog) 組件

Element Plus 提供了功能強大的對話框組件,能夠輕鬆實現彈窗效果。下面是如何在 setup() 中實現一個顯示對話框的功能:

<template>
<el-button @click="openDialog">顯示對話框</el-button>
<el-dialog :visible.sync="dialogVisible" title="對話框標題">
<p>這是一個對話框示例。</p>
</el-dialog>
</template>

<script setup>
import { ref } from 'vue';
import { ElButton, ElDialog } from 'element-plus';

const dialogVisible = ref(false);

const openDialog = () => {
dialogVisible.value = true;
};
</script>

在這個範例中,使用 ref() 創建了 dialogVisible 這個響應式變數,並且通過按鈕的點擊事件來控制對話框的顯示和隱藏。sync 修飾符用來將對話框的顯示狀態與 dialogVisible 變數同步。

表單 (Form) 組件

Element Plus 的表單組件支持高效的表單驗證,以下範例展示如何在 setup() 中使用表單組件進行資料提交。

<template>
<el-form :model="form" ref="formRef" label-width="120px">
<el-form-item
label="名稱"
prop="name"
:rules="[{ required: true, message: '請輸入名稱', trigger: 'blur' }]"
>
<el-input v-model="form.name" placeholder="請輸入名稱"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">提交</el-button>
</el-form-item>
</el-form>
</template>

<script setup>
import { ref } from 'vue';
import { ElButton, ElForm, ElFormItem, ElInput } from 'element-plus';

const form = ref({
name: '',
});

const submitForm = () => {
// 表單提交處理
console.log(form.value.name);
};
</script>

在這個範例中,使用 ref() 創建表單的資料模型,並且為名稱欄位設置了必填的驗證規則。當點擊提交按鈕時,將會輸出表單中的名稱欄位值。

表格 (Table) 組件

Element Plus 的表格組件支持展示大量數據,並且提供排序、過濾等功能。以下是如何使用 setup() 語法來渲染表格:

<template>
<el-table :data="tableData" style="width: 100%">
<el-table-column label="姓名" prop="name"></el-table-column>
<el-table-column label="年齡" prop="age"></el-table-column>
<el-table-column label="地址" prop="address"></el-table-column>
</el-table>
</template>

<script setup>
import { ref } from 'vue';
import { ElTable, ElTableColumn } from 'element-plus';

const tableData = ref([
{ name: '王小明', age: 25, address: '台北市' },
{ name: '李大華', age: 30, address: '高雄市' },
{ name: '張三', age: 28, address: '台中市' },
]);
</script>

這個範例展示了如何使用 ref() 創建表格的數據源,並使用 ElTableElTableColumn 來渲染表格。

全域配置

Element Plus 提供了全域配置的能力,允許你在項目中設置統一的組件配置。例如,設置所有按鈕的默認大小或主題色彩等。

import { createApp } from 'vue';
import App from './App.vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';

const app = createApp(App);

app.use(ElementPlus, {
size: 'small', // 設定所有組件的大小為 small
zIndex: 3000, // 設定 z-index
});

app.mount('#app');

這樣設置之後,你的所有組件將會默認使用小號尺寸,並且對話框等組件的 z-index 也會被統一設置。

總結

Element Plus 是一個強大且易於使用的 UI 框架,與 Vue 3 的 Composition API 完美集成。通過 setup() 語法,開發者能夠更加簡潔且模組化地編寫組件邏輯,從而提升開發效率。無論是常見的按鈕、對話框,還是複雜的表格、表單,Element Plus 都能提供強大的支持。希望本文能幫助你快速上手 Element Plus,並利用它來構建高效、現代化的前端應用。

Vuex 入門教學筆記:集中式狀態管理實作入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在 Vue.js 開發中,當應用程式變得複雜,元件之間需要共享的狀態越來越多時,僅靠 props 與 events 傳遞資料會變得混亂與難以維護。這時,我們就需要一個集中式的狀態管理方案,而 Vuex 正是官方為 Vue 提供的解決方案。

Vuex 是一個專為 Vue 應用開發的狀態管理模式。它將應用中所有的狀態集中管理,並以可預測的方式更新,便於追蹤與維護。


一、Vuex 是什麼?

Vuex 基於 Flux 架構 設計,核心概念如下:

  • State:集中管理的資料來源(全域狀態)
  • Getter:從 state 派生出來的資料(類似 computed)
  • Mutation:唯一可以同步改變 state 的方法
  • Action:處理非同步操作並提交 mutation
  • Module:將 store 拆分為模組化結構

二、安裝與設定 Vuex

以 Vue 3 專案為例,先安裝 Vuex:

npm install vuex@4

建立 store

// src/store/index.js
import { createStore } from "vuex";

const store = createStore({
state() {
return {
count: 0,
};
},
getters: {
doubleCount(state) {
return state.count * 2;
},
},
mutations: {
increment(state) {
state.count++;
},
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit("increment");
}, 1000);
},
},
});

export default store;

三、在 Vue 中註冊 Vuex

// main.js
import { createApp } from "vue";
import App from "./App.vue";
import store from "./store";

createApp(App).use(store).mount("#app");

四、在元件中使用 Vuex

1. 讀取 State 和 Getter

<template>
<div>
<p>Count:{{ count }}</p>
<p>Double:{{ doubleCount }}</p>
</div>
</template>

<script>
import { computed } from "vue";
import { useStore } from "vuex";

export default {
setup() {
const store = useStore();
const count = computed(() => store.state.count);
const doubleCount = computed(() => store.getters.doubleCount);
return { count, doubleCount };
},
};
</script>

2. 呼叫 Mutation 和 Action

<template>
<button @click="increment">+1</button>
<button @click="incrementAsync">+1(非同步)</button>
</template>

<script>
import { useStore } from "vuex";

export default {
setup() {
const store = useStore();
const increment = () => store.commit("increment");
const incrementAsync = () => store.dispatch("incrementAsync");
return { increment, incrementAsync };
},
};
</script>

五、模組化 Vuex Store

當我們的應用變大,state 增加時,可將 store 拆分成多個模組。

// src/store/modules/counter.js
export default {
namespaced: true,
state: () => ({
count: 0,
}),
mutations: {
increment(state) {
state.count++;
},
},
};
// src/store/index.js
import { createStore } from "vuex";
import counter from "./modules/counter";

export default createStore({
modules: {
counter,
},
});

在元件中使用時要記得模組的命名空間:

store.commit("counter/increment");

六、Vuex 與 Composition API 的搭配

Vuex 4 支援 Vue 3 的 Composition API,我們可以透過 useStore() 搭配 computed() 來存取或操作資料。這樣的使用方式更模組化,也能更輕鬆撰寫邏輯可重用的自定義 hooks。

// composables/useCounter.js
import { computed } from "vue";
import { useStore } from "vuex";

export default function useCounter() {
const store = useStore();
const count = computed(() => store.state.counter.count);
const increment = () => store.commit("counter/increment");
return { count, increment };
}

七、Vuex 與非同步操作的實務應用

Vuex 的 Action 適合處理 API 呼叫,例如取得後端資料:

// store/modules/todos.js
export default {
namespaced: true,
state: () => ({
list: [],
loading: false,
}),
mutations: {
setLoading(state, flag) {
state.loading = flag;
},
setTodos(state, todos) {
state.list = todos;
},
},
actions: {
async fetchTodos({ commit }) {
commit("setLoading", true);
const res = await fetch("https://jsonplaceholder.typicode.com/todos");
const data = await res.json();
commit("setTodos", data.slice(0, 5));
commit("setLoading", false);
},
},
};

八、Vuex 的限制與未來

Vuex 提供完整的狀態追蹤與結構化設計,適合大型應用。不過,它的學習曲線略高,對於小型專案可能顯得冗長。Vue 團隊也在 Vue 3 推出後推薦使用 Pinia 作為新的官方狀態管理方案,擁有更輕量的語法與更佳的 TypeScript 支援。

但 Vuex 在大型專案、多人協作、需要嚴格管理資料流程的場景下仍然非常實用與穩定。


九、總結與建議

功能說明
state全域共享狀態資料
getters從 state 衍生計算的資料
mutations同步更新狀態的唯一方法
actions處理非同步並提交 mutation
modules將 store 拆分管理
Composition API搭配 useStore() 模組化使用

建議我們在小型應用中可以先用 propsemit 傳遞資料,等到資料流變複雜或頁面之間需頻繁共享狀態時,再引入 Vuex 管理。當然,若我們正開發大型後台系統、電子商務網站,Vuex 的集中式結構能大大提升可維護性與擴展性。

如欲進一步探索,建議查看 Vuex 官方文件、或試著實作一個待辦清單管理應用,實踐 Vuex 中的完整生命週期與流程。

Webpack 入門教學筆記:現代前端建構工具的基礎與實戰 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在前端專案日益龐大與模組化的今天,建構工具(build tools)扮演了極其關鍵的角色。而 Webpack 作為目前最主流的模組打包器(module bundler),廣泛應用於各類前端應用與框架中(如 React、Vue 等)。本篇筆記將介紹 Webpack 的基本概念、核心組件與實際範例,協助你快速理解與實作。


一、什麼是 Webpack?

Webpack 是一個靜態模組打包器,它會從你的應用程式進入點(entry point)開始,分析相依的模組(JavaScript、CSS、圖片、JSON 等),然後打包成一或多個 bundle,供瀏覽器載入使用。

主要特性包含:

  • 支援模組系統(如 CommonJS、ESM)
  • 可搭配各種 Loader 處理不同類型資源
  • 使用 Plugin 擴充打包功能
  • 開發與生產模式可分離配置
  • 支援 Hot Module Replacement(HMR)與 Dev Server

二、基本安裝與專案初始化

建立一個新的專案資料夾:

mkdir my-webpack-app
cd my-webpack-app
npm init -y

安裝 Webpack 及其 CLI 工具:

npm install --save-dev webpack webpack-cli

建立基本的專案目錄結構:

my-webpack-app/
├── dist/
│ └── index.html
├── src/
│ └── index.js
├── package.json
└── webpack.config.js

三、撰寫基本範例

1. src/index.js

import "./style.css";

const element = document.createElement("h1");
element.textContent = "Hello Webpack!";
document.body.appendChild(element);

2. src/style.css

body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
text-align: center;
padding-top: 100px;
}

3. dist/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My Webpack App</title>
</head>
<body>
<script src="main.js"></script>
</body>
</html>

四、Webpack 基礎設定檔

建立 webpack.config.js

const path = require("path");

module.exports = {
entry: "./src/index.js", // 入口檔案
output: {
filename: "main.js", // 輸出檔案名稱
path: path.resolve(__dirname, "dist"), // 輸出目錄
clean: true, // 每次打包清空 dist
},
module: {
rules: [
{
test: /\.css$/i, // 處理 CSS 檔案
use: ["style-loader", "css-loader"],
},
],
},
mode: "development", // 模式(也可設為 'production')
};

安裝所需的 CSS loader:

npm install --save-dev style-loader css-loader

五、打包與啟動

執行打包指令:

npx webpack

執行後會自動將所有相依檔案打包為 dist/main.js,可以直接打開 dist/index.html 查看效果。


六、啟用 Webpack Dev Server(開發伺服器)

為了開發方便,我們可以啟用內建的開發伺服器,支援 HMR(Hot Module Replacement)功能。

安裝 dev server:

npm install --save-dev webpack-dev-server

webpack.config.js 增加設定:

devServer: {
static: './dist',
port: 3000,
open: true, // 自動開啟瀏覽器
hot: true, // 啟用 HMR
}

更新 package.json 的 scripts:

"scripts": {
"build": "webpack",
"start": "webpack serve"
}

執行:

npm run start

開發伺服器啟動後會自動開啟 localhost:3000,修改檔案後可即時預覽變更。


七、區分開發與生產模式

在實際開發中,我們會針對開發與正式環境建立不同的設定檔,使用 webpack-merge 套件來合併共用設定:

npm install --save-dev webpack-merge

建立以下三個檔案:

webpack.common.js
webpack.dev.js
webpack.prod.js

webpack.common.js 放共用設定,其他兩個則各自設定環境特有的項目,例如:

  • webpack.dev.js: 開啟 Source Map、HMR
  • webpack.prod.js: 最佳化壓縮、移除 console.log 等

八、擴充功能:使用 Babel 處理 ES6+

Webpack 本身不會轉譯 JavaScript,需要搭配 Babel:

npm install --save-dev babel-loader @babel/core @babel/preset-env

新增 .babelrc

{
"presets": ["@babel/preset-env"]
}

webpack.config.js 加入規則:

{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}

這樣就能讓 Webpack 在打包過程中使用 Babel 將 ES6+ 語法轉譯為相容的版本。


九、使用 Plugin 擴充功能:HtmlWebpackPlugin

HtmlWebpackPlugin 可自動產生並插入 script 標籤:

npm install --save-dev html-webpack-plugin

修改設定:

const HtmlWebpackPlugin = require("html-webpack-plugin");

plugins: [
new HtmlWebpackPlugin({
template: "./dist/index.html",
inject: "body",
}),
];

不再需要手動在 index.html 中引入 main.js,Webpack 會自動插入對應 bundle。


十、結語與建議

Webpack 雖然設定上比 Vite 複雜,但擁有極高的自訂彈性與完整生態系,是大型專案不可或缺的建構工具。透過 Loader 處理不同格式的資源、Plugin 擴充功能,再加上分環境設定與開發伺服器支援,Webpack 能有效協助你管理現代前端應用的整個建構流程。

適合情境包括:

  • React、Vue 中大型應用
  • 多入口或模組系統複雜的專案
  • 需要高度自訂打包流程的企業內部系統

雖然 Vite、Parcel 等工具正迅速崛起,但 Webpack 仍是學習前端建構工具不可忽略的重要基礎。

Cookie 入門教學筆記 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

Cookie 是一種儲存在使用者瀏覽器上的小型文字檔案,用於保存使用者的狀態或資訊。常見用途包含:

紀錄登入狀態:讓使用者在網站上保持登入,不需每次重新輸入帳號。 使用者偏好設定:保存使用者選擇的語言、主題等個人化設定。 追蹤使用者行為:例如分析網站流量、廣告投放追蹤等。

重點摘要

Cookie 依據不同的分類方式,可以分為以下幾種常見種類:

一、依用途分類:

  1. 功能性 Cookie(Functional Cookies)

    • 主要用於提升網站使用體驗,例如記住使用者的登入狀態、語言設定、購物車內容等。
  2. 必要性 Cookie(Strictly Necessary Cookies)

    • 維持網站基本運作所需,例如登入認證、網頁導航等,通常無法被關閉。
  3. 分析型 Cookie(Analytical/Performance Cookies)

    • 用於收集網站流量數據,分析使用者行為,幫助網站優化,例如 Google Analytics。
  4. 廣告追蹤 Cookie(Advertising/Targeting Cookies)

    • 用於追蹤使用者瀏覽行為,以提供個人化廣告或推薦內容。

二、依存放時間分類:

  1. 暫時性 Cookie(Session Cookies)

    • 只在使用者開啟網頁期間有效關閉瀏覽器後即刪除
  2. 永久性 Cookie(Persistent Cookies)

    • 設定到期日期,存放於使用者裝置上,保存時間較長,即使關閉瀏覽器也不會刪除,直到設定的到期日或手動刪除

三、依來源分類:

  1. 第一方 Cookie(First-party Cookies)

    • 使用者瀏覽的網站所設定,通常用於記錄該網站上的互動紀錄
  2. 第三方 Cookie(Third-party Cookies)

    • 由非該網站的第三方(如廣告商)設定,用於跨網站追蹤使用者行為,以推送廣告等。

四、特殊類型:

  1. 安全性 Cookie(Secure Cookies)

    • 只能透過 HTTPS 傳輸,避免被攔截,主要保障敏感資料安全。
  2. HttpOnly Cookie

    • 僅限伺服器端存取,JavaScript 無法讀取,用於防範 XSS 攻擊。
  3. SameSite Cookie

    • 限制跨站請求攜帶 Cookie,減少 CSRF 攻擊風險,值可設為:
    • Strict:禁止跨站請求攜帶 Cookie。
    • Lax:部分允許,如從第三方網站點擊連結進來時可帶 Cookie。
    • None:允許跨站攜帶,但須配合 Secure。

總結

這些分類會依需求搭配使用,例如一個「必要性第一方暫時性 Cookie」可能用於維護登入和操作狀態;一個「第三方廣告追蹤永久性 Cookie」則可能用於跨網站顯示個人化廣告

Alpine.js 入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

1. 什麼是 Alpine.js?

Alpine.js 是一個輕量級的 JavaScript 框架,專為增強 HTML 標記而設計。它的語法靈感來自 Vue.js,但更加簡潔,適用於需要簡單互動的網頁。

它的主要特點包括:

  • 使用 HTML 屬性直接定義行為
  • 不需要額外的構建工具
  • 易於學習和使用
  • 與其他框架(如 Vue、React)兼容

2. 安裝與引入

使用 Alpine.js 最簡單的方法是透過 CDN 引入。

<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alpine.js 入門</title>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>
<h1>Alpine.js 教學</h1>
</body>
</html>

3. 基本語法與應用

3.1 x-data

x-data 屬性用於定義 Alpine.js 的組件狀態。

<div x-data="{ message: 'Hello, Alpine!' }">
<p x-text="message"></p>
</div>

這段程式碼會顯示 Hello, Alpine!,並且 x-text 會自動更新內容。

3.2 x-bind

x-bind 允許綁定 HTML 屬性。

<div x-data="{ color: 'red' }">
<p x-bind:style="'color: ' + color">這是一段紅色文字</p>
</div>

3.3 x-on

x-on 用於事件監聽,例如點擊事件。

<div x-data="{ count: 0 }">
<button x-on:click="count++">增加</button>
<p>計數:<span x-text="count"></span></p>
</div>

3.4 x-model

x-model 允許雙向綁定表單元素。

<div x-data="{ name: '' }">
<input type="text" x-model="name" placeholder="輸入你的名字">
<p>你好,<span x-text="name"></span></p>
</div>

3.5 x-show

x-show 控制元素顯示或隱藏。

<div x-data="{ isVisible: true }">
<button x-on:click="isVisible = !isVisible">切換顯示</button>
<p x-show="isVisible">這段文字可以顯示或隱藏</p>
</div>

3.6 x-if

x-if 會動態新增或移除元素(比 x-show 更影響 DOM)。

<div x-data="{ isVisible: true }">
<button x-on:click="isVisible = !isVisible">切換</button>
<template x-if="isVisible">
<p>這是一段可動態新增或刪除的文字</p>
</template>
</div>

3.7 x-for

x-for 用於迭代陣列。

<div x-data="{ items: ['蘋果', '香蕉', '橘子'] }">
<ul>
<template x-for="item in items" :key="item">
<li x-text="item"></li>
</template>
</ul>
</div>

3.8 計時器與非同步操作

Alpine.js 支援 setTimeoutfetch 等 JavaScript 方法。

<div x-data="{
message: '載入中...',
async fetchData() {
let response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
let data = await response.json();
this.message = data.title;
}
}" x-init="fetchData">
<p x-text="message"></p>
</div>

4. Alpine.js 與 Tailwind CSS

Alpine.js 常與 Tailwind CSS 搭配使用,打造簡潔的 UI。

<div x-data="{ open: false }" class="p-5">
<button x-on:click="open = !open" class="bg-blue-500 text-white px-4 py-2 rounded">
切換選單
</button>
<ul x-show="open" class="mt-2 border p-2">
<li>選單 1</li>
<li>選單 2</li>
<li>選單 3</li>
</ul>
</div>

5. Alpine.js 進階應用

5.1 Alpine.store

Alpine.store 可用於全域狀態管理。

<script>
document.addEventListener('alpine:init', () => {
Alpine.store('app', { count: 0 });
});
</script>

<div x-data>
<button x-on:click="$store.app.count++">增加</button>
<p>計數:<span x-text="$store.app.count"></span></p>
</div>

5.2 Alpine.plugin

Alpine.js 提供外掛支援,例如 persist(本地儲存)。

<script src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js"></script>
<script>
document.addEventListener('alpine:init', () => {
Alpine.plugin(Alpine.persist);
});
</script>

<div x-data="{ count: $persist(0) }">
<button x-on:click="count++">增加</button>
<p>計數:<span x-text="count"></span></p>
</div>

6. 總結

Alpine.js 是一個靈活且輕量的框架,適合用於簡單互動需求,如表單驗證、選單切換、即時更新內容等。它不需要複雜的配置,能夠快速增強靜態 HTML 頁面。

如果你的專案需要更強大的功能,可以考慮與 Vue.js 或 React 搭配,或在更大規模的應用中使用其他框架。