[Medium] 📄 Vue Basic & API
1. Can you describe the core principles and advantages of the framework Vue?
請描述 Vue 框架的核心原理和優勢
核心原理
Vue 是一個漸進式的 JavaScript 框架,其核心原理包含以下幾個重要概念:
1. 虛擬 DOM(Virtual DOM)
使用虛擬 DOM 來提升效能。它只會更新有變化的 DOM 節點,而不是重新渲染整個 DOM Tree。透過 diff 演算法比較新舊虛擬 DOM 的差異,只針對差異部分進行實際 DOM 操作。
// 虛擬 DOM 概念示意
const vnode = {
tag: 'div',
props: { class: 'container' },
children: [
{ tag: 'h1', children: 'Hello' },
{ tag: 'p', children: 'World' },
],
};
2. 資料雙向綁定(Two-way Data Binding)
使用雙向資料綁定,當模型(Model)更改時,視圖(View)會自動更新,反之亦然。這讓開發者不需要手動操作 DOM,只需關注資料的變化。
<!-- Vue 3 推薦寫法:<script setup> -->
<template>
<input v-model="message" />
<p>{{ message }}</p>
</template>
<script setup>
import { ref } from 'vue';
const message = ref('Hello Vue');
</script>
Options API 寫法
<template>
<input v-model="message" />
<p>{{ message }}</p>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue',
};
},
};
</script>
3. 組件化(Component-based)
將整個應用切分成一個個組件,意味著重用性提升,這對維護開發會更為省工。每個組件都有自己的狀態、樣式和邏輯,可以獨立開發和測試。
<!-- Button.vue - Vue 3 <script setup> -->
<template>
<button @click="handleClick">
<slot></slot>
</button>
</template>
<script setup>
const emit = defineEmits(['click']);
const handleClick = () => {
emit('click');
};
</script>
4. 生命週期(Lifecycle Hooks)
有自己的生命週期,當資料發生變化時,會觸發相應的生命週期鉤子,這樣就可以在特定的生命週期中,做出相應的操作。
<!-- Vue 3 <script setup> 寫法 -->
<script setup>
import { onMounted, onUpdated, onUnmounted } from 'vue';
onMounted(() => {
// 組件掛載後執行
console.log('Component mounted!');
});
onUpdated(() => {
// 資料更新後執行
console.log('Component updated!');
});
onUnmounted(() => {
// 組件卸載後執行
console.log('Component unmounted!');
});
</script>
5. 指令系統(Directives)
提供了一些常用的指令,例如 v-if、v-for、v-bind、v-model 等,可以讓開發者更快速地開發。
<template>
<!-- 條件渲染 -->
<div v-if="isVisible">顯示內容</div>
<!-- 列表渲染 -->
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
<!-- 屬性綁定 -->
<img :src="imageUrl" :alt="imageAlt" />
<!-- 雙向綁定 -->
<input v-model="username" />
</template>
6. 模板語法(Template Syntax)
使用 template 來撰寫 HTML,允許將資料透過插值的方式,直接渲染到 template 中。
<template>
<div>
<!-- 文字插值 -->
<p>{{ message }}</p>
<!-- 表達式 -->
<p>{{ count + 1 }}</p>
<!-- 方法呼叫 -->
<p>{{ formatDate(date) }}</p>
</div>
</template>
Vue 的獨有優勢(和 React 相比)
1. 學習曲線較低
對團隊成員彼此程度的掌控落差不會太大,同時在書寫風格上,由官方統一規定,避免過於自由奔放,同時對不同專案的維護也能更快上手。
<!-- Vue 的單檔案組件結構清晰 -->
<template>
<!-- HTML 模板 -->
</template>
<script>
// JavaScript 邏輯
</script>
<style>
/* CSS 樣式 */
</style>
2. 擁有自己的獨特指令語法
雖然這點可能見仁見智,但 Vue 的指令系統提供了更直觀的方式來處理常見的 UI 邏輯:
<!-- Vue 指令 -->
<div v-if="isLoggedIn">歡迎回來</div>
<button @click="handleClick">點擊</button>
<!-- React JSX -->
<div>{isLoggedIn && '歡迎回來'}</div>
<button onClick="{handleClick}">點擊</button>
3. 資料雙向綁定更容易實現
因為有自己的指令,所以開發者實現資料雙向綁定可以非常容易(v-model),而 React 雖然也能實作類似的功能,但沒有 Vue 來得直覺。
<!-- Vue 雙向綁定 -->
<input v-model="username" />
<!-- React 需要手動處理 -->
<input value={username} onChange={(e) => setUsername(e.target.value)} />
4. 模板和邏輯分離
React 的 JSX 仍為部分開發者所詬病,在部分開發情境下,將邏輯和 UI 進行分離會顯得更易閱讀與維護。
<!-- Vue:結構清晰 -->
<template>
<div class="user-card">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
</template>
<script>
export default {
data() {
return {
user: {
name: 'John',
email: 'john@example.com',
},
};
},
};
</script>
5. 官方生態系統完整
Vue 官方提供了完整的解決方案(Vue Router、Vuex/Pinia、Vue CLI),不需要在眾多第三方套件中選擇。
2. Please explain the usage of v-model, v-bind and v-html
請解釋
v-model、v-bind和v-html的使用方式
v-model:資料雙向綁定
當改變資料的同時,隨即驅動改變 template 上渲染的內容,反之改變 template 的內容,也會更新資料。
<template>
<div>
<!-- 文字輸入框 -->
<input v-model="message" />
<p>輸入的內容:{{ message }}</p>
<!-- 核取方塊 -->
<input type="checkbox" v-model="checked" />
<p>是否勾選:{{ checked }}</p>
<!-- 選項列表 -->
<select v-model="selected">
<option value="A">選項 A</option>
<option value="B">選項 B</option>
</select>
<p>選擇的選項:{{ selected }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: '',
checked: false,
selected: 'A',
};
},
};
</script>
v-model 的修飾符
<!-- .lazy:改為在 change 事件後更新 -->
<input v-model.lazy="msg" />
<!-- .number:自動轉為數字 -->
<input v-model.number="age" type="number" />
<!-- .trim:自動過濾首尾空白字元 -->
<input v-model.trim="msg" />
v-bind:動態綁定屬性
常見於綁定 class 或連結、圖片等。當透過 v-bind 綁定 class 後,可以透過資料變動,來決定該 class 樣式是否被綁定,同理 API 回傳的圖片路徑、連結網址,也能透過綁定的形式來維持動態更新。
<template>
<div>
<!-- 綁定 class(可以簡寫為 :class) -->
<div :class="{ active: isActive, 'text-danger': hasError }">動態 class</div>
<!-- 綁定 style -->
<div :style="{ color: textColor, fontSize: fontSize + 'px' }">動態樣式</div>
<!-- 綁定圖片路徑 -->
<img :src="imageUrl" :alt="imageAlt" />
<!-- 綁定連結 -->
<a :href="linkUrl">前往連結</a>
<!-- 綁定自訂屬性 -->
<div :data-id="userId" :data-name="userName"></div>
</div>
</template>
<script>
export default {
data() {
return {
isActive: true,
hasError: false,
textColor: 'red',
fontSize: 16,
imageUrl: 'https://example.com/image.jpg',
imageAlt: '圖片描述',
linkUrl: 'https://example.com',
userId: 123,
userName: 'John',
};
},
};
</script>
v-bind 的簡寫
<!-- 完整寫法 -->
<img v-bind:src="imageUrl" />
<!-- 簡寫 -->
<img :src="imageUrl" />
<!-- 綁定多個屬性 -->
<div v-bind="objectOfAttrs"></div>
v-html:渲染 HTML 字串
如果資料回傳的內容中帶有 HTML 的標籤時,可以透過這個指令來渲染,例如顯示 Markdown 語法又或是對方直接回傳含有 <img> 標籤的圖片路徑。
<template>
<div>
<!-- 普通插值:會顯示 HTML 標籤 -->
<p>{{ rawHtml }}</p>
<!-- 輸出:<span style="color: red">紅色文字</span> -->
<!-- v-html:會渲染 HTML -->
<p v-html="rawHtml"></p>
<!-- 輸出:紅色文字(實際渲染為紅色) -->
</div>
</template>
<script>
export default {
data() {
return {
rawHtml: '<span style="color: red">紅色文字</span>',
};
},
};
</script>
⚠️ 安全性警告
千萬不要對使用者提供的內容使用 v-html,這會導致 XSS(跨站腳本攻擊)漏洞!
<!-- ❌ 危險:使用者可以注入惡意腳本 -->
<div v-html="userProvidedContent"></div>
<!-- ✅ 安全:只用於可信任的內容 -->
<div v-html="markdownRenderedContent"></div>
安全的替代方案
<template>
<div>
<!-- 使用套件進行 HTML 淨化 -->
<div v-html="sanitizedHtml"></div>
</div>
</template>
<script>
import DOMPurify from 'dompurify';
export default {
data() {
return {
userInput: '<img src=x onerror=alert("XSS")>',
};
},
computed: {
sanitizedHtml() {
// 使用 DOMPurify 清理 HTML
return DOMPurify.sanitize(this.userInput);
},
},
};
</script>
三者比較總結
| 指令 | 用途 | 簡寫 | 範例 |
|---|---|---|---|
v-model | 雙向綁定表單元素 | 無 | <input v-model="msg"> |
v-bind | 單向綁定屬性 | : | <img :src="url"> |
v-html | 渲染 HTML 字串 | 無 | <div v-html="html"></div> |
3. How to access HTML elements (Template Refs)?
Vue 若欲操作 HTML 元素,例如取得 input 元素並讓其聚焦 (focus) 該如何使用?
在 Vue 中,我們不建議使用 document.querySelector 來獲取 DOM 元素,而是使用 Template Refs。
Options API (Vue 2 / Vue 3)
使用 ref 屬性在模板中標記元素,然後透過 this.$refs 訪問。
<template>
<div>
<input ref="inputElement" />
<button @click="focusInput">Focus Input</button>
</div>
</template>
<script>
export default {
methods: {
focusInput() {
// 訪問 DOM 元素
this.$refs.inputElement.focus();
},
},
mounted() {
// 確保組件掛載後再訪問
console.log(this.$refs.inputElement);
},
};
</script>
Composition API (Vue 3)
在 <script setup> 中,我們宣告一個同名的 ref 變數來獲取元素。
<template>
<div>
<input ref="inputElement" />
<button @click="focusInput">Focus Input</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
// 1. 宣告一個與 template ref 同名的變數,初始值為 null
const inputElement = ref(null);
const focusInput = () => {
// 2. 透過 .value 訪問 DOM
inputElement.value?.focus();
};
onMounted(() => {
// 3. 確保組件掛載後再訪問
console.log(inputElement.value);
});
</script>
注意:
- 變數名稱必須與 template 中的
ref屬性值完全一致。 - 必須在組件掛載 (
onMounted) 後才能訪問到 DOM 元素,否則會是null。 - 如果是用在
v-for迴圈中,ref 會是一個陣列。
4. Please explain the difference between v-show and v-if
請解釋
v-show和v-if的區別
相同點(Similarities)
兩者都是用於操作 DOM 元素的顯示與隱藏,根據條件的不同,決定是否顯示內容。
<template>
<!-- 當 isVisible 為 true 時,都會顯示內容 -->
<div v-if="isVisible">使用 v-if</div>
<div v-show="isVisible">使用 v-show</div>
</template>
相異點(Differences)
1. DOM 操作方式不同
<template>
<div>
<!-- v-show:透過 CSS display 屬性控制 -->
<div v-show="false">這個元素仍存在於 DOM 中,只是 display: none</div>
<!-- v-if:直接從 DOM 中移除或新增 -->
<div v-if="false">這個元素不會出現在 DOM 中</div>
</div>
</template>
實際渲染結果:
<!-- v-show 渲染結果 -->
<div style="display: none;">這個元素仍存在於 DOM 中,只是 display: none</div>
<!-- v-if 渲染結果:false 時完全不存在 -->
<!-- 沒有任何 DOM 節點 -->
2. 效能差異
v-show:
- ✅ 初次渲染開銷較大(元素一定會被建立)
- ✅ 切換開銷較小(只改變 CSS)
- ✅ 適合頻繁切換的場景
v-if:
- ✅ 初次渲染開銷較小(條件為 false 時不渲染)
- ❌ 切換開銷較大(需要銷毀/重建元素)
- ✅ 適合條件不常改變的場景
<template>
<div>
<!-- 頻繁切換:使用 v-show -->
<button @click="toggleModal">切換彈窗</button>
<div v-show="showModal" class="modal">
彈窗內容(頻繁開關,使用 v-show 效能更好)
</div>
<!-- 不常切換:使用 v-if -->
<div v-if="userRole === 'admin'" class="admin-panel">
管理員面板(登入後幾乎不變,使用 v-if)
</div>
</div>
</template>
<script>
export default {
data() {
return {
showModal: false,
userRole: 'user',
};
},
methods: {
toggleModal() {
this.showModal = !this.showModal;
},
},
};
</script>
3. 生命週期觸發
v-if:
- 會觸發組件的完整生命週期
- 條件為 false 時,會執行
unmounted鉤子 - 條件為 true 時,會執行
mounted鉤子
<template>
<child-component v-if="showChild" />
</template>
<script>
// ChildComponent.vue
export default {
mounted() {
console.log('組件已掛載'); // v-if 從 false 變 true 時會執行
},
unmounted() {
console.log('組件已卸載'); // v-if 從 true 變 false 時會執行
},
};
</script>
v-show:
- 不會觸發組件的生命週期