跳至主要内容

17 篇文章 含有標籤「vue.js」

檢視所有標籤

Vue ref 與 reactive 入門教學筆記 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

前言

在 Vue 3 中,refreactive 都是用來創建響應式資料的工具,但它們適用的情境略有不同。以下是清楚的比較與實際使用範例,幫助你理解什麼時候該用哪一個。


ref 使用情境

適用於:

  • 原始資料型別:如 NumberStringBooleanDate 等。
  • 你只需要追蹤單一值。
  • 當你需要某個變數傳遞到 <template> 或函式中。

語法:

import { ref } from 'vue'

const count = ref(0)
count.value++ // 需要用 .value 訪問或修改

範例:

<script setup>
import { ref } from 'vue'

const message = ref('Hello')
const count = ref(0)

const updateMessage = () => {
message.value = 'Hi Vue 3'
}
</script>

reactive 使用情境

適用於:

  • 物件或陣列:需要操作多個屬性的資料結構。
  • 多層巢狀資料或你希望所有屬性都具有響應性。
  • 表單資料、設定物件等複雜狀態管理。

語法:

import { reactive } from 'vue'

const user = reactive({
name: 'Daniel',
age: 30
})

user.age++ // 直接修改即可,無需 .value

範例:

<script setup>
import { reactive } from 'vue'

const form = reactive({
username: '',
email: '',
isSubscribed: false
})

const submitForm = () => {
console.log(form.username, form.email, form.isSubscribed)
}
</script>

⚠️ ref 包物件 vs reactive

雖然你也可以這樣用:

const user = ref({ name: 'Daniel', age: 30 })

但要存取時就需要:

user.value.name = 'John'

reactive 則可以直接操作:

user.name = 'John'

所以若你有物件資料,通常選擇 reactive 更直覺。


建議使用方式整理:

類型建議使用方式
單一變數(數字、字串)ref
多欄位表單資料reactive
陣列、物件、巢狀結構reactive
要被 watchcomputed 的原始值ref

Vue 正確新增或修改物件屬性入門教學筆記 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

前言

在 Vue 2(Options API)中,this.$set 是用來在響應式系統中正確新增或修改物件屬性的方法。這對於動態新增屬性或修改陣列的指定索引值特別有用。


語法

this.$set(target, propertyName/index, value)
  • target:要修改的對象或陣列
  • propertyName(物件)或 index(陣列)
  • value:要設的值

為什麼需要 this.$set

Vue 2 的 reactivity(響應式系統)使用 Object.defineProperty,它無法偵測到新加的屬性或是直接對陣列用索引來賦值。

例如,下面的程式碼是不會觸發畫面更新的:

this.someObj.newKey = 'value'      // 不會觸發更新
this.someArray[1] = 'changed' // 不會觸發更新

必須改成:

this.$set(this.someObj, 'newKey', 'value')      // 會觸發更新
this.$set(this.someArray, 1, 'changed') // 會觸發更新

範例一:對物件新增屬性

data() {
return {
user: {
name: '小明'
}
}
},
mounted() {
this.$set(this.user, 'age', 30);
}

這樣才能讓 user.age 成為響應式屬性,更新時畫面才會重新渲染。


範例二:修改陣列中的值

data() {
return {
items: ['a', 'b', 'c']
}
},
methods: {
updateItem() {
this.$set(this.items, 1, 'changed');
}
}

Vue 3 呢?

在 Vue 3 裡,因為 reactivity 系統改用 Proxy,所以可以直接新增或修改屬性,不需要 this.$set 了:

this.someObj.newKey = 'value'   // Vue 3 沒問題
this.someArray[1] = 'changed' // Vue 3 沒問題

如果你正在寫 Vue 2 的 Tic Tac Toe 遊戲,當你要動態更新棋盤的某個格子時,這樣做就是對的:

this.$set(this.board[row], col, 'X');

這樣才能確保畫面能即時更新。

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

· 閱讀時間約 5 分鐘
kdchang

Nuxt.js 是一個基於 Vue.js 的漸進式框架,專為構建伺服器端渲染(SSR)和靜態站點生成(SSG)應用程式而設計。它提供開發者一個強大的開發體驗,並簡化 Vue.js 應用的架構與設定,適合 SEO 優化、效能最佳化以及提升開發效率。

Nuxt.js 的核心特性包括:

  1. 伺服器端渲染(SSR):增強 SEO 並提升初始載入速度。
  2. 靜態站點生成(SSG):透過預先生成 HTML 提供更快的載入時間。
  3. 自動路由:基於 pages 目錄的檔案自動建立對應的路由,無需額外配置 Vue Router。
  4. 模組系統:支援大量 Nuxt 模組(如 TailwindCSS、PWA、Auth 等)來快速擴展功能。
  5. 組態簡單:預設優化 Vue.js 應用的結構與設定,減少繁瑣的配置工作。
  6. 組件自動載入:Nuxt 可自動載入 components 目錄內的 Vue 組件,減少 import 的需求。

二、安裝與初始化 Nuxt.js

1. 使用 Nuxt CLI 安裝(推薦方式)

Nuxt 提供官方 CLI 工具 nuxi 來建立新專案。

npx nuxi init my-nuxt-app
cd my-nuxt-app
npm install

上述指令會自動建立一個 my-nuxt-app 專案,並下載 Nuxt 相關相依套件。

2. 使用 create-nuxt-app 安裝(舊版方式)

如果要使用較舊的安裝方式,也可以透過 create-nuxt-app 指令來建立專案:

npx create-nuxt-app my-nuxt-app

此方法會提供互動式選單,讓開發者選擇 UI 框架(TailwindCSS、Bootstrap)、插件(Axios、PWA)以及 Nuxt 模式(SSR 或 SSG)。

3. 啟動開發伺服器

安裝完成後,可以執行以下指令來啟動開發環境:

npm run dev

預設會啟動本機伺服器 http://localhost:3000,可在瀏覽器中打開檢視。


三、專案結構

Nuxt.js 採用約定式(Convention over Configuration)架構,專案目錄結構如下:

my-nuxt-app/
│── assets/ # 未編譯的靜態資源,如 CSS、圖片
│── components/ # Vue 組件(自動載入)
│── layouts/ # 頁面佈局
│── pages/ # 自動建立的路由頁面
│── plugins/ # Nuxt 插件,如 Vue 插件或第三方庫
│── public/ # 靜態資源,可直接透過 URL 存取
│── server/ # 伺服器端 API(Nuxt 3)
│── store/ # Vuex 狀態管理(Nuxt 2,Nuxt 3 改用 `pinia`)
│── nuxt.config.ts # Nuxt 設定檔
│── package.json # npm 套件設定

四、路由與頁面

1. 自動建立路由

Nuxt.js 會根據 pages/ 目錄內的 Vue 檔案自動產生對應的路由。例如,在 pages/index.vue 建立首頁:

<template>
<div>
<h1>歡迎來到 Nuxt.js</h1>
</div>
</template>

若在 pages/about.vue 建立新的 Vue 檔案,則 http://localhost:3000/about 會自動對應到該頁面。

2. 動態路由

可以使用 _ 命名的方式建立動態路由。例如,在 pages/blog/_id.vue

<template>
<div>
<h1>文章 ID: {{ route.params.id }}</h1>
</div>
</template>

<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
</script>

訪問 http://localhost:3000/blog/123,頁面將顯示 文章 ID: 123


五、Nuxt 組件與佈局

1. 自動載入組件

components/ 內的 Vue 檔案會自動載入,例如建立 components/Navbar.vue

<template>
<nav class="bg-blue-500 p-4 text-white">
<h1>網站導覽列</h1>
</nav>
</template>

然後在 pages/index.vue 內直接使用 <Navbar />,無需 import

<template>
<div>
<Navbar />
<h1>首頁內容</h1>
</div>
</template>

2. 佈局(Layouts)

佈局是共享的頁面結構,可在 layouts/default.vue 內定義:

<template>
<div>
<Navbar />
<slot />
</div>
</template>

所有 pages/ 內的頁面會自動套用 default.vue 佈局。


六、Nuxt 伺服器端 API(Nuxt 3)

Nuxt 3 內建簡單的 API 伺服器,可在 server/api/hello.ts 新增 API:

export default defineEventHandler((event) => {
return { message: "Hello from Nuxt API" };
});

這樣就可以透過 http://localhost:3000/api/hello 訪問該 API。


七、Nuxt 資料獲取

1. useFetch() 獲取 API 資料

Nuxt 3 提供 useFetch 來處理 API 讀取,例如:

<template>
<div>
<h1>{{ data.message }}</h1>
</div>
</template>

<script setup>
const { data } = useFetch('/api/hello');
</script>

這會自動調用 server/api/hello.ts 並顯示回應內容。


八、部署 Nuxt 應用

1. 生成靜態站點

若要將 Nuxt 部署為靜態網站,可執行:

npm run build
npm run generate

這會在 dist/ 目錄內產生靜態 HTML 檔案,可直接部署到 Netlify 或 Vercel。

2. 部署至 Vercel

使用 Vercel CLI 部署:

npm install -g vercel
vercel

即可快速部署 Nuxt 應用。


九、結語

Nuxt.js 提供強大的功能來簡化 Vue.js 開發,透過自動路由、組件自動載入、伺服器 API 以及資料獲取等功能,大幅提升開發效率。對於需要 SEO 優化或靜態站點的專案而言,Nuxt 是一個非常適合的選擇。

Vue.js 3 官方入門語法教學筆記 [13] - Slots 插槽 | 學習筆記

· 閱讀時間約 1 分鐘
kdchang

除了透過 Props 傳遞資料外,父元件還可以透過插槽將模板片段傳遞給子元件:

<template>
<ChildComp>
這是一些插槽內容!
</ChildComp>
</template>

在子元件中,可以使用 <slot> 元素作為插槽來渲染父元件傳遞的內容:

<template>
<!-- 子元件的模板 -->
<slot />
</template>

插槽內的內容會被視為「預設內容」:當父元件沒有傳遞插槽內容時,會顯示這些預設內容:

<template>
<slot>預設內容</slot>
</template>

目前我們尚未向 <ChildComp> 傳遞任何插槽內容,因此你應該會看到預設內容。現在試著利用父元件的 msg 狀態,為子元件提供一些插槽內容吧!

<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const msg = ref('from parent')
</script>

<template>
<ChildComp>Message: {{ msg }}</ChildComp>
</template>
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const msg = ref('from parent')
</script>

<template>
<ChildComp>Message: {{ msg }}</ChildComp>
</template>

Vue.js 3 官方入門語法教學筆記 [12] - Emits 事件傳遞 | 學習筆記

· 閱讀時間約 1 分鐘
kdchang

除了接收 Props 之外,子元件也可以向父元件觸發事件:

<script setup>
// 宣告要觸發的事件
const emit = defineEmits(['response'])

// 觸發事件並傳遞參數
emit('response', 'hello from child')
</script>

emit() 的第一個參數是事件名稱,任何額外的參數都會傳遞給事件監聽器。

父元件可以使用 v-on 來監聽子元件觸發的事件——如下範例中,處理函式接收來自子元件 emit 的額外參數,並將其賦值給本地狀態:

<template>
<ChildComp @response="(msg) => childMsg = msg" />
</template>

現在我們在編輯器中試試看吧!

Vue.js 3 官方入門語法教學筆記 [11] - Props 屬性 | 學習筆記

· 閱讀時間約 1 分鐘
kdchang

子元件可以透過 Props 接收來自父元件的輸入。首先,子元件需要宣告它所接收的 Props:

<!-- ChildComp.vue -->
<script setup>
const props = defineProps({
msg: String
})
</script>

注意,defineProps() 是一個編譯時的巨集,不需要額外匯入。一旦宣告後,msg Prop 就可以在子元件的模板中使用,也可以透過 defineProps() 返回的物件在 JavaScript 中存取。

父元件可以像設定屬性一樣,將 Prop 傳遞給子元件。若要傳遞動態值,也可以使用 v-bind 語法:

<template>
<ChildComp :msg="greeting" />
</template>

我們現在在編輯器中試試看吧!

<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const greeting = ref('Hello from parent')
</script>

<template>
<ChildComp :msg="greeting" />
</template>
<script setup>
const props = defineProps({
msg: String
})
</script>

<template>
<h2>{{ msg || 'No props passed yet' }}</h2>
</template>

Vue.js 3 官方入門語法教學筆記 [10] - Components 元件 | 學習筆記

· 閱讀時間約 1 分鐘
kdchang

到目前為止,我們只使用了一個單一的元件。實際的 Vue 應用程式通常是由巢狀元件所組成的。

父元件可以在其模板中渲染另一個元件作為子元件。要使用子元件,我們需要先匯入它:

import ChildComp from './ChildComp.vue'

然後,我們可以在模板中使用該元件,如下所示:

<template>
<ChildComp />
</template>

我們現在試試看將匯入子元件並將其渲染到模板中。

<script setup>
import ChildComp from './ChildComp.vue'
</script>

<template>
<ChildComp />
</template>
<template>
<h2>A Child Component!</h2>
</template>

Vue.js 3 官方入門語法教學筆記 [9] - Watchers 觀察者 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

有時我們可能需要以反應性的方式執行「Side-effect 副作用」,例如,當一個數值改變時將其記錄到控制台。我們可以使用觀察者來實現這一點:

import { ref, watch } from 'vue'

const count = ref(0)

watch(count, (newCount) => {
// 是的,console.log() 是一種副作用
console.log(`新的計數是:${newCount}`)
})

watch() 可以直接監視一個 ref,每當 count 的值改變時,回調函數就會被觸發。watch() 也可以監視其他類型的數據來源——更多細節請參閱指南:觀察者(Watchers)。

比將訊息記錄到控制台更實用的例子,可能是當一個 ID 發生變化時,根據新 ID 獲取數據。我們的代碼目前是在元件掛載時,從一個模擬 API 獲取 todos 數據。此外,還有一個按鈕可以遞增應該被獲取的 todo ID。請嘗試實現一個觀察者,在按下按鈕時根據新 ID 獲取新的 todo 數據。

參考範例:

<script setup>
import { ref, watch } from 'vue'

const todoId = ref(1)
const todoData = ref(null)

async function fetchData() {
todoData.value = null
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
todoData.value = await res.json()
}

fetchData()

watch(todoId, fetchData)
</script>

<template>
<p>Todo id: {{ todoId }}</p>
<button @click="todoId++" :disabled="!todoData">Fetch next todo</button>
<p v-if="!todoData">Loading...</p>
<pre v-else>{{ todoData }}</pre>
</template>

Vue.js 3 官方入門語法教學筆記 [8] - Lifecycle and Template Refs 生命週期與模板引用 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

Lifecycle and Template Refs 生命週期與模板引用

到目前為止,Vue.js 透過響應式資料與聲明式渲染,幫助我們處理了所有的 DOM 更新。然而,無可避免地會有需要手動操作 DOM 的情況。

我們可以使用特殊的 ref 屬性來請求模板引用 (template ref),也就是模板中某個元素的引用:

範例

<template>
<p ref="pElementRef">hello</p>
</template>

定義引用

為了訪問這個引用,我們需要宣告一個名稱匹配的 ref

import { ref } from 'vue';

const pElementRef = ref(null);

請注意,這個 ref 在初始化時會是 null,因為當 <script setup> 被執行時,對應的 DOM 元素還不存在。模板引用只有在元件掛載 (mounted) 後才能被訪問。

在掛載後執行程式碼

我們可以使用 onMounted() 函式在掛載後執行程式碼:

import { onMounted } from 'vue';

onMounted(() => {
// 元件已掛載完成
});

生命週期鉤子

這稱為生命週期鉤子 (lifecycle hook),它允許我們在元件生命週期的特定時間點註冊回呼函式。其他的生命週期鉤子還包括 onUpdatedonUnmounted 等。更多細節請參考官方文件 生命週期圖示

試試看

現在,我們可以嘗試添加一個 onMounted 鉤子,透過 pElementRef.value 訪問 <p> 元素,並對其進行一些直接的 DOM 操作(例如更改 textContent)。

<script setup>
import { ref, onMounted } from 'vue'

const pElementRef = ref(null)

onMounted(() => {
pElementRef.value.textContent = 'mounted!'
})
</script>

<template>
<p ref="pElementRef">Hello</p>
</template>

Vue.js 3 官方入門語法教學筆記 [7] - Computed Property 計算屬性 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

讓我們在前一個待辦清單的範例基礎上繼續改進。這裡,我們已經為每個待辦項目新增了切換功能 (toggle),這是透過在每個待辦物件中新增一個 done 屬性,並使用 v-model 綁定到核取方塊來實現的:

<template>
<li v-for="todo in todos">
<input type="checkbox" v-model="todo.done">
...
</li>
</template>

接下來我們可以進一步改進功能,新增一個按鈕來隱藏已完成的待辦項目。我們已經有一個按鈕可以切換 hideCompleted 狀態。但要如何根據這個狀態來動態渲染不同的待辦清單項目呢?

這裡引入了 computed()。我們可以建立一個計算屬性,基於其他的響應式數據來源來計算它的 .value 值:

import { ref, computed } from 'vue';

const hideCompleted = ref(false);
const todos = ref([
/* ... */
]);

const filteredTodos = computed(() => {
// 根據 `todos.value` 和 `hideCompleted.value`
// 返回篩選後的待辦項目
});

我們將 v-for 的數據來源從原本的 todos 改為 filteredTodos

- <li v-for="todo in todos">
+ <li v-for="todo in filteredTodos">

計算屬性會自動追蹤其計算邏輯中使用的其他響應式數據作為依賴項目。它會快取計算結果,並在其依賴項目改變時自動更新。

現在,嘗試新增一個 filteredTodos 計算屬性,並實現其計算邏輯!如果實現正確,當隱藏已完成項目時,勾選一個待辦項目應會立即將其隱藏。

<script setup>
import { ref, computed } from 'vue'

let id = 0

const newTodo = ref('')
const hideCompleted = ref(false)
const todos = ref([
{ id: id++, text: 'Learn HTML', done: true },
{ id: id++, text: 'Learn JavaScript', done: true },
{ id: id++, text: 'Learn Vue', done: false }
])

const filteredTodos = computed(() => {
return hideCompleted.value
? todos.value.filter((t) => !t.done)
: todos.value
})

function addTodo() {
todos.value.push({ id: id++, text: newTodo.value, done: false })
newTodo.value = ''
}

function removeTodo(todo) {
todos.value = todos.value.filter((t) => t !== todo)
}
</script>

<template>
<form @submit.prevent="addTodo">
<input v-model="newTodo" required placeholder="new todo">
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in filteredTodos" :key="todo.id">
<input type="checkbox" v-model="todo.done">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
<button @click="hideCompleted = !hideCompleted">
{{ hideCompleted ? 'Show all' : 'Hide completed' }}
</button>
</template>

<style>
.done {
text-decoration: line-through;
}
</style>