### GDSC NYUST x 資訊創客社
<br>
### 網頁前端開發讀書會
#### Vue3 基礎課程
<br>
#### 2023/11/27 ( Mon ) 19:00 - 21:00
#### 講師:楊鈞元 Charles
#### 本次課程影片:(⚒️製作中)
<img src="" height="200px">
---
## 前言
----
### 什麼是Vue.js?
----
<img src="https://hackmd.io/_uploads/HyVxsjxH6.png" style="width:20%" />
一個平易近人、高效能且多功能的
漸進式JavaScript 框架
用於建立面向於 Desktop, Mobile, WebGL, Terminal 應用程式的前端
Vue.js 基於標準的 HTML、CSS 和 JavaScript
因此最廣泛應用就是用在建立網頁
----
#### 使用 Vue.js 的主要場景
----
- 無須任何建置就單純增強 html 檔案
- 作為 Web 元件遷入到任何頁面上
- Single-Page Application (SPA):
在使用網站時,只載入一個頁面,在網頁更新時渲染頁面,不需重新加載
- Fullstack / Server-Side Rendering (SSR):
從伺服器端取得資料,再繪製出完整的 HTML 頁面,有助提高搜尋引擎優化和初次載入性能
- Jamstack / Static Site Generation (SSG):
靜態網站生成:在部署前生成靜態 HTML,不需要在瀏覽器或服務器上進行渲染
----
### Vue.js的特點和優勢
----

----
- 組件化架構:
Vue.js 以整合 HTML、CSS、JS 的單文件元件為基礎,方便每個元件獨立測試,且程式碼較簡單易懂,有助於最佳化與除錯
- 輕量級:
Vue.js 相當輕量,是加載速度較快的因素之一
- 學習曲線平緩:
Vue.js 由簡入深,只要具有 HTML、CSS、JS 基礎知識就能快速學習並立即應用,能以最少的程式碼達到最大的成效
----
- 豐富工具:
雖然 Vue 的檔案較小,但由於開發社群日益壯大,有豐富的工具能夠實現進階的需求
- 增量採用:
如果需要,Vue 可以僅使用 CDN 引入在 html 檔案,即可使用 Vue 的強大功能
- 與其他框架整合:
作為靈活的前端框架,Vue 可以輕鬆地與其他工具和框架整合,以滿足項目的具體需求
----
- 虛擬 DOM Model 和渲染:
Vue使用虛擬DOM,當頁面異動時,無需不斷重新渲染整個DOM,同時提供更好的性能和更順暢的頁面體驗。
- DOM操控優勢:
透過雙向綁定功能,更新元件和追蹤數據更新相當簡單,綁定的數據可以像 DOM Object 一樣更新,因此在應用即時更新的情況下,Vue表現特別靈活。
----
- 速度和性能:
當比其他前端框架,Vue 有相對更加卓越的反映速度與性能表現
- 日益壯大的社區支援與相關教學:
基於 Vue 的卓越特色,深受大量前端開發社群認可,目前有越來越多基於 Vue 的 Libraries、Utilities、Framework 誕生,且 Vue 仍在非常積極的更新與優化,並相當願意與開發社群討論未來發展的方向
----
#### 已經可見使用 Vue.js 搭建部分網頁的大型企業

----
[🔗 GitLab 在7年前即全面採用 Vue.js 的原因](https://about.gitlab.com/blog/2016/10/20/why-we-chose-vue)
---
### 使用 Vite 建立 Vue 專案
#### 或
### 使用 Online Playground 體驗
----
### 使用 Vite 建立 Vue 專案
----
#### 使用環境
- VS Code
- WSL Ubuntu 22.04 (或任何Linux環境)
- Node.js V20.9.0 LTS (或任何其他穩定版本)
- Yarn 1.22.19 (或任何穩定版本)
----
```= bash
yarn create vite
```

----
### 選擇 Vue、JavaScript


----
### 初始化 Yarn 專案

```= bash
yarn
```
----
### 安裝 Prettier
```bash=
yarn add --dev --exact prettier
```
----
### 啟動 Vite 環境
```= bash
yarn dev
```

----
### 設定 Prettier
創建 .prettierrc.js
```js=
export default {
semi: true, // 在語句末尾使用分號
singleQuote: true, // 使用單引號而不是雙引號
trailingComma: 'all', // 在物件和數組最後一個元素後使用逗號
printWidth: 80, // 每行最大字符數
tabWidth: 2, // 縮進使用兩個空格
endOfLine: 'lf', // 使用 Linux 系統預設的 LF 換行符
arrowParens: 'always', // 當箭頭函式只有一個參數時,依然補上括號
vueIndentScriptAndStyle: true, // 在 .vue 檔案中,縮進 <script> 和 <style> 標籤內容
};
```
----
### 設定 vscode setting
在 .vscode/settings.json 寫入
```json=
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features"
}
}
```
----
### 安裝推薦VS Code Extension
----
在 .vscode/extensions.json 寫入
```json=
{
"recommendations": [
"vue.volar",
"vue.vscode-typescript-vue-plugin",
"formulahendry.auto-rename-tag",
"vscode-icons-team.vscode-icons",
"ecmel.vscode-html-css",
"oderwat.indent-rainbow",
"esbenp.prettier-vscode",
"albert.tabout",
"mhutchie.git-graph",
"wallabyjs.console-ninja",
"ms-vscode.vscode-typescript-next",
"xabikos.javascriptsnippets",
"sibiraj-s.vscode-scss-formatter"
]
}
```
----

---
### 使用 Online Playground 體驗
[🔗 StackBlitz](https://stackblitz.com/edit/vitejs-vite-j1wtka?file=index.html&terminal=dev)
---
### Single-File Components
### (SFC,單檔文件)
### [🔗 官方文檔](https://vuejs.org/guide/scaling-up/sfc.html)
----
- `<template>`: 元件的 HTML 模板
- `<script>`: 主要 JavaScript / TypeScript 程式
- lang:可以指定要使用 JS 或 TS
```html
<script> // 預設即JS
<script lang="ts">
```
- `<style>`: CSS 樣式
- lang:選擇指定的 css 預處理器,例如 SCSS、Less
```html
<style lang="scss">
```
- scope:讓樣式只對當前檔案生效,避免非預期的覆寫嵌套的其他 vue 檔
---
### Composition API vs Optional API
### (組合式與選項式)
### [🔗 官方文檔](https://vuejs.org/guide/extras/composition-api-faq.html)
----
#### Optional API
```javascript=
export default{
props: {},
components: {},
data() {
// 所有資料
},
computed() {
// 點擊增加資料
},
mounted() {
// axios取得資料
},
methods() {
// 刪除資料
}
}
```
----
#### Composition API
```javascript=
export default {
setup() {
// 所有資料...
// 取得資料...
// 增加資料...
// 刪除資料...
}
}
```
----
<img src="https://hackmd.io/_uploads/ryFLAXWrp.png" width="70%">
----
- 結論:
在 Vue 3 當中,使用 Optional API 跟 Composition API 都是可以的
但強烈推薦 **直接學習 Composition API**
---
### `<scirpt setup>` 語法糖
### [🔗 官方文檔](https://vuejs.org/api/sfc-script-setup.html#script-setup)
----
當我們採用 Composition API
以及 SFC 模式開發的時候
我們可以在所有的 vue 檔中
都使用 `<script setup>` 的語法糖
----
最傳統的 Optional API
```javascript=
<script>
export default {
data() {
return {
title: 'Counter',
count: 0,
};
},
methods: {
increment() {
this.count++;
},
},
};
</script>
```
----
使用 Composition API、搭配普通`<script>`
```javascript=
<script>
export default {
setup() {
const title = ref('Counter');
const count = ref(0);
const increment = () => {
count.value++;
};
return {
title,
count,
increment,
};
},
};
</script>
```
----
👍使用 Composition API、搭配 `<script setup>`
```javascript=
<script setup>
const title = 'Counter';
let count = 0;
const increment = () => {
count++;
};
</script>
```
----
#### 使用 `<script setup>` 的好處
- 更少的制式內容,更簡潔的代碼。
- 支援純 TypeScript 聲明 props 和自定義事件
- 運行時有更好的性能 (避免不必要的渲染)。
IDE 在類型推導有更好的性能 (減少 Code 中抽取 Type 的工作)
----
#### 結論
<div style="text-align:left">舊的語法:Vue 2、Optional API、傳統 script <br>
若想成為前端工程師,建議看得懂,會修改即可<br>
且可以搭配 AI 工具很好的理解程式碼。</div>
<br>
<div style="text-align:left">
而對新手來說,在學習階段建議把大量的時間放在
Vue3、Composition API、script setup <br>
因為未來更新與優化的功能、新語法<br>
必定支援Vue 3 開始的這些語法<br>
只能部分或完全不支援舊語法
</div>
[🔗 舉例](https://github.com/vuejs/rfcs/discussions/503)
----
#### 補充
若你看到的範例 Code...
- 是 `<script>`
- 且包含 `data()、methods()、this、$ 等等` => Optional API
- 只要包含 `setup()`
=> Composition API
<br>
- 是 `<script setup>` => 一定是 Composition API
---
### Reactivity API
### (響應式數據)
### [🔗 官方文檔](https://vuejs.org/api/reactivity-core.html#reactivity-api-core)
----
- ref() :
綁定 `<template>` 中 DOM 元素或子元件<br>並訪問到這些元素或組件的 prop 和 method,在 `<script>`區段中要取用該變數的數值時,<br>需要使用`.value`
```javascript=
import { ref } from 'vue';
const count = ref(0)
console.log(count.value) // 0
count.value = 1
console.log(count.value) // 1
```
注意,不要使用 let 宣告要成為 ref 的變數或物件
一律使用 const 宣告
----
#### 自動補上 `.value` 功能

----
- reactive() :
將一般的 JavaScript 物件轉成響應式物件
```javascript=
<script setup>
import { reactive } from 'vue';
const person = reactive({ name: 'John Doe', age: 30 });
console.log(person.age);
person.age++;
console.log(person.age);
</script>
```
----
#### 關於 ref() 與 reactive 取捨
<div style="text-align: left; font-size:32px">
由於 reactive 只能針對物件進行轉換,並且不需要使用.value,<br>
這會導致在看到呼叫變數時,沒有使用.value的情況<br>
無法直接確定是一般的 JS 變數,還是具有響應式的 reactive,<br>
因此社群上有一流派是完全不用 reactive,<br>
僅使用 ref 完成所有基礎響應式宣告
</div>
<br>
<div style="text-align: left; font-size:32px">
而另一派則喜歡將一般的數值、字串等使用 ref,<br>
當使用物件時,一律使用 reactive<br>
作者在接受訪談時表示,團隊可以自己選擇喜歡的風格,<br>
並在同一專案統一風格即可
</div>
[🔗作者回應 ref 與 reactive 的取捨](https://www.youtube.com/watch?v=e8Wlv4AGJjk&pp=ygUHI-iwouaIkA%3D%3D)
----
- computed():
基於數據的變化而自動更新屬性,適合同步操作
example:輸入民國年,即時自動顯示西元年
```html=
<template>
<div>
<label for="rocYear">輸入民國年:</label>
<input type="number" v-model="rocYear" />
<p v-show="rocYear">對應的西元年為:{{ gregorianYear }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const rocYear = ref(0);
const gregorianYear = computed(() => {
return rocYear.value + 1911;
});
</script>
```
----
對比 純 HTML、JS 如何達成同等效果
```html=
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>民國年轉西元年</title>
</head>
<body>
<label for="rocYear">輸入民國年:</label>
<input type="number" id="rocYearInput" />
<p id="result" style="display: none;"></p>
<script>
document.getElementById('rocYearInput').addEventListener('input', function() {
const rocYear = parseInt(this.value);
if (!isNaN(rocYear)) {
const gregorianYear = rocYear + 1911;
document.getElementById('result').innerText = '對應的西元年為:' + gregorianYear;
document.getElementById('result').style.display = 'block';
} else {
document.getElementById('result').style.display = 'none';
}
});
</script>
</body>
</html>
```
----
- watch():監聽一個或多個數據變化,且適合異步操作
example:監聽數值變化,取得變化前後數值
```html=
<script setup>
import { ref, watch } from 'vue';
// 創建一個響應式的計數器
const count = ref(0);
// 監聽 count 的變化
// watch(count, val => {
// console.log('計數器:', val);
// });
watch(count, (newValue, oldValue) => {
console.log(`計數器從 ${oldValue} 變為 ${newValue}`);
});
// 定義增加計數的方法
const increment = () => {
count.value++;
};
</script>
<template>
<div>
<p>當前計數: {{ count }}</p>
<button @click="increment">增加計數</button>
</div>
</template>
```
----
computed 跟 watch 取捨
為什麼不 watch 用到底?
----
<table style="font-size:28px">
<thead>
<tr>
<th style="text-align: center">Computed</th>
<th style="text-align: center">Watch</th>
</tr>
</thead>
<tbody>
<tr>
<td>基於現有數據計算出一個新的值</td>
<td>監聽物件的每一次變化,<br>包括數組和物件的內部屬性</td>
</tr>
<tr>
<td>保持數據的純粹性,使用同步操作</td>
<td>執行異步操作或複雜邏輯,<br>並在特定數據變化時執行相應的代碼</td>
</tr>
<tr>
<td>基於一組固定的數據屬性<br>而非外部邏輯</td>
<td>需要調用API、發送請求等操作</td>
</tr>
</tbody>
</table>
----
如果都使用 Watch 來取代 Computed...
- 過度監聽: 一律使用watch監聽所有數據變化,會讓程式碼變得難以理解和維護
- 異步操作複雜性:如果所有需要監聽變更數據後的操作都使用watch,會使程式碼更難以追蹤與除錯
- 性能考慮: computed具有自動緩存機制,只在依賴的數據發生實際變化時才重新計算。但Watch會在指定數據每次變化時都執行,過度使用 Watch 會導致不必要的效能降低。
---
### Template Syntax
### (模板語法)
### [🔗 官方文檔](https://vuejs.org/guide/essentials/template-syntax.html#template-syntax)
----
- Text Interpolation(文本插值):
使用雙花括號`{{ }}`可以將數據綁定到模板中
```html=
<script setup>
let msg = 'Hello World';
</script>
<template>
{{ msg }}
</template>
```
----
- JavaScript Expressions:
Vue 支援在 `<template>` 使用所有的 JS 表達式<br>
例如:
```javascript=
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>
```
----
#### Directive (指令)
- Content Directives (內容指令)
- Rendering Directives (渲染指令)
- Attribute Directives (屬性指令)
- Event Directives (事件指令)
- Form Directives (表單指令)
----
#### Content Directives (內容指令)
- v-text:等同 JS 的 textContent
```html=
<span v-text="msg"></span>
<!-- same as -->
<span>{{msg}}</span>
```
- v-html:等同 JS 的 innerHTML
要注意使用,僅用於受信任的內容,切勿用於使用者提供的內容。否則容易導致 XSS 攻擊
[🔗 初探XSS: 攻擊及防禦](https://wert6410.medium.com/xss-%E5%88%9D%E6%8E%A2xss-%E6%94%BB%E6%93%8A%E5%8F%8A%E9%98%B2%E7%A6%A6-f5b9f016ff43)
----
#### Rendering Directives (渲染指令)
----
- v-for:多次渲染 element 或 template block
```html=
<template>
<div>
<h1>購物車</h1>
<ul>
<li v-for="product in cart" :key="product.id">
{{ product.name }} - {{ product.price }} 元
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
const cart = ref([
{ id: 1, name: '商品一', price: 100 },
{ id: 2, name: '商品二', price: 150 },
{ id: 3, name: '商品三', price: 200 },
]);
</script>
```
想想看,為什麼不直接寫在 template 裡面,
而寫在 `<script setup>`,再傳遞給 `<template>`?
----
- v-if
- v-else-if
- v-else
同程式語言的 if...else if...else
達成指定條件才會渲染內容
```html=
<script setup>
import { ref } from 'vue';
const status = ref('');
</script>
<template>
<div>
<label for="statusSelect">選擇狀態:</label>
<select id="statusSelect" v-model="status">
<option value="success">成功</option>
<option value="loading">載入中</option>
<option value="error">錯誤</option>
</select>
<p>
狀態:
<span v-if="status === 'success'">✅操作成功!</span>
<span v-else-if="status === 'loading'">💭載入中....</span>
<span v-else>❌錯誤</span>
</p>
</div>
</template>
```
----
- v-show:一律渲染並隱藏,達成指定條件才會顯示
```html=
<script setup>
import { ref } from 'vue';
const isVisible = ref(false);
</script>
<template>
<div>
<button @click="isVisible = !isVisible">切換顯示</button>
<div v-show="isVisible">
<p>這是可切換顯示的內容。</p>
</div>
</div>
</template>
```
----
#### v-if 跟 v-show 的使用取捨
- v-if 在條件達成的時候才渲染內容
- v-show 先渲染好,條件達成後從隱藏改為顯示
<br>
<div style="text-align:left;font-size:32px">
預先準備好能確保使用者達成條件時馬上看到內容,<br>
但如果內容過於大量,<br>
那預先加載過多使用者不會都需要查看的內容,<br>
反而會導致浪費在預先加載的等待時間
</div>
----
#### Props Directives (屬性指令)
----
- v-bind:動態決定 element 的 props
```html=
<script setup>
import { ref } from 'vue';
const customColor = ref('red');
</script>
<template>
<input type="text" v-model="customColor" />
<p v-bind:style="`color:${customColor}`">hello</p>
<p :style="`color:${customColor}`">hello</p>
</template>
```
----
#### Event Directives (事件指令)
----
- v-on:監聽 DOM 事件,觸發相應 event
```html=
<script setup>
const handleClick = () => {
console.log('按鈕被點擊了!');
};
</script>
<template>
<button v-on:click="handleClick">點擊我</button>
<button @click="handleClick">點擊我</button>
</template>
```
----
#### Form Directives (表單指令)
----
- v-model:實現雙向數據綁定。讓使用者介面與瀏覽器之間即時同步數據。
```html=
<script setup>
import { ref } from 'vue';
const data = ref({
name: '',
email: '',
subscribe: false,
});
</script>
<template>
<form
@submit.prevent="submitForm"
style="display: flex; flex-direction: column; align-items: flex-start"
>
<div>
<label for="name">姓名:</label>
<input id="name" v-model="data.name" required />
</div>
<div>
<label for="email">電子郵件:</label>
<input id="email" v-model="data.email" type="email" required />
</div>
<div>
<label for="subscribe">訂閱最新消息</label>
<input id="subscribe" v-model="data.subscribe" type="checkbox" />
</div>
</form>
<p>
嗨!{{ data.name }} <br />
您的電子郵件是 {{ data.email }} <br />
您選擇 {{ data.subscribe ? '訂閱' : '不訂閱' }} 最新消息
</p>
</template>
```
----
### Modifiers
### (修飾符)
### [🔗 官方文檔](https://vuejs.org/guide/essentials/forms.html#modifiers)
----
Modifiers 是一種用來修改指令行為的方式
在指令後面以'.xxx'表示,用來調整指令行為或效果

----
例如:
```html=
<input v-model.trim="msg" />
<input v-model.number="age" />
<button @click.once="handleClick">點擊一次</button>
```
---
## 結語和問答
----
### 補充學習資源
----
### Q & A
{"slideOptions":"{\"transition\":\"concave\",\"allottedMinutes\":100}","title":"Vue3 基礎課程","description":"用來定義一個 element 的 box model 大小計算方式","contributors":"[{\"id\":\"f8142aa2-66aa-4867-821d-2f1ffff7a7ba\",\"add\":17564,\"del\":2370}]"}